summaryrefslogtreecommitdiff
path: root/mojo/public
diff options
context:
space:
mode:
authorHidehiko Abe <hidehiko@google.com>2018-04-23 20:01:13 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-04-23 20:01:13 -0700
commited7128dca79cff94e99465e3c1bc31d91d83c76d (patch)
treebd2d04362f66c36d4279f7a9735ba21ea3a2a021 /mojo/public
parentd6187ab7d79d95d101c2ecb83aa98c05bcdcccd1 (diff)
parent0ab20ac2283987e63b0e7c1318db2a5cf7c668d2 (diff)
downloadlibchrome-ed7128dca79cff94e99465e3c1bc31d91d83c76d.tar.gz
Migrate libmojo repository into libchrome, part 2. am: b268b43ac6
am: 0ab20ac228 Change-Id: I1b1afe0c902f1d122b100f5bf56d1df4a94eb090
Diffstat (limited to 'mojo/public')
-rw-r--r--mojo/public/BUILD.gn28
-rw-r--r--mojo/public/DEPS11
-rw-r--r--mojo/public/LICENSE27
-rw-r--r--mojo/public/c/system/BUILD.gn37
-rw-r--r--mojo/public/c/system/README.md869
-rw-r--r--mojo/public/c/system/buffer.h188
-rw-r--r--mojo/public/c/system/core.h22
-rw-r--r--mojo/public/c/system/data_pipe.h344
-rw-r--r--mojo/public/c/system/functions.h78
-rw-r--r--mojo/public/c/system/macros.h49
-rw-r--r--mojo/public/c/system/message_pipe.h341
-rw-r--r--mojo/public/c/system/platform_handle.h191
-rw-r--r--mojo/public/c/system/set_thunks_for_app.cc20
-rw-r--r--mojo/public/c/system/system_export.h33
-rw-r--r--mojo/public/c/system/tests/BUILD.gn38
-rw-r--r--mojo/public/c/system/tests/core_perftest.cc329
-rw-r--r--mojo/public/c/system/tests/core_unittest.cc322
-rw-r--r--mojo/public/c/system/tests/core_unittest_pure_c.c77
-rw-r--r--mojo/public/c/system/tests/macros_unittest.cc68
-rw-r--r--mojo/public/c/system/thunks.cc273
-rw-r--r--mojo/public/c/system/thunks.h148
-rw-r--r--mojo/public/c/system/types.h223
-rw-r--r--mojo/public/c/system/watcher.h184
-rw-r--r--mojo/public/c/test_support/BUILD.gn15
-rw-r--r--mojo/public/c/test_support/test_support.h52
-rw-r--r--mojo/public/cpp/bindings/BUILD.gn194
-rw-r--r--mojo/public/cpp/bindings/DEPS3
-rw-r--r--mojo/public/cpp/bindings/README.md1231
-rw-r--r--mojo/public/cpp/bindings/array_data_view.h244
-rw-r--r--mojo/public/cpp/bindings/array_traits.h71
-rw-r--r--mojo/public/cpp/bindings/array_traits_carray.h76
-rw-r--r--mojo/public/cpp/bindings/array_traits_stl.h127
-rw-r--r--mojo/public/cpp/bindings/array_traits_wtf_vector.h47
-rw-r--r--mojo/public/cpp/bindings/associated_binding.h171
-rw-r--r--mojo/public/cpp/bindings/associated_binding_set.h29
-rw-r--r--mojo/public/cpp/bindings/associated_group.h51
-rw-r--r--mojo/public/cpp/bindings/associated_group_controller.h93
-rw-r--r--mojo/public/cpp/bindings/associated_interface_ptr.h283
-rw-r--r--mojo/public/cpp/bindings/associated_interface_ptr_info.h77
-rw-r--r--mojo/public/cpp/bindings/associated_interface_request.h90
-rw-r--r--mojo/public/cpp/bindings/binding.h276
-rw-r--r--mojo/public/cpp/bindings/binding_set.h267
-rw-r--r--mojo/public/cpp/bindings/bindings_export.h34
-rw-r--r--mojo/public/cpp/bindings/clone_traits.h86
-rw-r--r--mojo/public/cpp/bindings/connection_error_callback.h21
-rw-r--r--mojo/public/cpp/bindings/connector.h241
-rw-r--r--mojo/public/cpp/bindings/disconnect_reason.h25
-rw-r--r--mojo/public/cpp/bindings/enum_traits.h27
-rw-r--r--mojo/public/cpp/bindings/filter_chain.h61
-rw-r--r--mojo/public/cpp/bindings/interface_data_view.h25
-rw-r--r--mojo/public/cpp/bindings/interface_endpoint_client.h193
-rw-r--r--mojo/public/cpp/bindings/interface_endpoint_controller.h37
-rw-r--r--mojo/public/cpp/bindings/interface_id.h35
-rw-r--r--mojo/public/cpp/bindings/interface_ptr.h242
-rw-r--r--mojo/public/cpp/bindings/interface_ptr_info.h63
-rw-r--r--mojo/public/cpp/bindings/interface_ptr_set.h107
-rw-r--r--mojo/public/cpp/bindings/interface_request.h178
-rw-r--r--mojo/public/cpp/bindings/lib/array_internal.cc59
-rw-r--r--mojo/public/cpp/bindings/lib/array_internal.h368
-rw-r--r--mojo/public/cpp/bindings/lib/array_serialization.h555
-rw-r--r--mojo/public/cpp/bindings/lib/associated_binding.cc62
-rw-r--r--mojo/public/cpp/bindings/lib/associated_group.cc34
-rw-r--r--mojo/public/cpp/bindings/lib/associated_group_controller.cc24
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr.cc18
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h157
-rw-r--r--mojo/public/cpp/bindings/lib/binding_state.cc90
-rw-r--r--mojo/public/cpp/bindings/lib/binding_state.h128
-rw-r--r--mojo/public/cpp/bindings/lib/bindings_internal.h336
-rw-r--r--mojo/public/cpp/bindings/lib/buffer.h70
-rw-r--r--mojo/public/cpp/bindings/lib/connector.cc493
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_handler.cc150
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_handler.h48
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_proxy.cc188
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_proxy.h49
-rw-r--r--mojo/public/cpp/bindings/lib/equals_traits.h94
-rw-r--r--mojo/public/cpp/bindings/lib/filter_chain.cc47
-rw-r--r--mojo/public/cpp/bindings/lib/fixed_buffer.cc30
-rw-r--r--mojo/public/cpp/bindings/lib/fixed_buffer.h39
-rw-r--r--mojo/public/cpp/bindings/lib/handle_interface_serialization.h181
-rw-r--r--mojo/public/cpp/bindings/lib/hash_util.h84
-rw-r--r--mojo/public/cpp/bindings/lib/interface_endpoint_client.cc412
-rw-r--r--mojo/public/cpp/bindings/lib/interface_ptr_state.h226
-rw-r--r--mojo/public/cpp/bindings/lib/map_data_internal.h85
-rw-r--r--mojo/public/cpp/bindings/lib/map_serialization.h182
-rw-r--r--mojo/public/cpp/bindings/lib/may_auto_lock.h62
-rw-r--r--mojo/public/cpp/bindings/lib/message.cc332
-rw-r--r--mojo/public/cpp/bindings/lib/message_buffer.cc52
-rw-r--r--mojo/public/cpp/bindings/lib/message_buffer.h43
-rw-r--r--mojo/public/cpp/bindings/lib/message_builder.cc69
-rw-r--r--mojo/public/cpp/bindings/lib/message_builder.h45
-rw-r--r--mojo/public/cpp/bindings/lib/message_header_validator.cc133
-rw-r--r--mojo/public/cpp/bindings/lib/message_internal.h82
-rw-r--r--mojo/public/cpp/bindings/lib/multiplex_router.cc960
-rw-r--r--mojo/public/cpp/bindings/lib/multiplex_router.h275
-rw-r--r--mojo/public/cpp/bindings/lib/native_enum_data.h26
-rw-r--r--mojo/public/cpp/bindings/lib/native_enum_serialization.h82
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct.cc34
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_data.cc22
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_data.h38
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_serialization.cc61
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_serialization.h134
-rw-r--r--mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc90
-rw-r--r--mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc68
-rw-r--r--mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc382
-rw-r--r--mojo/public/cpp/bindings/lib/serialization.h107
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_context.cc57
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_context.h77
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_forward.h123
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_util.h213
-rw-r--r--mojo/public/cpp/bindings/lib/string_serialization.h70
-rw-r--r--mojo/public/cpp/bindings/lib/string_traits_string16.cc42
-rw-r--r--mojo/public/cpp/bindings/lib/string_traits_wtf.cc84
-rw-r--r--mojo/public/cpp/bindings/lib/sync_call_restrictions.cc93
-rw-r--r--mojo/public/cpp/bindings/lib/sync_event_watcher.cc67
-rw-r--r--mojo/public/cpp/bindings/lib/sync_handle_registry.cc135
-rw-r--r--mojo/public/cpp/bindings/lib/sync_handle_watcher.cc76
-rw-r--r--mojo/public/cpp/bindings/lib/template_util.h120
-rw-r--r--mojo/public/cpp/bindings/lib/union_accessor.h33
-rw-r--r--mojo/public/cpp/bindings/lib/validate_params.h88
-rw-r--r--mojo/public/cpp/bindings/lib/validation_context.cc50
-rw-r--r--mojo/public/cpp/bindings/lib/validation_context.h169
-rw-r--r--mojo/public/cpp/bindings/lib/validation_errors.cc150
-rw-r--r--mojo/public/cpp/bindings/lib/validation_errors.h167
-rw-r--r--mojo/public/cpp/bindings/lib/validation_util.cc210
-rw-r--r--mojo/public/cpp/bindings/lib/validation_util.h206
-rw-r--r--mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h78
-rw-r--r--mojo/public/cpp/bindings/lib/wtf_hash_util.h132
-rw-r--r--mojo/public/cpp/bindings/lib/wtf_serialization.h12
-rw-r--r--mojo/public/cpp/bindings/map.h42
-rw-r--r--mojo/public/cpp/bindings/map_data_view.h63
-rw-r--r--mojo/public/cpp/bindings/map_traits.h56
-rw-r--r--mojo/public/cpp/bindings/map_traits_stl.h109
-rw-r--r--mojo/public/cpp/bindings/map_traits_wtf_hash_map.h64
-rw-r--r--mojo/public/cpp/bindings/message.h302
-rw-r--r--mojo/public/cpp/bindings/message_header_validator.h32
-rw-r--r--mojo/public/cpp/bindings/native_enum.h28
-rw-r--r--mojo/public/cpp/bindings/native_struct.h51
-rw-r--r--mojo/public/cpp/bindings/native_struct_data_view.h36
-rw-r--r--mojo/public/cpp/bindings/pipe_control_message_handler.h55
-rw-r--r--mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h29
-rw-r--r--mojo/public/cpp/bindings/pipe_control_message_proxy.h45
-rw-r--r--mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h22
-rw-r--r--mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h123
-rw-r--r--mojo/public/cpp/bindings/string_data_view.h34
-rw-r--r--mojo/public/cpp/bindings/string_traits.h54
-rw-r--r--mojo/public/cpp/bindings/string_traits_stl.h38
-rw-r--r--mojo/public/cpp/bindings/string_traits_string16.h37
-rw-r--r--mojo/public/cpp/bindings/string_traits_string_piece.h43
-rw-r--r--mojo/public/cpp/bindings/string_traits_wtf.h31
-rw-r--r--mojo/public/cpp/bindings/strong_associated_binding.h125
-rw-r--r--mojo/public/cpp/bindings/strong_binding.h125
-rw-r--r--mojo/public/cpp/bindings/strong_binding_set.h26
-rw-r--r--mojo/public/cpp/bindings/struct_ptr.h283
-rw-r--r--mojo/public/cpp/bindings/struct_traits.h165
-rw-r--r--mojo/public/cpp/bindings/sync_call_restrictions.h108
-rw-r--r--mojo/public/cpp/bindings/sync_event_watcher.h68
-rw-r--r--mojo/public/cpp/bindings/sync_handle_registry.h71
-rw-r--r--mojo/public/cpp/bindings/sync_handle_watcher.h75
-rw-r--r--mojo/public/cpp/bindings/tests/BUILD.gn146
-rw-r--r--mojo/public/cpp/bindings/tests/associated_interface_unittest.cc1189
-rw-r--r--mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc395
-rw-r--r--mojo/public/cpp/bindings/tests/binding_callback_unittest.cc338
-rw-r--r--mojo/public/cpp/bindings/tests/binding_set_unittest.cc416
-rw-r--r--mojo/public/cpp/bindings/tests/binding_unittest.cc611
-rw-r--r--mojo/public/cpp/bindings/tests/bindings_perftest.cc286
-rw-r--r--mojo/public/cpp/bindings/tests/blink_typemaps.gni8
-rw-r--r--mojo/public/cpp/bindings/tests/buffer_unittest.cc93
-rw-r--r--mojo/public/cpp/bindings/tests/chromium_typemaps.gni9
-rw-r--r--mojo/public/cpp/bindings/tests/connector_unittest.cc599
-rw-r--r--mojo/public/cpp/bindings/tests/constant_unittest.cc60
-rw-r--r--mojo/public/cpp/bindings/tests/container_test_util.cc52
-rw-r--r--mojo/public/cpp/bindings/tests/container_test_util.h55
-rw-r--r--mojo/public/cpp/bindings/tests/data_view_unittest.cc303
-rw-r--r--mojo/public/cpp/bindings/tests/e2e_perftest.cc204
-rw-r--r--mojo/public/cpp/bindings/tests/equals_unittest.cc122
-rw-r--r--mojo/public/cpp/bindings/tests/handle_passing_unittest.cc356
-rw-r--r--mojo/public/cpp/bindings/tests/hash_unittest.cc35
-rw-r--r--mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc937
-rw-r--r--mojo/public/cpp/bindings/tests/map_unittest.cc46
-rw-r--r--mojo/public/cpp/bindings/tests/message_queue.cc39
-rw-r--r--mojo/public/cpp/bindings/tests/message_queue.h44
-rw-r--r--mojo/public/cpp/bindings/tests/mojo_test_blink_export.h29
-rw-r--r--mojo/public/cpp/bindings/tests/mojo_test_export.h29
-rw-r--r--mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc314
-rw-r--r--mojo/public/cpp/bindings/tests/pickle_unittest.cc403
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_blink.cc67
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_blink.h88
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_chromium.cc69
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_chromium.h81
-rw-r--r--mojo/public/cpp/bindings/tests/rect_blink.h83
-rw-r--r--mojo/public/cpp/bindings/tests/rect_blink.typemap18
-rw-r--r--mojo/public/cpp/bindings/tests/rect_blink_traits.h35
-rw-r--r--mojo/public/cpp/bindings/tests/rect_chromium.h87
-rw-r--r--mojo/public/cpp/bindings/tests/rect_chromium.typemap18
-rw-r--r--mojo/public/cpp/bindings/tests/rect_chromium_traits.h34
-rw-r--r--mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc194
-rw-r--r--mojo/public/cpp/bindings/tests/request_response_unittest.cc157
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.cc111
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.h92
-rw-r--r--mojo/public/cpp/bindings/tests/sample_service_unittest.cc362
-rw-r--r--mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc251
-rw-r--r--mojo/public/cpp/bindings/tests/shared_rect.h43
-rw-r--r--mojo/public/cpp/bindings/tests/shared_rect_traits.h33
-rw-r--r--mojo/public/cpp/bindings/tests/struct_traits_unittest.cc553
-rw-r--r--mojo/public/cpp/bindings/tests/struct_unittest.cc526
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits.typemap26
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc36
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl.h168
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc137
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h196
-rw-r--r--mojo/public/cpp/bindings/tests/sync_method_unittest.cc831
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types_blink.typemap17
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap17
-rw-r--r--mojo/public/cpp/bindings/tests/type_conversion_unittest.cc161
-rw-r--r--mojo/public/cpp/bindings/tests/union_unittest.cc1246
-rw-r--r--mojo/public/cpp/bindings/tests/validation_context_unittest.cc297
-rw-r--r--mojo/public/cpp/bindings/tests/validation_test_input_parser.cc412
-rw-r--r--mojo/public/cpp/bindings/tests/validation_test_input_parser.h121
-rw-r--r--mojo/public/cpp/bindings/tests/validation_unittest.cc498
-rw-r--r--mojo/public/cpp/bindings/tests/variant_test_util.h32
-rw-r--r--mojo/public/cpp/bindings/tests/versioning_apptest.cc123
-rw-r--r--mojo/public/cpp/bindings/tests/versioning_test_service.cc127
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc60
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_map_unittest.cc41
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_types_unittest.cc245
-rw-r--r--mojo/public/cpp/bindings/thread_safe_interface_ptr.h394
-rw-r--r--mojo/public/cpp/bindings/type_converter.h116
-rw-r--r--mojo/public/cpp/bindings/union_traits.h39
-rw-r--r--mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h22
-rw-r--r--mojo/public/cpp/system/BUILD.gn57
-rw-r--r--mojo/public/cpp/system/README.md396
-rw-r--r--mojo/public/cpp/system/buffer.cc46
-rw-r--r--mojo/public/cpp/system/buffer.h82
-rw-r--r--mojo/public/cpp/system/core.h14
-rw-r--r--mojo/public/cpp/system/data_pipe.h163
-rw-r--r--mojo/public/cpp/system/functions.h26
-rw-r--r--mojo/public/cpp/system/handle.h220
-rw-r--r--mojo/public/cpp/system/handle_signals_state.h83
-rw-r--r--mojo/public/cpp/system/message.cc13
-rw-r--r--mojo/public/cpp/system/message.h83
-rw-r--r--mojo/public/cpp/system/message_pipe.h158
-rw-r--r--mojo/public/cpp/system/platform_handle.cc178
-rw-r--r--mojo/public/cpp/system/platform_handle.h92
-rw-r--r--mojo/public/cpp/system/simple_watcher.cc279
-rw-r--r--mojo/public/cpp/system/simple_watcher.h215
-rw-r--r--mojo/public/cpp/system/system_export.h34
-rw-r--r--mojo/public/cpp/system/tests/BUILD.gn23
-rw-r--r--mojo/public/cpp/system/tests/core_unittest.cc510
-rw-r--r--mojo/public/cpp/system/tests/handle_signals_state_unittest.cc42
-rw-r--r--mojo/public/cpp/system/tests/simple_watcher_unittest.cc277
-rw-r--r--mojo/public/cpp/system/tests/wait_set_unittest.cc376
-rw-r--r--mojo/public/cpp/system/tests/wait_unittest.cc321
-rw-r--r--mojo/public/cpp/system/wait.cc200
-rw-r--r--mojo/public/cpp/system/wait.h75
-rw-r--r--mojo/public/cpp/system/wait_set.cc371
-rw-r--r--mojo/public/cpp/system/wait_set.h124
-rw-r--r--mojo/public/cpp/system/watcher.cc20
-rw-r--r--mojo/public/cpp/system/watcher.h37
-rw-r--r--mojo/public/cpp/test_support/BUILD.gn19
-rw-r--r--mojo/public/cpp/test_support/lib/test_support.cc26
-rw-r--r--mojo/public/cpp/test_support/lib/test_utils.cc100
-rw-r--r--mojo/public/cpp/test_support/test_support.h35
-rw-r--r--mojo/public/cpp/test_support/test_utils.h40
-rw-r--r--mojo/public/interfaces/BUILD.gn9
-rw-r--r--mojo/public/interfaces/bindings/BUILD.gn29
-rw-r--r--mojo/public/interfaces/bindings/interface_control_messages.mojom67
-rw-r--r--mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom67
-rw-r--r--mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom46
-rw-r--r--mojo/public/interfaces/bindings/pipe_control_messages.mojom46
-rw-r--r--mojo/public/interfaces/bindings/tests/BUILD.gn204
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data26
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data24
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data24
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data27
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data25
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data26
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data27
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data18
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data36
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data35
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data36
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data38
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data7
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data0
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data2
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data7
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data6
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data4
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data6
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data9
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data10
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data7
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data13
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data11
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data9
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data9
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data12
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data11
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data12
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data48
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data48
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data25
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data40
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data40
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data48
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data19
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data20
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data20
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data28
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data30
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data30
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data21
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data19
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data9
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data17
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data19
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data13
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data13
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data14
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data22
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data27
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data20
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data21
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data34
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data34
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data23
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data24
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data23
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data14
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data612
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data18
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data20
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data13
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data12
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data57
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data13
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data14
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data34
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data27
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data32
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data34
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data18
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data18
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data20
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data13
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data19
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data16
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data15
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data21
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data12
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data34
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data26
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data32
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data35
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data37
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data38
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data37
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data37
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data36
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data38
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data24
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data24
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data40
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data26
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data34
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data40
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data21
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data32
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data12
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data33
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data36
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data14
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data27
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data19
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data19
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data20
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data17
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data8
-rw-r--r--mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected1
-rw-r--r--mojo/public/interfaces/bindings/tests/echo.mojom12
-rw-r--r--mojo/public/interfaces/bindings/tests/echo_import.mojom10
-rw-r--r--mojo/public/interfaces/bindings/tests/math_calculator.mojom12
-rw-r--r--mojo/public/interfaces/bindings/tests/no_module.mojom9
-rw-r--r--mojo/public/interfaces/bindings/tests/ping_service.mojom14
-rw-r--r--mojo/public/interfaces/bindings/tests/rect.mojom31
-rw-r--r--mojo/public/interfaces/bindings/tests/regression_tests.mojom76
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_factory.mojom41
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_import.mojom38
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_import2.mojom28
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_interfaces.mojom32
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_service.mojom112
-rw-r--r--mojo/public/interfaces/bindings/tests/scoping.mojom17
-rw-r--r--mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom36
-rw-r--r--mojo/public/interfaces/bindings/tests/struct_with_traits.mojom83
-rw-r--r--mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom54
-rw-r--r--mojo/public/interfaces/bindings/tests/test_bad_messages.mojom13
-rw-r--r--mojo/public/interfaces/bindings/tests/test_constants.mojom57
-rw-r--r--mojo/public/interfaces/bindings/tests/test_data_view.mojom41
-rw-r--r--mojo/public/interfaces/bindings/tests/test_export.mojom20
-rw-r--r--mojo/public/interfaces/bindings/tests/test_export2.mojom10
-rw-r--r--mojo/public/interfaces/bindings/tests/test_import.mojom11
-rw-r--r--mojo/public/interfaces/bindings/tests/test_native_types.mojom38
-rw-r--r--mojo/public/interfaces/bindings/tests/test_structs.mojom414
-rw-r--r--mojo/public/interfaces/bindings/tests/test_sync_methods.mojom44
-rw-r--r--mojo/public/interfaces/bindings/tests/test_unions.mojom105
-rw-r--r--mojo/public/interfaces/bindings/tests/test_wtf_types.mojom50
-rw-r--r--mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom18
-rw-r--r--mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom135
-rw-r--r--mojo/public/interfaces/bindings/tests/versioning_test_client.mojom34
-rw-r--r--mojo/public/interfaces/bindings/tests/versioning_test_service.mojom38
-rw-r--r--mojo/public/java/BUILD.gn64
-rw-r--r--mojo/public/java/bindings/README.md12
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java11
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java11
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java116
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java199
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java130
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java15
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java214
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java70
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java776
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java49
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java26
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java587
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java169
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java29
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java425
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java105
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java58
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java69
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java248
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java25
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java21
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java31
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java274
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java26
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java73
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java21
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java88
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java30
-rw-r--r--mojo/public/java/system/README.md25
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Core.java183
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java334
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Flags.java83
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Handle.java61
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java219
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java224
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java44
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java82
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Pair.java67
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java34
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java41
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java160
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java45
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java38
-rw-r--r--mojo/public/js/BUILD.gn93
-rw-r--r--mojo/public/js/README.md7
-rw-r--r--mojo/public/js/bindings.js322
-rw-r--r--mojo/public/js/buffer.js156
-rw-r--r--mojo/public/js/codec.js926
-rw-r--r--mojo/public/js/connector.js113
-rw-r--r--mojo/public/js/constants.cc33
-rw-r--r--mojo/public/js/constants.h30
-rw-r--r--mojo/public/js/core.js304
-rw-r--r--mojo/public/js/interface_types.js70
-rw-r--r--mojo/public/js/lib/control_message_handler.js111
-rw-r--r--mojo/public/js/lib/control_message_proxy.js104
-rw-r--r--mojo/public/js/lib/interface_endpoint_client.js232
-rw-r--r--mojo/public/js/lib/interface_endpoint_handle.js158
-rw-r--r--mojo/public/js/lib/pipe_control_message_handler.js61
-rw-r--r--mojo/public/js/lib/pipe_control_message_proxy.js56
-rw-r--r--mojo/public/js/new_bindings/base.js111
-rw-r--r--mojo/public/js/new_bindings/bindings.js275
-rw-r--r--mojo/public/js/new_bindings/buffer.js155
-rw-r--r--mojo/public/js/new_bindings/codec.js917
-rw-r--r--mojo/public/js/new_bindings/connector.js104
-rw-r--r--mojo/public/js/new_bindings/interface_types.js46
-rw-r--r--mojo/public/js/new_bindings/lib/control_message_handler.js106
-rw-r--r--mojo/public/js/new_bindings/lib/control_message_proxy.js97
-rw-r--r--mojo/public/js/new_bindings/router.js190
-rw-r--r--mojo/public/js/new_bindings/unicode.js51
-rw-r--r--mojo/public/js/new_bindings/validator.js511
-rw-r--r--mojo/public/js/router.js269
-rw-r--r--mojo/public/js/support.js53
-rw-r--r--mojo/public/js/tests/core_unittest.js223
-rw-r--r--mojo/public/js/tests/validation_test_input_parser.js299
-rw-r--r--mojo/public/js/tests/validation_unittest.js334
-rw-r--r--mojo/public/js/threading.js21
-rw-r--r--mojo/public/js/unicode.js51
-rw-r--r--mojo/public/js/validator.js560
-rw-r--r--mojo/public/tests/test_support_private.cc76
-rw-r--r--mojo/public/tests/test_support_private.h37
-rw-r--r--mojo/public/tools/bindings/BUILD.gn77
-rw-r--r--mojo/public/tools/bindings/README.md749
-rw-r--r--mojo/public/tools/bindings/blink_bindings_configuration.gni33
-rw-r--r--mojo/public/tools/bindings/chromium_bindings_configuration.gni83
-rwxr-xr-xmojo/public/tools/bindings/format_typemap_generator_args.py34
-rwxr-xr-xmojo/public/tools/bindings/generate_type_mappings.py148
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl131
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl29
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl65
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl448
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl49
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl16
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl41
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl96
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl64
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl212
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl111
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl236
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl118
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl30
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl46
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl70
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl161
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl57
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl32
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl14
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl92
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl12
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl56
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl47
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl141
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl24
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl47
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl82
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl94
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl39
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl20
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl80
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl85
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl41
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl3
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl12
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl418
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl42
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/header.java.tmpl14
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl297
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/union.java.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl33
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl198
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl70
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl49
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl126
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl154
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl60
-rw-r--r--mojo/public/tools/bindings/generators/mojom_cpp_generator.py818
-rw-r--r--mojo/public/tools/bindings/generators/mojom_java_generator.py550
-rw-r--r--mojo/public/tools/bindings/generators/mojom_js_generator.py425
-rw-r--r--mojo/public/tools/bindings/mojom.gni661
-rwxr-xr-xmojo/public/tools/bindings/mojom_bindings_generator.py336
-rw-r--r--mojo/public/tools/bindings/mojom_bindings_generator_unittest.py23
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/error.py27
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/fileutil.py18
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py91
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/generator.py153
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py24
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/module.py891
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py34
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/pack.py250
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py193
-rwxr-xr-xmojo/public/tools/bindings/pylib/mojom/generate/run_tests.py35
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py67
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/test_support.py193
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/translate.py639
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/ast.py410
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/lexer.py254
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/parser.py461
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py55
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py156
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py37
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py48
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py136
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py135
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py192
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py1497
-rwxr-xr-xmojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py36
-rwxr-xr-xmojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py34
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py80
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py0
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py32
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py47
-rwxr-xr-xmojo/public/tools/chrome_ipc/generate_mojom.py453
-rwxr-xr-xmojo/public/tools/gn/zip.py86
719 files changed, 74200 insertions, 0 deletions
diff --git a/mojo/public/BUILD.gn b/mojo/public/BUILD.gn
new file mode 100644
index 0000000000..3baf667064
--- /dev/null
+++ b/mojo/public/BUILD.gn
@@ -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.
+
+group("public") {
+ # Meta-target, don't link into production code.
+ testonly = true
+ deps = [
+ ":sdk",
+ "cpp/bindings",
+ "interfaces/bindings/tests:test_interfaces",
+ ]
+
+ if (is_android) {
+ deps += [
+ "java:bindings_java",
+ "java:system_java",
+ ]
+ }
+}
+
+group("sdk") {
+ deps = [
+ "c/system",
+ "cpp/bindings",
+ "js",
+ ]
+}
diff --git a/mojo/public/DEPS b/mojo/public/DEPS
new file mode 100644
index 0000000000..2e49995741
--- /dev/null
+++ b/mojo/public/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ # This code is checked into the chromium repo so it's fine to depend on this.
+ "+base",
+ "+build",
+ "+testing",
+
+ "+ipc/ipc_param_traits.h",
+
+ # internal includes.
+ "+mojo",
+]
diff --git a/mojo/public/LICENSE b/mojo/public/LICENSE
new file mode 100644
index 0000000000..972bb2edb0
--- /dev/null
+++ b/mojo/public/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2014 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/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn
new file mode 100644
index 0000000000..08185c7514
--- /dev/null
+++ b/mojo/public/c/system/BUILD.gn
@@ -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.
+
+component("system") {
+ output_name = "mojo_public_system"
+
+ sources = [
+ "buffer.h",
+ "core.h",
+ "data_pipe.h",
+ "functions.h",
+ "macros.h",
+ "message_pipe.h",
+ "platform_handle.h",
+ "system_export.h",
+ "thunks.cc",
+ "thunks.h",
+ "types.h",
+ "watcher.h",
+ ]
+
+ defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ]
+}
+
+# This should ONLY be depended upon directly by shared_library targets which
+# need to export the MojoSetSystemThunks symbol, like targets generated by the
+# mojo_native_application template in //services/service_manager/public/cpp/service.gni.
+source_set("set_thunks_for_app") {
+ sources = [
+ "set_thunks_for_app.cc",
+ ]
+
+ public_deps = [
+ ":system",
+ ]
+}
diff --git a/mojo/public/c/system/README.md b/mojo/public/c/system/README.md
new file mode 100644
index 0000000000..2abe80ffc7
--- /dev/null
+++ b/mojo/public/c/system/README.md
@@ -0,0 +1,869 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C System API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+The Mojo C System API is a lightweight API (with an eventually-stable ABI) upon
+which all higher layers of the Mojo system are built.
+
+This API exposes the fundamental capabilities to: create, read from, and write
+to **message pipes**; create, read from, and write to **data pipes**; create
+**shared buffers** and generate sharable handles to them; wrap platform-specific
+handle objects (such as **file descriptors**, **Windows handles**, and
+**Mach ports**) for seamless transit over message pipes; and efficiently watch
+handles for various types of state transitions.
+
+This document provides a brief guide to API usage with example code snippets.
+For a detailed API references please consult the headers in
+[//mojo/public/c/system](https://cs.chromium.org/chromium/src/mojo/public/c/system/).
+
+### A Note About Multithreading
+
+The Mojo C System API is entirely thread-agnostic. This means that all functions
+may be called from any thread in a process, and there are no restrictions on how
+many threads can use the same object at the same time.
+
+Of course this does not mean you can completely ignore potential concurrency
+issues -- such as a handle being closed on one thread while another thread is
+trying to perform an operation on the same handle -- but there is nothing
+fundamentally incorrect about using any given API or handle from multiple
+threads.
+
+### A Note About Synchronization
+
+Every Mojo API call is non-blocking and synchronously yields some kind of status
+result code, but the call's side effects -- such as affecting the state of
+one or more handles in the system -- may or may not occur asynchronously.
+
+Mojo objects can be observed for interesting state changes in a way that is
+thread-agnostic and in some ways similar to POSIX signal handlers: *i.e.*
+user-provided notification handlers may be invoked at any time on arbitrary
+threads in the process. It is entirely up to the API user to take appropriate
+measures to synchronize operations against other application state.
+
+The higher level [system](/mojo#High-Level-System-APIs) and
+[bindings](/mojo#High-Level-Bindings-APIs) APIs provide helpers to simplify Mojo
+usage in this regard, at the expense of some flexibility.
+
+## Result Codes
+
+Most API functions return a value of type `MojoResult`. This is an integral
+result code used to convey some meaningful level of detail about the result of a
+requested operation.
+
+See [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h)
+for different possible values. See documentation for individual API calls for
+more specific contextual meaning of various result codes.
+
+## Handles
+
+Every Mojo IPC primitive is identified by a generic, opaque integer handle of
+type `MojoHandle`. Handles can be acquired by creating new objects using various
+API calls, or by reading messages which contain attached handles.
+
+A `MojoHandle` can represent a message pipe endpoint, a data pipe consumer,
+a data pipe producer, a shared buffer reference, a wrapped native platform
+handle such as a POSIX file descriptor or a Windows system handle, or a watcher
+object (see [Signals & Watchers](#Signals-Watchers) below.)
+
+All types of handles except for watchers (which are an inherently local concept)
+can be attached to messages and sent over message pipes.
+
+Any `MojoHandle` may be closed by calling `MojoClose`:
+
+``` c
+MojoHandle x = DoSomethingToGetAValidHandle();
+MojoResult result = MojoClose(x);
+```
+
+If the handle passed to `MojoClose` was a valid handle, it will be closed and
+`MojoClose` returns `MOJO_RESULT_OK`. Otherwise it returns
+`MOJO_RESULT_INVALID_ARGUMENT`.
+
+Similar to native system handles on various popular platforms, `MojoHandle`
+values may be reused over time. Thus it is important to avoid logical errors
+which lead to misplaced handle ownership, double-closes, *etc.*
+
+## Message Pipes
+
+A message pipe is a bidirectional messaging channel which can carry arbitrary
+unstructured binary messages with zero or more `MojoHandle` attachments to be
+transferred from one end of a pipe to the other. Message pipes work seamlessly
+across process boundaries or within a single process.
+
+The [Embedder Development Kit (EDK)](/mojo/edk/embedder) provides the means to
+bootstrap one or more primordial cross-process message pipes, and it's up to
+Mojo embedders to expose this capability in some useful way. Once such a pipe is
+established, additional handles -- including other message pipe handles -- may
+be sent to a remote process using that pipe (or in turn, over other pipes sent
+over that pipe, or pipes sent over *that* pipe, and so on...)
+
+The public C System API exposes the ability to read and write messages on pipes
+and to create new message pipes.
+
+See [//mojo/public/c/system/message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/message_pipe.h)
+for detailed message pipe API documentation.
+
+### Creating Message Pipes
+
+`MojoCreateMessagePipe` can be used to create a new message pipe:
+
+``` c
+MojoHandle a, b;
+MojoResult result = MojoCreateMessagePipe(NULL, &a, &b);
+```
+
+After this snippet, `result` should be `MOJO_RESULT_OK` (it's really hard for
+this to fail!), and `a` and `b` will contain valid Mojo handles, one for each
+end of the new message pipe.
+
+Any messages written to `a` are eventually readable from `b`, and any messages
+written to `b` are eventually readable from `a`. If `a` is closed at any point,
+`b` will eventually become aware of this fact; likewise if `b` is closed, `a`
+will become aware of that.
+
+The state of these conditions can be queried and watched asynchronously as
+described in the [Signals & Watchers](#Signals-Watchers) section below.
+
+### Allocating Messages
+
+In order to avoid redundant internal buffer copies, Mojo would like to allocate
+your message storage buffers for you. This is easy:
+
+``` c
+MojoMessageHandle message;
+MojoResult result = MojoAllocMessage(6, NULL, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE,
+ &message);
+```
+
+Note that we have a special `MojoMessageHandle` type for message objects.
+
+The code above allocates a buffer for a message payload of 6 bytes with no
+handles attached.
+
+If we change our mind and decide not to send this message, we can delete it:
+
+``` c
+MojoResult result = MojoFreeMessage(message);
+```
+
+If we instead decide to send our newly allocated message, we first need to fill
+in the payload data with something interesting. How about a pleasant greeting:
+
+``` c
+void* buffer = NULL;
+MojoResult result = MojoGetMessageBuffer(message, &buffer);
+memcpy(buffer, "hello", 6);
+```
+
+Now we can write the message to a pipe. Note that attempting to write a message
+transfers ownership of the message object (and any attached handles) into the
+target pipe and there is therefore no need to subsequently call
+`MojoFreeMessage` on that message.
+
+### Writing Messages
+
+``` c
+result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE);
+```
+
+`MojoWriteMessage` is a *non-blocking* call: it always returns
+immediately. If its return code is `MOJO_RESULT_OK` the message will eventually
+find its way to the other end of the pipe -- assuming that end isn't closed
+first, of course. If the return code is anything else, the message is deleted
+and not transferred.
+
+In this case since we know `b` is still open, we also know the message will
+eventually arrive at `b`. `b` can be queried or watched to become aware of when
+the message arrives, but we'll ignore that complexity for now. See
+[Signals & Watchers](#Signals-Watchers) below for more information.
+
+*** aside
+**NOTE**: Although this is an implementation detail and not strictly guaranteed by the
+System API, it is true in the current implementation that the message will
+arrive at `b` before the above `MojoWriteMessage` call even returns, because `b`
+is in the same process as `a` and has never been transferred over another pipe.
+***
+
+### Reading Messages
+
+We can read a new message object from a pipe:
+
+``` c
+MojoMessageHandle message;
+uint32_t num_bytes;
+MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, NULL,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+and map its buffer to retrieve the contents:
+
+``` c
+void* buffer = NULL;
+MojoResult result = MojoGetMessageBuffer(message, &buffer);
+printf("Pipe says: %s", (const char*)buffer);
+```
+
+`result` should be `MOJO_RESULT_OK` and this snippet should write `"hello"` to
+`stdout`.
+
+If we try were to try reading again now that there are no messages on `b`:
+
+``` c
+MojoMessageHandle message;
+MojoResult result = MojoReadMessageNew(b, &message, NULL, NULL, NULL,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is
+not yet readable.
+
+### Messages With Handles
+
+Probably the most useful feature of Mojo IPC is that message pipes can carry
+arbitrary Mojo handles, including other message pipes. This is also
+straightforward.
+
+Here's an example which creates two pipes, using the first pipe to transfer
+one end of the second pipe. If you have a good imagination you can pretend the
+first pipe spans a process boundary, which makes the example more practically
+interesting:
+
+``` c
+MojoHandle a, b;
+MojoHandle c, d;
+MojoMessage message;
+
+// Allocate a message with an empty payload and handle |c| attached. Note that
+// this takes ownership of |c|, effectively invalidating its handle value.
+MojoResult result = MojoAllocMessage(0, &c, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE,
+ message);
+
+result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE);
+
+// Some time later...
+uint32_t num_bytes;
+MojoHandle e;
+uint32_t num_handles = 1;
+MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, &e,
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+At this point the handle in `e` is now referencing the same message pipe
+endpoint which was originally referenced by `c`.
+
+Note that `num_handles` above is initialized to 1 before we pass its address to
+`MojoReadMessageNew`. This is to indicate how much `MojoHandle` storage is
+available at the output buffer we gave it (`&e` above).
+
+If we didn't know how many handles to expect in an incoming message -- which is
+often the case -- we can use `MojoReadMessageNew` to query for this information
+first:
+
+``` c
+MojoMessageHandle message;
+uint32_t num_bytes = 0;
+uint32_t num_handles = 0;
+MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL,
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+If in this case there were a received message on `b` with some nonzero number
+of handles, `result` would be `MOJO_RESULT_RESOURCE_EXHAUSTED`, and both
+`num_bytes` and `num_handles` would be updated to reflect the payload size and
+number of attached handles on the next available message.
+
+It's also worth noting that if there did happen to be a message available with
+no payload and no handles (*i.e.* an empty message), this would actually return
+`MOJO_RESULT_OK`.
+
+## Data Pipes
+
+Data pipes provide an efficient unidirectional channel for moving large amounts
+of unframed data between two endpoints. Every data pipe has a fixed
+**element size** and **capacity**. Reads and writes must be done in sizes that
+are a multiple of the element size, and writes to the pipe can only be queued
+up to the pipe's capacity before reads must be done to make more space
+available.
+
+Every data pipe has a single **producer** handle used to write data into the
+pipe and a single **consumer** handle used to read data out of the pipe.
+
+Finally, data pipes support both immediate I/O -- reading into and writing out
+from user-supplied buffers -- as well as two-phase I/O, allowing callers to
+temporarily lock some portion of the data pipe in order to read or write its
+contents directly.
+
+See [//mojo/public/c/system/data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/data_pipe.h)
+for detailed data pipe API documentation.
+
+### Creating Data Pipes
+
+Use `MojoCreateDataPipe` to create a new data pipe. The
+`MojoCreateDataPipeOptions` structure is used to configure the new pipe, but
+this can be omitted to assume the default options of a single-byte element size
+and an implementation-defined default capacity (64 kB at the time of this
+writing.)
+
+``` c
+MojoHandle producer, consumer;
+MojoResult result = MojoCreateDataPipe(NULL, &producer, &consumer);
+```
+
+### Immediate I/O
+
+Data can be written into or read out of a data pipe using buffers provided by
+the caller. This is generally more convenient than two-phase I/O but is
+also less efficient due to extra copying.
+
+``` c
+uint32_t num_bytes = 12;
+MojoResult result = MojoWriteData(producer, "datadatadata", &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+```
+
+The above snippet will attempt to write 12 bytes into the data pipe, which
+should succeed and return `MOJO_RESULT_OK`. If the available capacity on the
+pipe was less than the amount requested (the input value of `*num_bytes`) this
+will copy what it can into the pipe and return the number of bytes written in
+`*num_bytes`. If no data could be copied this will instead return
+`MOJO_RESULT_SHOULD_WAIT`.
+
+Reading from the consumer is a similar operation.
+
+``` c
+char buffer[64];
+uint32_t num_bytes = 64;
+MojoResult result = MojoReadData(consumer, buffer, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+```
+
+This will attempt to read up to 64 bytes, returning the actual number of bytes
+read in `*num_bytes`.
+
+`MojoReadData` supports a number of interesting flags to change the behavior:
+you can peek at the data (copy bytes out without removing them from the pipe),
+query the number of bytes available without doing any actual reading of the
+contents, or discard data from the pipe without bothering to copy it anywhere.
+
+This also supports a `MOJO_READ_DATA_FLAG_ALL_OR_NONE` which ensures that the
+call succeeds **only** if the exact number of bytes requested could be read.
+Otherwise such a request will fail with `MOJO_READ_DATA_OUT_OF_RANGE`.
+
+### Two-Phase I/O
+
+Data pipes also support two-phase I/O operations, allowing a caller to
+temporarily lock a portion of the data pipe's storage for direct memory access.
+
+``` c
+void* buffer;
+uint32_t num_bytes = 1024;
+MojoResult result = MojoBeginWriteData(producer, &buffer, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+```
+
+This requests write access to a region of up to 1024 bytes of the data pipe's
+next available capacity. Upon success, `buffer` will point to the writable
+storage and `num_bytes` will indicate the size of the buffer there.
+
+The caller should then write some data into the memory region and release it
+ASAP, indicating the number of bytes actually written:
+
+``` c
+memcpy(buffer, "hello", 6);
+MojoResult result = MojoEndWriteData(producer, 6);
+```
+
+Two-phase reads look similar:
+
+``` c
+void* buffer;
+uint32_t num_bytes = 1024;
+MojoResult result = MojoBeginReadData(consumer, &buffer, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+// result should be MOJO_RESULT_OK, since there is some data available.
+
+printf("Pipe says: %s", (const char*)buffer); // Should say "hello".
+
+result = MojoEndReadData(consumer, 1); // Say we only consumed one byte.
+
+num_bytes = 1024;
+result = MojoBeginReadData(consumer, &buffer, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+printf("Pipe says: %s", (const char*)buffer); // Should say "ello".
+result = MojoEndReadData(consumer, 5);
+```
+
+## Shared Buffers
+
+Shared buffers are chunks of memory which can be mapped simultaneously by
+multiple processes. Mojo provides a simple API to make these available to
+applications.
+
+See [//mojo/public/c/system/buffer.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h)
+for detailed shared buffer API documentation.
+
+### Creating Buffer Handles
+
+Usage is straightforward. You can create a new buffer:
+
+``` c
+// Allocate a shared buffer of 4 kB.
+MojoHandle buffer;
+MojoResult result = MojoCreateSharedBuffer(NULL, 4096, &buffer);
+```
+
+You can also duplicate an existing shared buffer handle:
+
+``` c
+MojoHandle another_name_for_buffer;
+MojoResult result = MojoDuplicateBufferHandle(buffer, NULL,
+ &another_name_for_buffer);
+```
+
+This is useful if you want to retain a handle to the buffer while also sharing
+handles with one or more other clients. The allocated buffer remains valid as
+long as at least one shared buffer handle exists to reference it.
+
+### Mapping Buffers
+
+You can map (and later unmap) a specified range of the buffer to get direct
+memory access to its contents:
+
+``` c
+void* data;
+MojoResult result = MojoMapBuffer(buffer, 0, 64, &data,
+ MOJO_MAP_BUFFER_FLAG_NONE);
+
+*(int*)data = 42;
+result = MojoUnmapBuffer(data);
+```
+
+A buffer may have any number of active mappings at a time, in any number of
+processes.
+
+### Read-Only Handles
+
+An option can also be specified on `MojoDuplicateBufferHandle` to ensure
+that the newly duplicated handle can only be mapped to read-only memory:
+
+``` c
+MojoHandle read_only_buffer;
+MojoDuplicateBufferHandleOptions options;
+options.struct_size = sizeof(options);
+options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
+MojoResult result = MojoDuplicateBufferHandle(buffer, &options,
+ &read_only_buffer);
+
+// Attempt to map and write to the buffer using the read-only handle:
+void* data;
+result = MojoMapBuffer(read_only_buffer, 0, 64, &data,
+ MOJO_MAP_BUFFER_FLAG_NONE);
+*(int*)data = 42; // CRASH
+```
+
+*** note
+**NOTE:** One important limitation of the current implementation is that
+read-only handles can only be produced from a handle that was originally created
+by `MojoCreateSharedBuffer` (*i.e.*, you cannot create a read-only duplicate
+from a non-read-only duplicate), and the handle cannot have been transferred
+over a message pipe first.
+***
+
+## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
+
+Native platform handles to system objects can be wrapped as Mojo handles for
+seamless transit over message pipes. Mojo currently supports wrapping POSIX
+file descriptors, Windows handles, and Mach ports.
+
+See [//mojo/public/c/system/platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/platform_handle.h)
+for detailed platform handle API documentation.
+
+### Wrapping Basic Handle Types
+
+Wrapping a POSIX file descriptor is simple:
+
+``` c
+MojoPlatformHandle platform_handle;
+platform_handle.struct_size = sizeof(platform_handle);
+platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+platform_handle.value = (uint64_t)fd;
+MojoHandle handle;
+MojoResult result = MojoWrapPlatformHandle(&platform_handle, &handle);
+```
+
+Note that at this point `handle` effectively owns the file descriptor
+and if you were to call `MojoClose(handle)`, the file descriptor would be closed
+too; but we're not going to close it here! We're going to pretend we've sent it
+over a message pipe, and now we want to unwrap it on the other side:
+
+``` c
+MojoPlatformHandle platform_handle;
+platform_handle.struct_size = sizeof(platform_handle);
+MojoResult result = MojoUnwrapPlatformHandle(handle, &platform_handle);
+int fd = (int)platform_handle.value;
+```
+
+The situation looks nearly identical for wrapping and unwrapping Windows handles
+and Mach ports.
+
+### Wrapping Shared Buffer Handles
+
+Unlike other handle types, shared buffers have special meaning in Mojo, and it
+may be desirable to wrap a native platform handle -- along with some extra
+metadata -- such that be treated like a real Mojo shared buffer handle.
+Conversely it can also be useful to unpack a Mojo shared buffer handle into
+a native platform handle which references the buffer object. Both of these
+things can be done using the `MojoWrapPlatformSharedBuffer` and
+`MojoUnwrapPlatformSharedBuffer` APIs.
+
+On Windows, the wrapped platform handle must always be a Windows handle to
+a file mapping object.
+
+On OS X, the wrapped platform handle must be a memory-object send right.
+
+On all other POSIX systems, the wrapped platform handle must be a file
+descriptor for a shared memory object.
+
+## Signals & Watchers
+
+Message pipe and data pipe (producer and consumer) handles can change state in
+ways that may be interesting to a Mojo API user. For example, you may wish to
+know when a message pipe handle has messages available to be read or when its
+peer has been closed. Such states are reflected by a fixed set of boolean
+signals on each pipe handle.
+
+### Signals
+
+Every message pipe and data pipe handle maintains a notion of
+**signaling state** which may be queried at any time. For example:
+
+``` c
+MojoHandle a, b;
+MojoCreateMessagePipe(NULL, &a, &b);
+
+MojoHandleSignalsState state;
+MojoResult result = MojoQueryHandleSignalsState(a, &state);
+```
+
+The `MojoHandleSignalsState` structure exposes two fields: `satisfied_signals`
+and `satisfiable_signals`. Both of these are bitmasks of the type
+`MojoHandleSignals` (see [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h)
+for more details.)
+
+The `satisfied_signals` bitmask indicates signals which were satisfied on the
+handle at the time of the call, while the `satisfiable_signals` bitmask
+indicates signals which were still possible to satisfy at the time of the call.
+It is thus by definition always true that:
+
+``` c
+(satisfied_signals | satisfiable_signals) == satisfiable_signals
+```
+
+In other words a signal obviously cannot be satisfied if it is no longer
+satisfiable. Furthermore once a signal is unsatisfiable, *i.e.* is no longer
+set in `sastisfiable_signals`, it can **never** become satisfiable again.
+
+To illustrate this more clearly, consider the message pipe created above. Both
+ends of the pipe are still open and neither has been written to yet. Thus both
+handles start out with the same signaling state:
+
+| Field | State |
+|-----------------------|-------|
+| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_WRITABLE`
+| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_WRITABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+
+Writing a message to handle `b` will eventually alter the signaling state of `a`
+such that `MOJO_HANDLE_SIGNAL_READABLE` also becomes satisfied. If we were to
+then close `b`, the signaling state of `a` would look like:
+
+| Field | State |
+|-----------------------|-------|
+| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+
+Note that even though `a`'s peer is known to be closed (hence making `a`
+permanently unwritable) it remains readable because there's still an unread
+received message waiting to be read from `a`.
+
+Finally if we read the last message from `a` its signaling state becomes:
+
+| Field | State |
+|-----------------------|-------|
+| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+
+and we know definitively that `a` can never be read from again.
+
+### Watching Signals
+
+The ability to query a handle's signaling state can be useful, but it's not
+sufficient to support robust and efficient pipe usage. Mojo watchers empower
+users with the ability to **watch** a handle's signaling state for interesting
+changes and automatically invoke a notification handler in response.
+
+When a watcher is created it must be bound to a function pointer matching
+the following signature, defined in
+[//mojo/public/c/system/watcher.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/watcher.h):
+
+``` c
+typedef void (*MojoWatcherNotificationCallback)(
+ uintptr_t context,
+ MojoResult result,
+ MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags);
+```
+
+The `context` argument corresponds to a specific handle being watched by the
+watcher (read more below), and the remaining arguments provide details regarding
+the specific reason for the notification. It's important to be aware that a
+watcher's registered handler may be called **at any time** and
+**on any thread**.
+
+It's also helpful to understand a bit about the mechanism by which the handler
+can be invoked. Essentially, any Mojo C System API call may elicit a handle
+state change of some kind. If such a change is relevant to conditions watched by
+a watcher, and that watcher is in a state which allows it raise a corresponding
+notification, its notification handler will be invoked synchronously some time
+before the outermost System API call on the current thread's stack returns.
+
+Handle state changes can also occur as a result of incoming IPC from an external
+process. If a pipe in the current process is connected to an endpoint in another
+process and the internal Mojo system receives an incoming message bound for the
+local endpoint, the arrival of that message will trigger a state change on the
+receiving handle and may thus invoke one or more watchers' notification handlers
+as a result.
+
+The `MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM` flag on the notification
+handler's `flags` argument is used to indicate whether the handler was invoked
+due to such an internal system IPC event (if the flag is set), or if it was
+invoked synchronously due to some local API call (if the flag is unset.)
+This distinction can be useful to make in certain cases to *e.g.* avoid
+accidental reentrancy in user code.
+
+### Creating a Watcher
+
+Creating a watcher is simple:
+
+``` c
+
+void OnNotification(uintptr_t context,
+ MojoResult result,
+ MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags) {
+ // ...
+}
+
+MojoHandle w;
+MojoResult result = MojoCreateWatcher(&OnNotification, &w);
+```
+
+Like all other `MojoHandle` types, watchers may be destroyed by closing them
+with `MojoClose`. Unlike other `MojoHandle` types, watcher handles are **not**
+transferrable across message pipes.
+
+In order for a watcher to be useful, it has to watch at least one handle.
+
+### Adding a Handle to a Watcher
+
+Any given watcher can watch any given (message or data pipe) handle for some set
+of signaling conditions. A handle may be watched simultaneously by multiple
+watchers, and a single watcher can watch multiple different handles
+simultaneously.
+
+``` c
+MojoHandle a, b;
+MojoCreateMessagePipe(NULL, &a, &b);
+
+// Watch handle |a| for readability.
+const uintptr_t context = 1234;
+MojoResult result = MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context);
+```
+
+We've successfully instructed watcher `w` to begin watching pipe handle `a` for
+readability. However, our recently created watcher is still in a **disarmed**
+state, meaning that it will never fire a notification pertaining to this watched
+signaling condition. It must be **armed** before that can happen.
+
+### Arming a Watcher
+
+In order for a watcher to invoke its notification handler in response to a
+relevant signaling state change on a watched handle, it must first be armed. A
+watcher may only be armed if none of its watched handles would elicit a
+notification immediately once armed.
+
+In this case `a` is clearly not yet readable, so arming should succeed:
+
+``` c
+MojoResult result = MojoArmWatcher(w, NULL, NULL, NULL, NULL);
+```
+
+Now we can write to `b` to make `a` readable:
+
+``` c
+MojoWriteMessage(b, NULL, 0, NULL, 0, MOJO_WRITE_MESSAGE_NONE);
+```
+
+Eventually -- and in practice possibly before `MojoWriteMessage` even
+returns -- this will cause `OnNotification` to be invoked on the calling thread
+with the `context` value (*i.e.* 1234) that was given when the handle was added
+to the watcher.
+
+The `result` parameter will be `MOJO_RESULT_OK` to indicate that the watched
+signaling condition has been *satisfied*. If the watched condition had instead
+become permanently *unsatisfiable* (*e.g.*, if `b` were instead closed), `result`
+would instead indicate `MOJO_RESULT_FAILED_PRECONDITION`.
+
+**NOTE:** Immediately before a watcher decides to invoke its notification
+handler, it automatically disarms itself to prevent another state change from
+eliciting another notification. Therefore a watcher must be repeatedly rearmed
+in order to continue dispatching signaling notifications.
+
+As noted above, arming a watcher may fail if any of the watched conditions for
+a handle are already partially satisfied or fully unsatisfiable. In that case
+the caller may provide buffers for `MojoArmWatcher` to store information about
+a subset of the relevant watches which caused it to fail:
+
+``` c
+// Provide some storage for information about watches that are already ready.
+uint32_t num_ready_contexts = 4;
+uintptr_t ready_contexts[4];
+MojoResult ready_results[4];
+struct MojoHandleSignalsStates ready_states[4];
+MojoResult result = MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+ ready_results, ready_states);
+```
+
+Because `a` is still readable this operation will fail with
+`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_ready_contexts`
+informs `MojoArmWatcher` that it may store information regarding up to 4 watches
+which currently prevent arming. In this case of course there is only one active
+watch, so upon return we will see:
+
+* `num_ready_contexts` is `1`.
+* `ready_contexts[0]` is `1234`.
+* `ready_results[0]` is `MOJO_RESULT_OK`
+* `ready_states[0]` is the last known signaling state of handle `a`.
+
+In other words the stored information mirrors what would have been the
+notification handler's arguments if the watcher were allowed to arm and thus
+notify immediately.
+
+### Cancelling a Watch
+
+There are three ways a watch can be cancelled:
+
+* The watched handle is closed
+* The watcher handle is closed (in which case all of its watches are cancelled.)
+* `MojoCancelWatch` is explicitly called for a given `context`.
+
+In the above example this means any of the following operations will cancel the
+watch on `a`:
+
+``` c
+// Close the watched handle...
+MojoClose(a);
+
+// OR close the watcher handle...
+MojoClose(w);
+
+// OR explicitly cancel.
+MojoResult result = MojoCancelWatch(w, 1234);
+```
+
+In every case the watcher's notification handler is invoked for the cancelled
+watch(es) regardless of whether or not the watcher is or was armed at the time.
+The notification handler receives a `result` of `MOJO_RESULT_CANCELLED` for
+these notifications, and this is guaranteed to be the final notification for any
+given watch context.
+
+### Practical Watch Context Usage
+
+It is common and probably wise to treat a watch's `context` value as an opaque
+pointer to some thread-safe state associated in some way with the handle being
+watched. Here's a small example which uses a single watcher to watch both ends
+of a message pipe and accumulate a count of messages received at each end.
+
+``` c
+// NOTE: For the sake of simplicity this example code is not in fact
+// thread-safe. As long as there's only one thread running in the process and
+// no external process connections, this is fine.
+
+struct WatchedHandleState {
+ MojoHandle watcher;
+ MojoHandle handle;
+ int message_count;
+};
+
+void OnNotification(uintptr_t context,
+ MojoResult result,
+ MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags) {
+ struct WatchedHandleState* state = (struct WatchedHandleState*)(context);
+ MojoResult rv;
+
+ if (result == MOJO_RESULT_CANCELLED) {
+ // Cancellation is always the last notification and is guaranteed to
+ // eventually happen for every context, assuming no handles are leaked. We
+ // treat this as an opportunity to free the WatchedHandleState.
+ free(state);
+ return;
+ }
+
+ if (result == MOJO_RESULT_FAILED_PRECONDITION) {
+ // No longer readable, i.e. the other handle must have been closed. Better
+ // cancel. Note that we could also just call MojoClose(state->watcher) here
+ // since we know |context| is its only registered watch.
+ MojoCancelWatch(state->watcher, context);
+ return;
+ }
+
+ // This is the only handle watched by the watcher, so as long as we can't arm
+ // the watcher we know something's up with this handle. Try to read messages
+ // until we can successfully arm again or something goes terribly wrong.
+ while (MojoArmWatcher(state->watcher, NULL, NULL, NULL, NULL) ==
+ MOJO_RESULT_FAILED_PRECONDITION) {
+ rv = MojoReadMessageNew(state->handle, NULL, NULL, NULL,
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
+ if (rv == MOJO_RESULT_OK) {
+ state->message_count++;
+ } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+ MojoCancelWatch(state->watcher, context);
+ return;
+ }
+ }
+}
+
+MojoHandle a, b;
+MojoCreateMessagePipe(NULL, &a, &b);
+
+MojoHandle a_watcher, b_watcher;
+MojoCreateWatcher(&OnNotification, &a_watcher);
+MojoCreateWatcher(&OnNotification, &b_watcher)
+
+struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState));
+a_state->watcher = a_watcher;
+a_state->handle = a;
+a_state->message_count = 0;
+
+struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState));
+b_state->watcher = b_watcher;
+b_state->handle = b;
+b_state->message_count = 0;
+
+MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)a_state);
+MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)b_state);
+
+MojoArmWatcher(a_watcher, NULL, NULL, NULL, NULL);
+MojoArmWatcher(b_watcher, NULL, NULL, NULL, NULL);
+```
+
+Now any writes to `a` will increment `message_count` in `b_state`, and any
+writes to `b` will increment `message_count` in `a_state`.
+
+If either `a` or `b` is closed, both watches will be cancelled - one because
+watch cancellation is implicit in handle closure, and the other because its
+watcher will eventually detect that the handle is no longer readable.
diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h
new file mode 100644
index 0000000000..285e0d7b03
--- /dev/null
+++ b/mojo/public/c/system/buffer.h
@@ -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.
+
+// This file contains types/constants and functions specific to shared buffers.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_BUFFER_H_
+#define MOJO_PUBLIC_C_SYSTEM_BUFFER_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+// |MojoCreateSharedBufferOptions|: Used to specify creation parameters for a
+// shared buffer to |MojoCreateSharedBuffer()|.
+//
+// |uint32_t struct_size|: Set to the size of the
+// |MojoCreateSharedBufferOptions| struct. (Used to allow for future
+// extensions.)
+//
+// |MojoCreateSharedBufferOptionsFlags flags|: Reserved for future use.
+// |MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE|: No flags; default mode.
+
+typedef uint32_t MojoCreateSharedBufferOptionsFlags;
+
+#ifdef __cplusplus
+const MojoCreateSharedBufferOptionsFlags
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE = 0;
+#else
+#define MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE \
+ ((MojoCreateSharedBufferOptionsFlags)0)
+#endif
+
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+struct MOJO_ALIGNAS(8) MojoCreateSharedBufferOptions {
+ uint32_t struct_size;
+ MojoCreateSharedBufferOptionsFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoCreateSharedBufferOptions) == 8,
+ "MojoCreateSharedBufferOptions has wrong size");
+
+// |MojoDuplicateBufferHandleOptions|: Used to specify parameters in duplicating
+// access to a shared buffer to |MojoDuplicateBufferHandle()|.
+//
+// |uint32_t struct_size|: Set to the size of the
+// |MojoDuplicateBufferHandleOptions| struct. (Used to allow for future
+// extensions.)
+//
+// |MojoDuplicateBufferHandleOptionsFlags flags|: Flags to control the
+// behavior of |MojoDuplicateBufferHandle()|. May be some combination of
+// the following:
+//
+// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default
+// mode.
+// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY|: The duplicate
+// shared buffer can only be mapped read-only. A read-only duplicate
+// may only be created before any handles to the buffer are passed
+// over a message pipe.
+
+typedef uint32_t MojoDuplicateBufferHandleOptionsFlags;
+
+#ifdef __cplusplus
+const MojoDuplicateBufferHandleOptionsFlags
+ MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE = 0;
+const MojoDuplicateBufferHandleOptionsFlags
+ MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY = 1 << 0;
+#else
+#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE \
+ ((MojoDuplicateBufferHandleOptionsFlags)0)
+#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY \
+ ((MojoDuplicateBufferHandleOptionsFlags)1 << 0)
+#endif
+
+struct MojoDuplicateBufferHandleOptions {
+ uint32_t struct_size;
+ MojoDuplicateBufferHandleOptionsFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoDuplicateBufferHandleOptions) == 8,
+ "MojoDuplicateBufferHandleOptions has wrong size");
+
+// |MojoMapBufferFlags|: Used to specify different modes to |MojoMapBuffer()|.
+// |MOJO_MAP_BUFFER_FLAG_NONE| - No flags; default mode.
+
+typedef uint32_t MojoMapBufferFlags;
+
+#ifdef __cplusplus
+const MojoMapBufferFlags MOJO_MAP_BUFFER_FLAG_NONE = 0;
+#else
+#define MOJO_MAP_BUFFER_FLAG_NONE ((MojoMapBufferFlags)0)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Note: See the comment in functions.h about the meaning of the "optional"
+// label for pointer parameters.
+
+// Creates a buffer of size |num_bytes| bytes that can be shared between
+// processes. The returned handle may be duplicated any number of times by
+// |MojoDuplicateBufferHandle()|.
+//
+// To access the buffer's storage, one must call |MojoMapBuffer()|.
+//
+// |options| may be set to null for a shared buffer with the default options.
+//
+// On success, |*shared_buffer_handle| will be set to the handle for the shared
+// buffer. On failure it is not modified.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |*options| is invalid).
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has
+// been reached (e.g., if the requested size was too large, or if the
+// maximum number of handles was exceeded).
+// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|.
+MOJO_SYSTEM_EXPORT MojoResult MojoCreateSharedBuffer(
+ const struct MojoCreateSharedBufferOptions* options, // Optional.
+ uint64_t num_bytes, // In.
+ MojoHandle* shared_buffer_handle); // Out.
+
+// Duplicates the handle |buffer_handle| as a new shared buffer handle. On
+// success this returns the new handle in |*new_buffer_handle|. A shared buffer
+// remains allocated as long as there is at least one shared buffer handle
+// referencing it in at least one process in the system.
+//
+// |options| may be set to null to duplicate the buffer handle with the default
+// options.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |buffer_handle| is not a valid buffer handle or |*options| is invalid).
+// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|.
+MOJO_SYSTEM_EXPORT MojoResult MojoDuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const struct MojoDuplicateBufferHandleOptions* options, // Optional.
+ MojoHandle* new_buffer_handle); // Out.
+
+// Maps the part (at offset |offset| of length |num_bytes|) of the buffer given
+// by |buffer_handle| into memory, with options specified by |flags|. |offset +
+// num_bytes| must be less than or equal to the size of the buffer. On success,
+// |*buffer| points to memory with the requested part of the buffer. On
+// failure |*buffer| it is not modified.
+//
+// A single buffer handle may have multiple active mappings The permissions
+// (e.g., writable or executable) of the returned memory depend on th
+// properties of the buffer and properties attached to the buffer handle, as
+// well as |flags|.
+//
+// A mapped buffer must eventually be unmapped by calling |MojoUnmapBuffer()|
+// with the value of |*buffer| returned by this function.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |buffer_handle| is not a valid buffer handle or the range specified by
+// |offset| and |num_bytes| is not valid).
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the mapping operation itself failed
+// (e.g., due to not having appropriate address space available).
+MOJO_SYSTEM_EXPORT MojoResult MojoMapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer, // Out.
+ MojoMapBufferFlags flags);
+
+// Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must
+// have been the result of |MojoMapBuffer()| (not some other pointer inside
+// the mapped memory), and the entire mapping will be removed.
+//
+// A mapping may only be unmapped once.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |buffer| is invalid (e.g., is not the
+// result of |MojoMapBuffer()| or has already been unmapped).
+MOJO_SYSTEM_EXPORT MojoResult MojoUnmapBuffer(void* buffer); // In.
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_BUFFER_H_
diff --git a/mojo/public/c/system/core.h b/mojo/public/c/system/core.h
new file mode 100644
index 0000000000..03c0652a57
--- /dev/null
+++ b/mojo/public/c/system/core.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.
+
+// This is a catch-all header that includes everything.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_CORE_H_
+#define MOJO_PUBLIC_C_SYSTEM_CORE_H_
+
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/c/system/watcher.h"
+
+#endif // MOJO_PUBLIC_C_SYSTEM_CORE_H_
diff --git a/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h
new file mode 100644
index 0000000000..f51e36cb2e
--- /dev/null
+++ b/mojo/public/c/system/data_pipe.h
@@ -0,0 +1,344 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains types/constants and functions specific to data pipes.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_
+#define MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+// |MojoCreateDataPipeOptions|: Used to specify creation parameters for a data
+// pipe to |MojoCreateDataPipe()|.
+//
+// |uint32_t struct_size|: Set to the size of the |MojoCreateDataPipeOptions|
+// struct. (Used to allow for future extensions.)
+//
+// |MojoCreateDataPipeOptionsFlags flags|: Used to specify different modes of
+// operation. May be some combination of the following values:
+//
+// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode.
+//
+// |uint32_t element_num_bytes|: The size of an element, in bytes. All
+// transactions and buffers will consist of an integral number of
+// elements. Must be nonzero.
+//
+// |uint32_t capacity_num_bytes|: The capacity of the data pipe, in number of
+// bytes; must be a multiple of |element_num_bytes|. The data pipe will
+// always be able to queue AT LEAST this much data. Set to zero to opt for
+// a system-dependent automatically-calculated capacity (which will always
+// be at least one element).
+
+typedef uint32_t MojoCreateDataPipeOptionsFlags;
+
+#ifdef __cplusplus
+const MojoCreateDataPipeOptionsFlags MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE =
+ 0;
+#else
+#define MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE \
+ ((MojoCreateDataPipeOptionsFlags)0)
+#endif
+
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+struct MOJO_ALIGNAS(8) MojoCreateDataPipeOptions {
+ MOJO_ALIGNAS(4) uint32_t struct_size;
+ MOJO_ALIGNAS(4) MojoCreateDataPipeOptionsFlags flags;
+ MOJO_ALIGNAS(4) uint32_t element_num_bytes;
+ MOJO_ALIGNAS(4) uint32_t capacity_num_bytes;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoCreateDataPipeOptions) == 16,
+ "MojoCreateDataPipeOptions has wrong size");
+
+// |MojoWriteDataFlags|: Used to specify different modes to |MojoWriteData()|
+// and |MojoBeginWriteData()|. May be some combination of the following values:
+//
+// |MOJO_WRITE_DATA_FLAG_NONE| - No flags; default mode.
+// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| - Write either all the elements
+// requested or none of them.
+
+typedef uint32_t MojoWriteDataFlags;
+
+#ifdef __cplusplus
+const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_NONE = 0;
+const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_ALL_OR_NONE = 1 << 0;
+#else
+#define MOJO_WRITE_DATA_FLAG_NONE ((MojoWriteDataFlags)0)
+#define MOJO_WRITE_DATA_FLAG_ALL_OR_NONE ((MojoWriteDataFlags)1 << 0)
+#endif
+
+// |MojoReadDataFlags|: Used to specify different modes to |MojoReadData()| and
+// |MojoBeginReadData()|. May be some combination of the following values:
+//
+// |MOJO_READ_DATA_FLAG_NONE| - No flags; default mode.
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| - Read (or discard) either the requested
+// number of elements or none. NOTE: This flag is not currently supported
+// by |MojoBeginReadData()|.
+// |MOJO_READ_DATA_FLAG_DISCARD| - Discard (up to) the requested number of
+// elements.
+// |MOJO_READ_DATA_FLAG_QUERY| - Query the number of elements available to
+// read. For use with |MojoReadData()| only. Mutually exclusive with
+// |MOJO_READ_DATA_FLAG_DISCARD|, and |MOJO_READ_DATA_FLAG_ALL_OR_NONE|
+// is ignored if this flag is set.
+// |MOJO_READ_DATA_FLAG_PEEK| - Read elements without removing them. For use
+// with |MojoReadData()| only. Mutually exclusive with
+// |MOJO_READ_DATA_FLAG_DISCARD| and |MOJO_READ_DATA_FLAG_QUERY|.
+
+typedef uint32_t MojoReadDataFlags;
+
+#ifdef __cplusplus
+const MojoReadDataFlags MOJO_READ_DATA_FLAG_NONE = 0;
+const MojoReadDataFlags MOJO_READ_DATA_FLAG_ALL_OR_NONE = 1 << 0;
+const MojoReadDataFlags MOJO_READ_DATA_FLAG_DISCARD = 1 << 1;
+const MojoReadDataFlags MOJO_READ_DATA_FLAG_QUERY = 1 << 2;
+const MojoReadDataFlags MOJO_READ_DATA_FLAG_PEEK = 1 << 3;
+#else
+#define MOJO_READ_DATA_FLAG_NONE ((MojoReadDataFlags)0)
+#define MOJO_READ_DATA_FLAG_ALL_OR_NONE ((MojoReadDataFlags)1 << 0)
+#define MOJO_READ_DATA_FLAG_DISCARD ((MojoReadDataFlags)1 << 1)
+#define MOJO_READ_DATA_FLAG_QUERY ((MojoReadDataFlags)1 << 2)
+#define MOJO_READ_DATA_FLAG_PEEK ((MojoReadDataFlags)1 << 3)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Note: See the comment in functions.h about the meaning of the "optional"
+// label for pointer parameters.
+
+// Creates a data pipe, which is a unidirectional communication channel for
+// unframed data. Data must be read and written in multiples of discrete
+// discrete elements of size given in |options|.
+//
+// See |MojoCreateDataPipeOptions| for a description of the different options
+// available for data pipes.
+//
+// |options| may be set to null for a data pipe with the default options (which
+// will have an element size of one byte and have some system-dependent
+// capacity).
+//
+// On success, |*data_pipe_producer_handle| will be set to the handle for the
+// producer and |*data_pipe_consumer_handle| will be set to the handle for the
+// consumer. On failure they are not modified.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |*options| is invalid).
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has
+// been reached (e.g., if the requested capacity was too large, or if the
+// maximum number of handles was exceeded).
+// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|.
+MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe(
+ const struct MojoCreateDataPipeOptions* options, // Optional.
+ MojoHandle* data_pipe_producer_handle, // Out.
+ MojoHandle* data_pipe_consumer_handle); // Out.
+
+// Writes the data pipe producer given by |data_pipe_producer_handle|.
+//
+// |elements| points to data of size |*num_bytes|; |*num_bytes| must be a
+// multiple of the data pipe's element size. If
+// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data
+// is written (if enough write capacity is available) or none is.
+//
+// On success |*num_bytes| is set to the amount of data that was actually
+// written. On failure it is unmodified.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |data_pipe_producer_dispatcher| is not a handle to a data pipe
+// producer or |*num_bytes| is not a multiple of the data pipe's element
+// size.)
+// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been
+// closed.
+// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has
+// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data
+// (specified by |*num_bytes|) could not be written.
+// |MOJO_RESULT_BUSY| if there is a two-phase write ongoing with
+// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been
+// called, but not yet the matching |MojoEndWriteData()|).
+// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the
+// consumer is still open) and |flags| does *not* have
+// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set.
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoWriteData(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_bytes, // In/out.
+ MojoWriteDataFlags flags);
+
+// Begins a two-phase write to the data pipe producer given by
+// |data_pipe_producer_handle|. On success |*buffer| will be a pointer to which
+// the caller can write up to |*buffer_num_bytes| bytes of data.
+//
+// During a two-phase write, |data_pipe_producer_handle| is *not* writable.
+// If another caller tries to write to it by calling |MojoWriteData()| or
+// |MojoBeginWriteData()|, their request will fail with |MOJO_RESULT_BUSY|.
+//
+// If |MojoBeginWriteData()| returns MOJO_RESULT_OK and once the caller has
+// finished writing data to |*buffer|, |MojoEndWriteData()| must be called to
+// indicate the amount of data actually written and to complete the two-phase
+// write operation. |MojoEndWriteData()| need not be called when
+// |MojoBeginWriteData()| fails.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |data_pipe_producer_handle| is not a handle to a data pipe producer or
+// flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set.
+// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been
+// closed.
+// |MOJO_RESULT_BUSY| if there is already a two-phase write ongoing with
+// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been
+// called, but not yet the matching |MojoEndWriteData()|).
+// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the
+// consumer is still open).
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoBeginWriteData(MojoHandle data_pipe_producer_handle,
+ void** buffer, // Out.
+ uint32_t* buffer_num_bytes, // In/out.
+ MojoWriteDataFlags flags);
+
+// Ends a two-phase write that was previously initiated by
+// |MojoBeginWriteData()| for the same |data_pipe_producer_handle|.
+//
+// |num_bytes_written| must indicate the number of bytes actually written into
+// the two-phase write buffer. It must be less than or equal to the value of
+// |*buffer_num_bytes| output by |MojoBeginWriteData()|, and it must be a
+// multiple of the data pipe's element size.
+//
+// On failure, the two-phase write (if any) is ended (so the handle may become
+// writable again if there's space available) but no data written to |*buffer|
+// is "put into" the data pipe.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |data_pipe_producer_handle| is not a handle to a data pipe producer or
+// |num_bytes_written| is invalid (greater than the maximum value provided
+// by |MojoBeginWriteData()| or not a multiple of the element size).
+// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer is not in a
+// two-phase write (e.g., |MojoBeginWriteData()| was not called or
+// |MojoEndWriteData()| has already been called).
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoEndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written);
+
+// Reads data from the data pipe consumer given by |data_pipe_consumer_handle|.
+// May also be used to discard data or query the amount of data available.
+//
+// If |flags| has neither |MOJO_READ_DATA_FLAG_DISCARD| nor
+// |MOJO_READ_DATA_FLAG_QUERY| set, this tries to read up to |*num_bytes| (which
+// must be a multiple of the data pipe's element size) bytes of data to
+// |elements| and set |*num_bytes| to the amount actually read. If flags has
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, it will either read exactly
+// |*num_bytes| bytes of data or none. Additionally, if flags has
+// |MOJO_READ_DATA_FLAG_PEEK| set, the data read will remain in the pipe and be
+// available to future reads.
+//
+// If flags has |MOJO_READ_DATA_FLAG_DISCARD| set, it discards up to
+// |*num_bytes| (which again must be a multiple of the element size) bytes of
+// data, setting |*num_bytes| to the amount actually discarded. If flags has
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE|, it will either discard exactly
+// |*num_bytes| bytes of data or none. In this case, |MOJO_READ_DATA_FLAG_QUERY|
+// must not be set, and |elements| is ignored (and should typically be set to
+// null).
+//
+// If flags has |MOJO_READ_DATA_FLAG_QUERY| set, it queries the amount of data
+// available, setting |*num_bytes| to the number of bytes available. In this
+// case, |MOJO_READ_DATA_FLAG_DISCARD| must not be set, and
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is ignored, as are |elements| and the input
+// value of |*num_bytes|.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success (see above for a description of the different
+// operations).
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |data_pipe_consumer_handle| is invalid, the combination of flags in
+// |flags| is invalid, etc.).
+// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been
+// closed and data (or the required amount of data) was not available to
+// be read or discarded.
+// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE|
+// set and the required amount of data is not available to be read or
+// discarded (and the producer is still open).
+// |MOJO_RESULT_BUSY| if there is a two-phase read ongoing with
+// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been
+// called, but not yet the matching |MojoEndReadData()|).
+// |MOJO_RESULT_SHOULD_WAIT| if there is no data to be read or discarded (and
+// the producer is still open) and |flags| does *not* have
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set.
+MOJO_SYSTEM_EXPORT MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle,
+ void* elements, // Out.
+ uint32_t* num_bytes, // In/out.
+ MojoReadDataFlags flags);
+
+// Begins a two-phase read from the data pipe consumer given by
+// |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from
+// which the caller can read up to |*buffer_num_bytes| bytes of data.
+//
+// During a two-phase read, |data_pipe_consumer_handle| is *not* readable.
+// If another caller tries to read from it by calling |MojoReadData()| or
+// |MojoBeginReadData()|, their request will fail with |MOJO_RESULT_BUSY|.
+//
+// Once the caller has finished reading data from |*buffer|, |MojoEndReadData()|
+// must be called to indicate the number of bytes read and to complete the
+// two-phase read operation.
+//
+// |flags| must not have |MOJO_READ_DATA_FLAG_DISCARD|,
+// |MOJO_READ_DATA_FLAG_QUERY|, |MOJO_READ_DATA_FLAG_PEEK|, or
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |data_pipe_consumer_handle| is not a handle to a data pipe consumer,
+// or |flags| has invalid flags set.)
+// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been
+// closed.
+// |MOJO_RESULT_BUSY| if there is already a two-phase read ongoing with
+// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been
+// called, but not yet the matching |MojoEndReadData()|).
+// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be read (and the
+// producer is still open).
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoBeginReadData(MojoHandle data_pipe_consumer_handle,
+ const void** buffer, // Out.
+ uint32_t* buffer_num_bytes, // In/out.
+ MojoReadDataFlags flags);
+
+// Ends a two-phase read from the data pipe consumer given by
+// |data_pipe_consumer_handle| that was begun by a call to |MojoBeginReadData()|
+// on the same handle. |num_bytes_read| should indicate the amount of data
+// actually read; it must be less than or equal to the value of
+// |*buffer_num_bytes| output by |MojoBeginReadData()| and must be a multiple of
+// the element size.
+//
+// On failure, the two-phase read (if any) is ended (so the handle may become
+// readable again) but no data is "removed" from the data pipe.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |data_pipe_consumer_handle| is not a handle to a data pipe consumer or
+// |num_bytes_written| is greater than the maximum value provided by
+// |MojoBeginReadData()| or not a multiple of the element size).
+// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer is not in a
+// two-phase read (e.g., |MojoBeginReadData()| was not called or
+// |MojoEndReadData()| has already been called).
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoEndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_
diff --git a/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h
new file mode 100644
index 0000000000..d0656c67fc
--- /dev/null
+++ b/mojo/public/c/system/functions.h
@@ -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.
+
+// This file contains basic functions common to different Mojo system APIs.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_
+#define MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Note: Pointer parameters that are labelled "optional" may be null (at least
+// under some circumstances). Non-const pointer parameters are also labeled
+// "in", "out", or "in/out", to indicate how they are used. (Note that how/if
+// such a parameter is used may depend on other parameters or the requested
+// operation's success/failure. E.g., a separate |flags| parameter may control
+// whether a given "in/out" parameter is used for input, output, or both.)
+
+// Returns the time, in microseconds, since some undefined point in the past.
+// The values are only meaningful relative to other values that were obtained
+// from the same device without an intervening system restart. Such values are
+// guaranteed to be monotonically non-decreasing with the passage of real time.
+// Although the units are microseconds, the resolution of the clock may vary and
+// is typically in the range of ~1-15 ms.
+MOJO_SYSTEM_EXPORT MojoTimeTicks MojoGetTimeTicksNow(void);
+
+// Closes the given |handle|.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle.
+//
+// Concurrent operations on |handle| may succeed (or fail as usual) if they
+// happen before the close, be cancelled with result |MOJO_RESULT_CANCELLED| if
+// they properly overlap (this is likely the case with watchers), or fail with
+// |MOJO_RESULT_INVALID_ARGUMENT| if they happen after.
+MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle);
+
+// Queries the last known signals state of a handle.
+//
+// Note that no guarantees can be made about the accuracy of the returned
+// signals state by the time this returns, as other threads in the system may
+// change the handle's state at any time. Use with appropriate discretion.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success. |*signals_state| is populated with the
+// last known signals state of |handle|.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle or
+// |signals_state| is null.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoQueryHandleSignalsState(MojoHandle handle,
+ struct MojoHandleSignalsState* signals_state);
+
+// Retrieves system properties. See the documentation for |MojoPropertyType| for
+// supported property types and their corresponding output value type.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |type| is not recognized. In this case,
+// |value| is untouched.
+MOJO_SYSTEM_EXPORT MojoResult MojoGetProperty(MojoPropertyType type,
+ void* value);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_
diff --git a/mojo/public/c/system/macros.h b/mojo/public/c/system/macros.h
new file mode 100644
index 0000000000..917c69cc15
--- /dev/null
+++ b/mojo/public/c/system/macros.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 MOJO_PUBLIC_C_SYSTEM_MACROS_H_
+#define MOJO_PUBLIC_C_SYSTEM_MACROS_H_
+
+#include <stddef.h>
+
+// Assert things at compile time. (|msg| should be a valid identifier name.)
+// This macro is currently C++-only, but we want to use it in the C core.h.
+// Use like:
+// MOJO_STATIC_ASSERT(sizeof(Foo) == 12, "Foo has invalid size");
+#if defined(__cplusplus)
+#define MOJO_STATIC_ASSERT(expr, msg) static_assert(expr, msg)
+#else
+#define MOJO_STATIC_ASSERT(expr, msg)
+#endif
+
+// Like the C++11 |alignof| operator.
+#if __cplusplus >= 201103L
+#define MOJO_ALIGNOF(type) alignof(type)
+#elif defined(__GNUC__)
+#define MOJO_ALIGNOF(type) __alignof__(type)
+#elif defined(_MSC_VER)
+// The use of |sizeof| is to work around a bug in MSVC 2010 (see
+// http://goo.gl/isH0C; supposedly fixed since then).
+#define MOJO_ALIGNOF(type) (sizeof(type) - sizeof(type) + __alignof(type))
+#else
+#error "Please define MOJO_ALIGNOF() for your compiler."
+#endif
+
+// Specify the alignment of a |struct|, etc.
+// Use like:
+// struct MOJO_ALIGNAS(8) Foo { ... };
+// Unlike the C++11 |alignas()|, |alignment| must be an integer. It may not be a
+// type, nor can it be an expression like |MOJO_ALIGNOF(type)| (due to the
+// non-C++11 MSVS version).
+#if __cplusplus >= 201103L
+#define MOJO_ALIGNAS(alignment) alignas(alignment)
+#elif defined(__GNUC__)
+#define MOJO_ALIGNAS(alignment) __attribute__((aligned(alignment)))
+#elif defined(_MSC_VER)
+#define MOJO_ALIGNAS(alignment) __declspec(align(alignment))
+#else
+#error "Please define MOJO_ALIGNAS() for your compiler."
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_MACROS_H_
diff --git a/mojo/public/c/system/message_pipe.h b/mojo/public/c/system/message_pipe.h
new file mode 100644
index 0000000000..b759bc73db
--- /dev/null
+++ b/mojo/public/c/system/message_pipe.h
@@ -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.
+
+// This file contains types/constants and functions specific to message pipes.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_
+#define MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+// |MojoMessageHandle|: Used to refer to message objects created by
+// |MojoAllocMessage()| and transferred by |MojoWriteMessageNew()| or
+// |MojoReadMessageNew()|.
+
+typedef uintptr_t MojoMessageHandle;
+
+#ifdef __cplusplus
+const MojoMessageHandle MOJO_MESSAGE_HANDLE_INVALID = 0;
+#else
+#define MOJO_MESSAGE_HANDLE_INVALID ((MojoMessageHandle)0)
+#endif
+
+// |MojoCreateMessagePipeOptions|: Used to specify creation parameters for a
+// message pipe to |MojoCreateMessagePipe()|.
+// |uint32_t struct_size|: Set to the size of the
+// |MojoCreateMessagePipeOptions| struct. (Used to allow for future
+// extensions.)
+// |MojoCreateMessagePipeOptionsFlags flags|: Used to specify different modes
+// of operation.
+// |MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode.
+
+typedef uint32_t MojoCreateMessagePipeOptionsFlags;
+
+#ifdef __cplusplus
+const MojoCreateMessagePipeOptionsFlags
+ MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE = 0;
+#else
+#define MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE \
+ ((MojoCreateMessagePipeOptionsFlags)0)
+#endif
+
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+struct MOJO_ALIGNAS(8) MojoCreateMessagePipeOptions {
+ uint32_t struct_size;
+ MojoCreateMessagePipeOptionsFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoCreateMessagePipeOptions) == 8,
+ "MojoCreateMessagePipeOptions has wrong size");
+
+// |MojoWriteMessageFlags|: Used to specify different modes to
+// |MojoWriteMessage()|.
+// |MOJO_WRITE_MESSAGE_FLAG_NONE| - No flags; default mode.
+
+typedef uint32_t MojoWriteMessageFlags;
+
+#ifdef __cplusplus
+const MojoWriteMessageFlags MOJO_WRITE_MESSAGE_FLAG_NONE = 0;
+#else
+#define MOJO_WRITE_MESSAGE_FLAG_NONE ((MojoWriteMessageFlags)0)
+#endif
+
+// |MojoReadMessageFlags|: Used to specify different modes to
+// |MojoReadMessage()|.
+// |MOJO_READ_MESSAGE_FLAG_NONE| - No flags; default mode.
+// |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| - If the message is unable to be read
+// for whatever reason (e.g., the caller-supplied buffer is too small),
+// discard the message (i.e., simply dequeue it).
+
+typedef uint32_t MojoReadMessageFlags;
+
+#ifdef __cplusplus
+const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_NONE = 0;
+const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_MAY_DISCARD = 1 << 0;
+#else
+#define MOJO_READ_MESSAGE_FLAG_NONE ((MojoReadMessageFlags)0)
+#define MOJO_READ_MESSAGE_FLAG_MAY_DISCARD ((MojoReadMessageFlags)1 << 0)
+#endif
+
+// |MojoAllocMessageFlags|: Used to specify different options for
+// |MojoAllocMessage()|.
+// |MOJO_ALLOC_MESSAGE_FLAG_NONE| - No flags; default mode.
+
+typedef uint32_t MojoAllocMessageFlags;
+
+#ifdef __cplusplus
+const MojoAllocMessageFlags MOJO_ALLOC_MESSAGE_FLAG_NONE = 0;
+#else
+#define MOJO_ALLOC_MESSAGE_FLAG_NONE ((MojoAllocMessageFlags)0)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Note: See the comment in functions.h about the meaning of the "optional"
+// label for pointer parameters.
+
+// Creates a message pipe, which is a bidirectional communication channel for
+// framed data (i.e., messages). Messages can contain plain data and/or Mojo
+// handles.
+//
+// |options| may be set to null for a message pipe with the default options.
+//
+// On success, |*message_pipe_handle0| and |*message_pipe_handle1| are set to
+// handles for the two endpoints (ports) for the message pipe.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
+// |*options| is invalid).
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has
+// been reached.
+MOJO_SYSTEM_EXPORT MojoResult MojoCreateMessagePipe(
+ const struct MojoCreateMessagePipeOptions* options, // Optional.
+ MojoHandle* message_pipe_handle0, // Out.
+ MojoHandle* message_pipe_handle1); // Out.
+
+// Writes a message to the message pipe endpoint given by |message_pipe_handle|,
+// with message data specified by |bytes| of size |num_bytes| and attached
+// handles specified by |handles| of count |num_handles|, and options specified
+// by |flags|. If there is no message data, |bytes| may be null, in which case
+// |num_bytes| must be zero. If there are no attached handles, |handles| may be
+// null, in which case |num_handles| must be zero.
+//
+// If handles are attached, the handles will no longer be valid (on success the
+// receiver will receive equivalent, but logically different, handles). Handles
+// to be sent should not be in simultaneous use (e.g., on another thread).
+//
+// Returns:
+// |MOJO_RESULT_OK| on success (i.e., the message was enqueued).
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if
+// |message_pipe_handle| is not a valid handle, or some of the
+// requirements above are not satisfied).
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if some system limit has been reached, or
+// the number of handles to send is too large (TODO(vtl): reconsider the
+// latter case).
+// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed.
+// Note that closing an endpoint is not necessarily synchronous (e.g.,
+// across processes), so this function may succeed even if the other
+// endpoint has been closed (in which case the message would be dropped).
+// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|.
+// |MOJO_RESULT_BUSY| if some handle to be sent is currently in use.
+//
+// TODO(vtl): Add a notion of capacity for message pipes, and return
+// |MOJO_RESULT_SHOULD_WAIT| if the message pipe is full.
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoWriteMessage(MojoHandle message_pipe_handle,
+ const void* bytes, // Optional.
+ uint32_t num_bytes,
+ const MojoHandle* handles, // Optional.
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags);
+
+// Writes a message to the message pipe endpoint given by |message_pipe_handle|.
+//
+// |message|: A message object allocated by |MojoAllocMessage()|. Ownership of
+// the message is passed into Mojo.
+//
+// Returns results corresponding to |MojoWriteMessage()| above.
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoWriteMessageNew(MojoHandle message_pipe_handle,
+ MojoMessageHandle message,
+ MojoWriteMessageFlags);
+
+// Reads the next message from a message pipe, or indicates the size of the
+// message if it cannot fit in the provided buffers. The message will be read
+// in its entirety or not at all; if it is not, it will remain enqueued unless
+// the |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| flag was passed. At most one
+// message will be consumed from the queue, and the return value will indicate
+// whether a message was successfully read.
+//
+// |num_bytes| and |num_handles| are optional in/out parameters that on input
+// must be set to the sizes of the |bytes| and |handles| arrays, and on output
+// will be set to the actual number of bytes or handles contained in the
+// message (even if the message was not retrieved due to being too large).
+// Either |num_bytes| or |num_handles| may be null if the message is not
+// expected to contain the corresponding type of data, but such a call would
+// fail with |MOJO_RESULT_RESOURCE_EXHAUSTED| if the message in fact did
+// contain that type of data.
+//
+// |bytes| and |handles| will receive the contents of the message, if it is
+// retrieved. Either or both may be null, in which case the corresponding size
+// parameter(s) must also be set to zero or passed as null.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success (i.e., a message was actually read).
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid.
+// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the message was too large to fit in the
+// provided buffer(s). The message will have been left in the queue or
+// discarded, depending on flags.
+// |MOJO_RESULT_SHOULD_WAIT| if no message was available to be read.
+//
+// TODO(vtl): Reconsider the |MOJO_RESULT_RESOURCE_EXHAUSTED| error code; should
+// distinguish this from the hitting-system-limits case.
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoReadMessage(MojoHandle message_pipe_handle,
+ void* bytes, // Optional out.
+ uint32_t* num_bytes, // Optional in/out.
+ MojoHandle* handles, // Optional out.
+ uint32_t* num_handles, // Optional in/out.
+ MojoReadMessageFlags flags);
+
+// Reads the next message from a message pipe and returns a message containing
+// the message bytes. The returned message must eventually be freed using
+// |MojoFreeMessage()|.
+//
+// Message payload can be accessed using |MojoGetMessageBuffer()|.
+//
+// |message_pipe_handle|, |num_bytes|, |handles|, |num_handles|, and |flags|
+// correspond to their use in |MojoReadMessage()| above, with the
+// exception that |num_bytes| is only an output argument.
+// |message| must be non-null unless |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| is
+// set in flags.
+//
+// Return values correspond to the return values for |MojoReadMessage()| above.
+// On success (MOJO_RESULT_OK), |*message| will contain a handle to a message
+// object which may be passed to |MojoGetMessageBuffer()|. The caller owns the
+// message object and is responsible for freeing it via |MojoFreeMessage()|.
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoReadMessageNew(MojoHandle message_pipe_handle,
+ MojoMessageHandle* message, // Optional out.
+ uint32_t* num_bytes, // Optional out.
+ MojoHandle* handles, // Optional out.
+ uint32_t* num_handles, // Optional in/out.
+ MojoReadMessageFlags flags);
+
+// Fuses two message pipe endpoints together. Given two pipes:
+//
+// A <-> B and C <-> D
+//
+// Fusing handle B and handle C results in a single pipe:
+//
+// A <-> D
+//
+// Handles B and C are ALWAYS closed. Any unread messages at C will eventually
+// be delivered to A, and any unread messages at B will eventually be delivered
+// to D.
+//
+// NOTE: A handle may only be fused if it is an open message pipe handle which
+// has not been written to.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_FAILED_PRECONDITION| if both handles were valid message pipe
+// handles but could not be merged (e.g. one of them has been written to).
+// |MOJO_INVALID_ARGUMENT| if either handle is not a fusable message pipe
+// handle.
+MOJO_SYSTEM_EXPORT MojoResult
+ MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1);
+
+// Allocates a new message whose ownership may be passed to
+// |MojoWriteMessageNew()|. Use |MojoGetMessageBuffer()| to retrieve the address
+// of the mutable message payload.
+//
+// |num_bytes|: The size of the message payload in bytes.
+// |handles|: An array of handles to transfer in the message. This takes
+// ownership of and invalidates all contained handles. Must be null if and
+// only if |num_handles| is 0.
+// |num_handles|: The number of handles contained in |handles|.
+// |flags|: Must be |MOJO_CREATE_MESSAGE_FLAG_NONE|.
+// |message|: The address of a handle to be filled with the allocated message's
+// handle. Must be non-null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the message was successfully allocated. In this case
+// |*message| will be populated with a handle to an allocated message
+// with a buffer large enough to hold |num_bytes| contiguous bytes.
+// |MOJO_RESULT_INVALID_ARGUMENT| if one or more handles in |handles| was
+// invalid, or |handles| was null with a non-zero |num_handles|.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if allocation failed because either
+// |num_bytes| or |num_handles| exceeds an implementation-defined maximum.
+// |MOJO_RESULT_BUSY| if one or more handles in |handles| cannot be sent at
+// the time of this call.
+//
+// Only upon successful message allocation will all handles in |handles| be
+// transferred into the message and invalidated.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoAllocMessage(uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoAllocMessageFlags flags,
+ MojoMessageHandle* message); // Out
+
+// Frees a message allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|.
+//
+// |message|: The message to free. This must correspond to a message previously
+// allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|. Note that if
+// the message has already been passed to |MojoWriteMessageNew()| it should
+// NOT also be freed with this API.
+//
+// Returns:
+// |MOJO_RESULT_OK| if |message| was valid and has been freed.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| was not a valid message.
+MOJO_SYSTEM_EXPORT MojoResult MojoFreeMessage(MojoMessageHandle message);
+
+// Retrieves the address of mutable message bytes for a message allocated by
+// either |MojoAllocMessage()| or |MojoReadMessageNew()|.
+//
+// Returns:
+// |MOJO_RESULT_OK| if |message| is a valid message object. |*buffer| will
+// be updated to point to mutable message bytes.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message object.
+//
+// NOTE: A returned buffer address is always guaranteed to be 8-byte aligned.
+MOJO_SYSTEM_EXPORT MojoResult MojoGetMessageBuffer(MojoMessageHandle message,
+ void** buffer); // Out
+
+// Notifies the system that a bad message was received on a message pipe,
+// according to whatever criteria the caller chooses. This ultimately tries to
+// notify the embedder about the bad message, and the embedder may enforce some
+// policy for dealing with the source of the message (e.g. close the pipe,
+// terminate, a process, etc.) The embedder may not be notified if the calling
+// process has lost its connection to the source process.
+//
+// |message|: The message to report as bad. This must have come from a call to
+// |MojoReadMessageNew()|.
+// |error|: An error string which may provide the embedder with context when
+// notified of this error.
+// |error_num_bytes|: The length of |error| in bytes.
+//
+// Returns:
+// |MOJO_RESULT_OK| if successful.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoNotifyBadMessage(MojoMessageHandle message,
+ const char* error,
+ size_t error_num_bytes);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_
diff --git a/mojo/public/c/system/platform_handle.h b/mojo/public/c/system/platform_handle.h
new file mode 100644
index 0000000000..7449c2e794
--- /dev/null
+++ b/mojo/public/c/system/platform_handle.h
@@ -0,0 +1,191 @@
+// 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.
+
+// This file contains types/functions and constants for platform handle wrapping
+// and unwrapping APIs.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_
+#define MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// |MojoPlatformHandleType|: A value indicating the specific type of platform
+// handle encapsulated by a MojoPlatformHandle (see below.) This is stored
+// in the MojoPlatformHandle's |type| field and determines how the |value|
+// field is interpreted.
+//
+// |MOJO_PLATFORM_HANDLE_TYPE_INVALID| - An invalid platform handle.
+// |MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR| - A file descriptor. Only valid
+// on POSIX systems.
+// |MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT| - A Mach port. Only valid on OS X.
+// |MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE| - A Windows HANDLE value. Only
+// valid on Windows.
+
+typedef uint32_t MojoPlatformHandleType;
+
+#ifdef __cplusplus
+const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_INVALID = 0;
+const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR = 1;
+const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT = 2;
+const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE = 3;
+#else
+#define MOJO_PLATFORM_HANDLE_TYPE_INVALID ((MojoPlatformHandleType)0)
+#define MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR ((MojoPlatformHandleType)1)
+#define MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT ((MojoPlatformHandleType)2)
+#define MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE ((MojoPlatformHandleType)3)
+#endif
+
+// |MojoPlatformHandle|: A handle to a native platform object.
+//
+// |uint32_t struct_size|: The size of this structure. Used for versioning
+// to allow for future extensions.
+//
+// |MojoPlatformHandleType type|: The type of handle stored in |value|.
+//
+// |uint64_t value|: The value of this handle. Ignored if |type| is
+// MOJO_PLATFORM_HANDLE_TYPE_INVALID. Otherwise the meaning of this
+// value depends on the value of |type|.
+//
+
+struct MOJO_ALIGNAS(8) MojoPlatformHandle {
+ uint32_t struct_size;
+ MojoPlatformHandleType type;
+ uint64_t value;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoPlatformHandle) == 16,
+ "MojoPlatformHandle has wrong size");
+
+// |MojoPlatformSharedBufferHandleFlags|: Flags relevant to wrapped platform
+// shared buffers.
+//
+// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_NONE| - No flags.
+// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_READ_ONLY| - Indicates that the wrapped
+// buffer handle may only be mapped for reading.
+
+typedef uint32_t MojoPlatformSharedBufferHandleFlags;
+
+#ifdef __cplusplus
+const MojoPlatformSharedBufferHandleFlags
+MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE = 0;
+
+const MojoPlatformSharedBufferHandleFlags
+MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY = 1 << 0;
+#else
+#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE \
+ ((MojoPlatformSharedBufferHandleFlags)0)
+
+#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY \
+ ((MojoPlatformSharedBufferHandleFlags)1 << 0)
+#endif
+
+// Wraps a native platform handle as a Mojo handle which can be transferred
+// over a message pipe. Takes ownership of the underlying native platform
+// object.
+//
+// |platform_handle|: The platform handle to wrap.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case
+// |*mojo_handle| contains the Mojo handle of the wrapped object.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the system is out of handles.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |platform_handle| was not a valid
+// platform handle.
+//
+// NOTE: It is not always possible to detect if |platform_handle| is valid,
+// particularly when |platform_handle->type| is valid but
+// |platform_handle->value| does not represent a valid platform object.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle,
+ MojoHandle* mojo_handle); // Out
+
+// Unwraps a native platform handle from a Mojo handle. If this call succeeds,
+// ownership of the underlying platform object is assumed by the caller. The
+// The Mojo handle is always closed regardless of success or failure.
+//
+// |mojo_handle|: The Mojo handle from which to unwrap the native platform
+// handle.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case
+// |*platform_handle| contains the unwrapped platform handle.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |mojo_handle| was not a valid Mojo
+// handle wrapping a platform handle.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoUnwrapPlatformHandle(MojoHandle mojo_handle,
+ struct MojoPlatformHandle* platform_handle); // Out
+
+// Wraps a native platform shared buffer handle as a Mojo shared buffer handle
+// which can be used exactly like a shared buffer handle created by
+// |MojoCreateSharedBuffer()| or |MojoDuplicateBufferHandle()|.
+//
+// Takes ownership of the native platform shared buffer handle.
+//
+// |platform_handle|: The platform handle to wrap. Must be a native handle to a
+// shared buffer object.
+// |num_bytes|: The size of the shared buffer in bytes.
+// |flags|: Flags which influence the treatment of the shared buffer object. See
+// below.
+//
+// Flags:
+// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE| indicates default behavior.
+// No flags set.
+// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY| indicates that the
+// buffer handled to be wrapped may only be mapped as read-only. This
+// flag does NOT change the access control of the buffer in any way.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case
+// |*mojo_handle| contains a Mojo shared buffer handle.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |platform_handle| was not a valid
+// platform shared buffer handle.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoWrapPlatformSharedBufferHandle(
+ const struct MojoPlatformHandle* platform_handle,
+ size_t num_bytes,
+ MojoPlatformSharedBufferHandleFlags flags,
+ MojoHandle* mojo_handle); // Out
+
+// Unwraps a native platform shared buffer handle from a Mojo shared buffer
+// handle. If this call succeeds, ownership of the underlying shared buffer
+// object is assumed by the caller.
+//
+// The Mojo handle is always closed regardless of success or failure.
+//
+// |mojo_handle|: The Mojo shared buffer handle to unwrap.
+//
+// |platform_handle|, |num_bytes| and |flags| are used to receive output values
+// and MUST always be non-null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case
+// |*platform_handle| contains a platform shared buffer handle,
+// |*num_bytes| contains the size of the shared buffer object, and
+// |*flags| indicates flags relevant to the wrapped buffer (see below).
+// |MOJO_RESULT_INVALID_ARGUMENT| if |mojo_handle| is not a valid Mojo
+// shared buffer handle.
+//
+// Flags which may be set in |*flags| upon success:
+// |MOJO_PLATFORM_SHARED_BUFFER_FLAG_READ_ONLY| is set iff the unwrapped
+// shared buffer handle may only be mapped as read-only.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoUnwrapPlatformSharedBufferHandle(
+ MojoHandle mojo_handle,
+ struct MojoPlatformHandle* platform_handle,
+ size_t* num_bytes,
+ MojoPlatformSharedBufferHandleFlags* flags);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_
diff --git a/mojo/public/c/system/set_thunks_for_app.cc b/mojo/public/c/system/set_thunks_for_app.cc
new file mode 100644
index 0000000000..335cc02b79
--- /dev/null
+++ b/mojo/public/c/system/set_thunks_for_app.cc
@@ -0,0 +1,20 @@
+// 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 "mojo/public/c/system/thunks.h"
+
+extern "C" {
+
+#if defined(WIN32)
+#define THUNKS_EXPORT __declspec(dllexport)
+#else
+#define THUNKS_EXPORT __attribute__((visibility("default")))
+#endif
+
+THUNKS_EXPORT size_t MojoSetSystemThunks(
+ const MojoSystemThunks* system_thunks) {
+ return MojoEmbedderSetSystemThunks(system_thunks);
+}
+
+} // extern "C"
diff --git a/mojo/public/c/system/system_export.h b/mojo/public/c/system/system_export.h
new file mode 100644
index 0000000000..775f6679f9
--- /dev/null
+++ b/mojo/public/c/system/system_export.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 MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_
+#define MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(MOJO_SYSTEM_IMPLEMENTATION)
+#define MOJO_SYSTEM_EXPORT __declspec(dllexport)
+#else
+#define MOJO_SYSTEM_EXPORT __declspec(dllimport)
+#endif
+
+#else // !defined(WIN32)
+
+#if defined(MOJO_SYSTEM_IMPLEMENTATION)
+#define MOJO_SYSTEM_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_SYSTEM_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+#else // !defined(COMPONENT_BUILD)
+
+#define MOJO_SYSTEM_EXPORT
+
+#endif // defined(COMPONENT_BUILD)
+
+#endif // MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_
diff --git a/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn
new file mode 100644
index 0000000000..bace63c4aa
--- /dev/null
+++ b/mojo/public/c/system/tests/BUILD.gn
@@ -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.
+
+source_set("tests") {
+ testonly = true
+
+ visibility = [
+ "//mojo/public/cpp/system/tests:mojo_public_system_unittests",
+ "//mojo/public/cpp/system/tests:tests",
+ ]
+
+ sources = [
+ "core_unittest.cc",
+ "core_unittest_pure_c.c",
+ "macros_unittest.cc",
+ ]
+
+ deps = [
+ "//mojo/public/c/system",
+ "//mojo/public/cpp/system",
+ "//testing/gtest",
+ ]
+}
+
+source_set("perftests") {
+ testonly = true
+
+ sources = [
+ "core_perftest.cc",
+ ]
+
+ deps = [
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/c/system/tests/core_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc
new file mode 100644
index 0000000000..cab465b43d
--- /dev/null
+++ b/mojo/public/c/system/tests/core_perftest.cc
@@ -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.
+
+// This tests the performance of the C API.
+
+#include "mojo/public/c/system/core.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "base/macros.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(WIN32)
+#include <time.h>
+#endif // !defined(WIN32)
+
+namespace {
+
+#if !defined(WIN32)
+class MessagePipeWriterThread : public base::SimpleThread {
+ public:
+ MessagePipeWriterThread(MojoHandle handle, uint32_t num_bytes)
+ : SimpleThread("MessagePipeWriterThread"),
+ handle_(handle),
+ num_bytes_(num_bytes),
+ num_writes_(0) {}
+ ~MessagePipeWriterThread() override {}
+
+ void Run() override {
+ char buffer[10000];
+ assert(num_bytes_ <= sizeof(buffer));
+
+ // TODO(vtl): Should I throttle somehow?
+ for (;;) {
+ MojoResult result = MojoWriteMessage(handle_, buffer, num_bytes_, nullptr,
+ 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ num_writes_++;
+ continue;
+ }
+
+ // We failed to write.
+ // Either |handle_| or its peer was closed.
+ assert(result == MOJO_RESULT_INVALID_ARGUMENT ||
+ result == MOJO_RESULT_FAILED_PRECONDITION);
+ break;
+ }
+ }
+
+ // Use only after joining the thread.
+ int64_t num_writes() const { return num_writes_; }
+
+ private:
+ const MojoHandle handle_;
+ const uint32_t num_bytes_;
+ int64_t num_writes_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeWriterThread);
+};
+
+class MessagePipeReaderThread : public base::SimpleThread {
+ public:
+ explicit MessagePipeReaderThread(MojoHandle handle)
+ : SimpleThread("MessagePipeReaderThread"),
+ handle_(handle),
+ num_reads_(0) {}
+ ~MessagePipeReaderThread() override {}
+
+ void Run() override {
+ char buffer[10000];
+
+ for (;;) {
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadMessage(handle_, buffer, &num_bytes, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ num_reads_++;
+ continue;
+ }
+
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ result = mojo::Wait(mojo::Handle(handle_), MOJO_HANDLE_SIGNAL_READABLE);
+ if (result == MOJO_RESULT_OK) {
+ // Go to the top of the loop to read again.
+ continue;
+ }
+ }
+
+ // We failed to read and possibly failed to wait.
+ // Either |handle_| or its peer was closed.
+ assert(result == MOJO_RESULT_INVALID_ARGUMENT ||
+ result == MOJO_RESULT_FAILED_PRECONDITION);
+ break;
+ }
+ }
+
+ // Use only after joining the thread.
+ int64_t num_reads() const { return num_reads_; }
+
+ private:
+ const MojoHandle handle_;
+ int64_t num_reads_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeReaderThread);
+};
+#endif // !defined(WIN32)
+
+class CorePerftest : public testing::Test {
+ public:
+ CorePerftest() : buffer_(nullptr), num_bytes_(0) {}
+ ~CorePerftest() override {}
+
+ static void NoOp(void* /*closure*/) {}
+
+ static void MessagePipe_CreateAndClose(void* closure) {
+ CorePerftest* self = static_cast<CorePerftest*>(closure);
+ MojoResult result = MojoCreateMessagePipe(nullptr, &self->h0_, &self->h1_);
+ ALLOW_UNUSED_LOCAL(result);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(self->h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(self->h1_);
+ assert(result == MOJO_RESULT_OK);
+ }
+
+ static void MessagePipe_WriteAndRead(void* closure) {
+ CorePerftest* self = static_cast<CorePerftest*>(closure);
+ MojoResult result =
+ MojoWriteMessage(self->h0_, self->buffer_, self->num_bytes_, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ ALLOW_UNUSED_LOCAL(result);
+ assert(result == MOJO_RESULT_OK);
+ uint32_t read_bytes = self->num_bytes_;
+ result = MojoReadMessage(self->h1_, self->buffer_, &read_bytes, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+ assert(result == MOJO_RESULT_OK);
+ }
+
+ static void MessagePipe_EmptyRead(void* closure) {
+ CorePerftest* self = static_cast<CorePerftest*>(closure);
+ MojoResult result =
+ MojoReadMessage(self->h0_, nullptr, nullptr, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
+ ALLOW_UNUSED_LOCAL(result);
+ assert(result == MOJO_RESULT_SHOULD_WAIT);
+ }
+
+ protected:
+#if !defined(WIN32)
+ void DoMessagePipeThreadedTest(unsigned num_writers,
+ unsigned num_readers,
+ uint32_t num_bytes) {
+ static const int64_t kPerftestTimeMicroseconds = 3 * 1000000;
+
+ assert(num_writers > 0);
+ assert(num_readers > 0);
+
+ MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_);
+ ALLOW_UNUSED_LOCAL(result);
+ assert(result == MOJO_RESULT_OK);
+
+ std::vector<MessagePipeWriterThread*> writers;
+ for (unsigned i = 0; i < num_writers; i++)
+ writers.push_back(new MessagePipeWriterThread(h0_, num_bytes));
+
+ std::vector<MessagePipeReaderThread*> readers;
+ for (unsigned i = 0; i < num_readers; i++)
+ readers.push_back(new MessagePipeReaderThread(h1_));
+
+ // Start time here, just before we fire off the threads.
+ const MojoTimeTicks start_time = MojoGetTimeTicksNow();
+
+ // Interleave the starts.
+ for (unsigned i = 0; i < num_writers || i < num_readers; i++) {
+ if (i < num_writers)
+ writers[i]->Start();
+ if (i < num_readers)
+ readers[i]->Start();
+ }
+
+ Sleep(kPerftestTimeMicroseconds);
+
+ // Close both handles to make writers and readers stop immediately.
+ result = MojoClose(h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(h1_);
+ assert(result == MOJO_RESULT_OK);
+
+ // Join everything.
+ for (unsigned i = 0; i < num_writers; i++)
+ writers[i]->Join();
+ for (unsigned i = 0; i < num_readers; i++)
+ readers[i]->Join();
+
+ // Stop time here.
+ MojoTimeTicks end_time = MojoGetTimeTicksNow();
+
+ // Add up write and read counts, and destroy the threads.
+ int64_t num_writes = 0;
+ for (unsigned i = 0; i < num_writers; i++) {
+ num_writes += writers[i]->num_writes();
+ delete writers[i];
+ }
+ writers.clear();
+ int64_t num_reads = 0;
+ for (unsigned i = 0; i < num_readers; i++) {
+ num_reads += readers[i]->num_reads();
+ delete readers[i];
+ }
+ readers.clear();
+
+ char sub_test_name[200];
+ sprintf(sub_test_name, "%uw_%ur_%ubytes", num_writers, num_readers,
+ static_cast<unsigned>(num_bytes));
+ mojo::test::LogPerfResult(
+ "MessagePipe_Threaded_Writes", sub_test_name,
+ 1000000.0 * static_cast<double>(num_writes) / (end_time - start_time),
+ "writes/second");
+ mojo::test::LogPerfResult(
+ "MessagePipe_Threaded_Reads", sub_test_name,
+ 1000000.0 * static_cast<double>(num_reads) / (end_time - start_time),
+ "reads/second");
+ }
+#endif // !defined(WIN32)
+
+ MojoHandle h0_;
+ MojoHandle h1_;
+
+ void* buffer_;
+ uint32_t num_bytes_;
+
+ private:
+#if !defined(WIN32)
+ void Sleep(int64_t microseconds) {
+ struct timespec req = {
+ static_cast<time_t>(microseconds / 1000000), // Seconds.
+ static_cast<long>(microseconds % 1000000) * 1000L // Nanoseconds.
+ };
+ int rv = nanosleep(&req, nullptr);
+ ALLOW_UNUSED_LOCAL(rv);
+ assert(rv == 0);
+ }
+#endif // !defined(WIN32)
+
+ DISALLOW_COPY_AND_ASSIGN(CorePerftest);
+};
+
+// A no-op test so we can compare performance.
+TEST_F(CorePerftest, NoOp) {
+ mojo::test::IterateAndReportPerf("Iterate_NoOp", nullptr, &CorePerftest::NoOp,
+ this);
+}
+
+TEST_F(CorePerftest, MessagePipe_CreateAndClose) {
+ mojo::test::IterateAndReportPerf("MessagePipe_CreateAndClose", nullptr,
+ &CorePerftest::MessagePipe_CreateAndClose,
+ this);
+}
+
+TEST_F(CorePerftest, MessagePipe_WriteAndRead) {
+ MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_);
+ ALLOW_UNUSED_LOCAL(result);
+ assert(result == MOJO_RESULT_OK);
+ char buffer[10000] = {0};
+ buffer_ = buffer;
+ num_bytes_ = 10u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ num_bytes_ = 100u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "100bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ num_bytes_ = 1000u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "1000bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ num_bytes_ = 10000u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10000bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ result = MojoClose(h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(h1_);
+ assert(result == MOJO_RESULT_OK);
+}
+
+TEST_F(CorePerftest, MessagePipe_EmptyRead) {
+ MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_);
+ ALLOW_UNUSED_LOCAL(result);
+ assert(result == MOJO_RESULT_OK);
+ mojo::test::IterateAndReportPerf("MessagePipe_EmptyRead", nullptr,
+ &CorePerftest::MessagePipe_EmptyRead, this);
+ result = MojoClose(h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(h1_);
+ assert(result == MOJO_RESULT_OK);
+}
+
+#if !defined(WIN32)
+TEST_F(CorePerftest, MessagePipe_Threaded) {
+ DoMessagePipeThreadedTest(1u, 1u, 100u);
+ DoMessagePipeThreadedTest(2u, 2u, 100u);
+ DoMessagePipeThreadedTest(3u, 3u, 100u);
+ DoMessagePipeThreadedTest(10u, 10u, 100u);
+ DoMessagePipeThreadedTest(10u, 1u, 100u);
+ DoMessagePipeThreadedTest(1u, 10u, 100u);
+
+ // For comparison of overhead:
+ DoMessagePipeThreadedTest(1u, 1u, 10u);
+ // 100 was done above.
+ DoMessagePipeThreadedTest(1u, 1u, 1000u);
+ DoMessagePipeThreadedTest(1u, 1u, 10000u);
+
+ DoMessagePipeThreadedTest(3u, 3u, 10u);
+ // 100 was done above.
+ DoMessagePipeThreadedTest(3u, 3u, 1000u);
+ DoMessagePipeThreadedTest(3u, 3u, 10000u);
+}
+#endif // !defined(WIN32)
+
+} // namespace
diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc
new file mode 100644
index 0000000000..a9da255717
--- /dev/null
+++ b/mojo/public/c/system/tests/core_unittest.cc
@@ -0,0 +1,322 @@
+// Copyright 2013 The Chromium 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 C API.
+
+#include "mojo/public/c/system/core.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const MojoHandleSignals kSignalReadadableWritable =
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
+
+const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+
+TEST(CoreTest, GetTimeTicksNow) {
+ const MojoTimeTicks start = MojoGetTimeTicksNow();
+ EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
+ << "MojoGetTimeTicksNow should return nonzero value";
+}
+
+// The only handle that's guaranteed to be invalid is |MOJO_HANDLE_INVALID|.
+// Tests that everything that takes a handle properly recognizes it.
+TEST(CoreTest, InvalidHandle) {
+ MojoHandle h0, h1;
+ char buffer[10] = {0};
+ uint32_t buffer_size;
+ void* write_pointer;
+ const void* read_pointer;
+
+ // Close:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(MOJO_HANDLE_INVALID));
+
+ // Message pipe:
+ h0 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWriteMessage(h0, buffer, 3, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ // Data pipe:
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWriteData(h0, buffer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE));
+ write_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoBeginWriteData(h0, &write_pointer, &buffer_size,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndWriteData(h0, 1));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoReadData(h0, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+ read_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoBeginReadData(h0, &read_pointer, &buffer_size,
+ MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndReadData(h0, 1));
+
+ // Shared buffer:
+ h1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoDuplicateBufferHandle(h0, nullptr, &h1));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoMapBuffer(h0, 0, 1, &write_pointer, MOJO_MAP_BUFFER_FLAG_NONE));
+}
+
+TEST(CoreTest, BasicMessagePipe) {
+ MojoHandle h0, h1;
+ MojoHandleSignals sig;
+ char buffer[10] = {0};
+ uint32_t buffer_size;
+
+ h0 = MOJO_HANDLE_INVALID;
+ h1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &h0, &h1));
+ EXPECT_NE(h0, MOJO_HANDLE_INVALID);
+ EXPECT_NE(h1, MOJO_HANDLE_INVALID);
+
+ // Shouldn't be readable, we haven't written anything. Should be writable.
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Try to read.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ // Write to |h1|.
+ static const char kHello[] = "hello";
+ buffer_size = static_cast<uint32_t>(sizeof(kHello));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(h1, kHello, buffer_size, nullptr,
+ 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // |h0| should be readable.
+ size_t result_index = 1;
+ MojoHandleSignalsState states[1];
+ sig = MOJO_HANDLE_SIGNAL_READABLE;
+ Handle handle0(h0);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mojo::WaitMany(&handle0, &sig, 1, &result_index, states));
+
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(kSignalReadadableWritable, states[0].satisfied_signals);
+ EXPECT_EQ(kSignalAll, states[0].satisfiable_signals);
+
+ // Read from |h0|.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(static_cast<uint32_t>(sizeof(kHello)), buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+
+ // |h0| should no longer be readable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Close |h0|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0));
+
+ EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h1),
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
+
+ // |h1| should no longer be readable or writable.
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ mojo::Wait(mojo::Handle(h1),
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ &state));
+
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1));
+}
+
+TEST(CoreTest, BasicDataPipe) {
+ MojoHandle hp, hc;
+ MojoHandleSignals sig;
+ char buffer[20] = {0};
+ uint32_t buffer_size;
+ void* write_pointer;
+ const void* read_pointer;
+
+ hp = MOJO_HANDLE_INVALID;
+ hc = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &hp, &hc));
+ EXPECT_NE(hp, MOJO_HANDLE_INVALID);
+ EXPECT_NE(hc, MOJO_HANDLE_INVALID);
+
+ // The consumer |hc| shouldn't be readable.
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hc, &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
+ MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
+ state.satisfiable_signals);
+
+ // The producer |hp| should be writable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hp, &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ state.satisfiable_signals);
+
+ // Try to read from |hc|.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+
+ // Try to begin a two-phase read from |hc|.
+ read_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoBeginReadData(hc, &read_pointer, &buffer_size,
+ MOJO_READ_DATA_FLAG_NONE));
+
+ // Write to |hp|.
+ static const char kHello[] = "hello ";
+ // Don't include terminating null.
+ buffer_size = static_cast<uint32_t>(strlen(kHello));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(hp, kHello, &buffer_size,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // |hc| should be(come) readable.
+ size_t result_index = 1;
+ MojoHandleSignalsState states[1];
+ sig = MOJO_HANDLE_SIGNAL_READABLE;
+ Handle consumer_handle(hc);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mojo::WaitMany(&consumer_handle, &sig, 1, &result_index, states));
+
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
+ states[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
+ MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
+ states[0].satisfiable_signals);
+
+ // Do a two-phase write to |hp|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoBeginWriteData(hp, &write_pointer, &buffer_size,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ static const char kWorld[] = "world";
+ ASSERT_GE(buffer_size, sizeof(kWorld));
+ // Include the terminating null.
+ memcpy(write_pointer, kWorld, sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoEndWriteData(hp, static_cast<uint32_t>(sizeof(kWorld))));
+
+ // Read one character from |hc|.
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+
+ // Close |hp|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hp));
+
+ // |hc| should still be readable.
+ EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(hc),
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
+
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ state.satisfiable_signals);
+
+ // Do a two-phase read from |hc|.
+ read_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoBeginReadData(hc, &read_pointer, &buffer_size,
+ MOJO_READ_DATA_FLAG_NONE));
+ ASSERT_LE(buffer_size, sizeof(buffer) - 1);
+ memcpy(&buffer[1], read_pointer, buffer_size);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoEndReadData(hc, buffer_size));
+ EXPECT_STREQ("hello world", buffer);
+
+ // |hc| should no longer be readable.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ mojo::Wait(mojo::Handle(hc), MOJO_HANDLE_SIGNAL_READABLE, &state));
+
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hc));
+
+ // TODO(vtl): Test the other way around -- closing the consumer should make
+ // the producer never-writable?
+}
+
+TEST(CoreTest, BasicSharedBuffer) {
+ MojoHandle h0, h1;
+ void* pointer;
+
+ // Create a shared buffer (|h0|).
+ h0 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(nullptr, 100, &h0));
+ EXPECT_NE(h0, MOJO_HANDLE_INVALID);
+
+ // Map everything.
+ pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoMapBuffer(h0, 0, 100, &pointer, MOJO_MAP_BUFFER_FLAG_NONE));
+ ASSERT_TRUE(pointer);
+ static_cast<char*>(pointer)[50] = 'x';
+
+ // Duplicate |h0| to |h1|.
+ h1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(h0, nullptr, &h1));
+ EXPECT_NE(h1, MOJO_HANDLE_INVALID);
+
+ // Close |h0|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0));
+
+ // The mapping should still be good.
+ static_cast<char*>(pointer)[51] = 'y';
+
+ // Unmap it.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer));
+
+ // Map half of |h1|.
+ pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoMapBuffer(h1, 50, 50, &pointer, MOJO_MAP_BUFFER_FLAG_NONE));
+ ASSERT_TRUE(pointer);
+
+ // It should have what we wrote.
+ EXPECT_EQ('x', static_cast<char*>(pointer)[0]);
+ EXPECT_EQ('y', static_cast<char*>(pointer)[1]);
+
+ // Unmap it.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1));
+}
+
+// Defined in core_unittest_pure_c.c.
+extern "C" const char* MinimalCTest(void);
+
+// This checks that things actually work in C (not C++).
+TEST(CoreTest, MinimalCTest) {
+ const char* failure = MinimalCTest();
+ EXPECT_FALSE(failure) << failure;
+}
+
+// TODO(vtl): Add multi-threaded tests.
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/c/system/tests/core_unittest_pure_c.c b/mojo/public/c/system/tests/core_unittest_pure_c.c
new file mode 100644
index 0000000000..3164649cbd
--- /dev/null
+++ b/mojo/public/c/system/tests/core_unittest_pure_c.c
@@ -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.
+
+#ifdef __cplusplus
+#error "This file should be compiled as C, not C++."
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+// Include all the header files that are meant to be compilable as C. Start with
+// core.h, since it's the most important one.
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/c/system/macros.h"
+
+// The joys of the C preprocessor....
+#define STRINGIFY(x) #x
+#define STRINGIFY2(x) STRINGIFY(x)
+#define FAILURE(message) \
+ __FILE__ "(" STRINGIFY2(__LINE__) "): Failure: " message
+
+// Makeshift gtest.
+#define EXPECT_EQ(a, b) \
+ do { \
+ if ((a) != (b)) \
+ return FAILURE(STRINGIFY(a) " != " STRINGIFY(b) " (expected ==)"); \
+ } while (0)
+#define EXPECT_NE(a, b) \
+ do { \
+ if ((a) == (b)) \
+ return FAILURE(STRINGIFY(a) " == " STRINGIFY(b) " (expected !=)"); \
+ } while (0)
+
+// This function exists mainly to be compiled and linked. We do some cursory
+// checks and call it from a unit test, to make sure that link problems aren't
+// missed due to deadstripping. Returns null on success and a string on failure
+// (describing the failure).
+const char* MinimalCTest(void) {
+ // MSVS before 2013 *really* only supports C90: All variables must be declared
+ // at the top. (MSVS 2013 is more reasonable.)
+ MojoTimeTicks ticks;
+ MojoHandle handle0, handle1;
+ const char kHello[] = "hello";
+ char buffer[200] = {0};
+ uint32_t num_bytes;
+
+ ticks = MojoGetTimeTicksNow();
+ EXPECT_NE(ticks, 0);
+
+ handle0 = MOJO_HANDLE_INVALID;
+ EXPECT_NE(MOJO_RESULT_OK, MojoClose(handle0));
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoQueryHandleSignalsState(handle0, NULL));
+
+ handle1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(handle0, kHello, (uint32_t)sizeof(kHello), NULL,
+ 0u, MOJO_WRITE_DATA_FLAG_NONE));
+
+ num_bytes = (uint32_t)sizeof(buffer);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(handle1, buffer, &num_bytes, NULL,
+ NULL, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ((uint32_t)sizeof(kHello), num_bytes);
+ EXPECT_EQ(0, memcmp(buffer, kHello, sizeof(kHello)));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle0));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle1));
+
+ // TODO(vtl): data pipe
+
+ return NULL;
+}
diff --git a/mojo/public/c/system/tests/macros_unittest.cc b/mojo/public/c/system/tests/macros_unittest.cc
new file mode 100644
index 0000000000..fb9ff76405
--- /dev/null
+++ b/mojo/public/c/system/tests/macros_unittest.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.
+
+// This file tests the C Mojo system macros and consists of "positive" tests,
+// i.e., those verifying that things work (without compile errors, or even
+// warnings if warnings are treated as errors).
+// TODO(vtl): Fix no-compile tests (which are all disabled; crbug.com/105388)
+// and write some "negative" tests.
+
+#include "mojo/public/c/system/macros.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+// First test |MOJO_STATIC_ASSERT()| in a global scope.
+MOJO_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t),
+ "Bad static_assert() failure in global scope");
+
+TEST(MacrosTest, CompileAssert) {
+ // Then in a local scope.
+ MOJO_STATIC_ASSERT(sizeof(int32_t) == 2 * sizeof(int16_t),
+ "Bad static_assert() failure");
+}
+
+TEST(MacrosTest, Alignof) {
+ // Strictly speaking, this isn't a portable test, but I think it'll pass on
+ // all the platforms we currently support.
+ EXPECT_EQ(1u, MOJO_ALIGNOF(char));
+ EXPECT_EQ(4u, MOJO_ALIGNOF(int32_t));
+ EXPECT_EQ(8u, MOJO_ALIGNOF(int64_t));
+ EXPECT_EQ(8u, MOJO_ALIGNOF(double));
+}
+
+// These structs are used in the Alignas test. Define them globally to avoid
+// MSVS warnings/errors.
+#if defined(_MSC_VER)
+#pragma warning(push)
+// Disable the warning "structure was padded due to __declspec(align())".
+#pragma warning(disable : 4324)
+#endif
+struct MOJO_ALIGNAS(1) StructAlignas1 {
+ char x;
+};
+struct MOJO_ALIGNAS(4) StructAlignas4 {
+ char x;
+};
+struct MOJO_ALIGNAS(8) StructAlignas8 {
+ char x;
+};
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+TEST(MacrosTest, Alignas) {
+ EXPECT_EQ(1u, MOJO_ALIGNOF(StructAlignas1));
+ EXPECT_EQ(4u, MOJO_ALIGNOF(StructAlignas4));
+ EXPECT_EQ(8u, MOJO_ALIGNOF(StructAlignas8));
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/c/system/thunks.cc b/mojo/public/c/system/thunks.cc
new file mode 100644
index 0000000000..67c568f7d7
--- /dev/null
+++ b/mojo/public/c/system/thunks.cc
@@ -0,0 +1,273 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/c/system/thunks.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+extern "C" {
+
+static MojoSystemThunks g_thunks = {0};
+
+MojoTimeTicks MojoGetTimeTicksNow() {
+ assert(g_thunks.GetTimeTicksNow);
+ return g_thunks.GetTimeTicksNow();
+}
+
+MojoResult MojoClose(MojoHandle handle) {
+ assert(g_thunks.Close);
+ return g_thunks.Close(handle);
+}
+
+MojoResult MojoQueryHandleSignalsState(
+ MojoHandle handle,
+ struct MojoHandleSignalsState* signals_state) {
+ assert(g_thunks.QueryHandleSignalsState);
+ return g_thunks.QueryHandleSignalsState(handle, signals_state);
+}
+
+MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1) {
+ assert(g_thunks.CreateMessagePipe);
+ return g_thunks.CreateMessagePipe(options, message_pipe_handle0,
+ message_pipe_handle1);
+}
+
+MojoResult MojoWriteMessage(MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ assert(g_thunks.WriteMessage);
+ return g_thunks.WriteMessage(message_pipe_handle, bytes, num_bytes, handles,
+ num_handles, flags);
+}
+
+MojoResult MojoReadMessage(MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ assert(g_thunks.ReadMessage);
+ return g_thunks.ReadMessage(message_pipe_handle, bytes, num_bytes, handles,
+ num_handles, flags);
+}
+
+MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle) {
+ assert(g_thunks.CreateDataPipe);
+ return g_thunks.CreateDataPipe(options, data_pipe_producer_handle,
+ data_pipe_consumer_handle);
+}
+
+MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_elements,
+ MojoWriteDataFlags flags) {
+ assert(g_thunks.WriteData);
+ return g_thunks.WriteData(data_pipe_producer_handle, elements, num_elements,
+ flags);
+}
+
+MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoWriteDataFlags flags) {
+ assert(g_thunks.BeginWriteData);
+ return g_thunks.BeginWriteData(data_pipe_producer_handle, buffer,
+ buffer_num_elements, flags);
+}
+
+MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_elements_written) {
+ assert(g_thunks.EndWriteData);
+ return g_thunks.EndWriteData(data_pipe_producer_handle, num_elements_written);
+}
+
+MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_elements,
+ MojoReadDataFlags flags) {
+ assert(g_thunks.ReadData);
+ return g_thunks.ReadData(data_pipe_consumer_handle, elements, num_elements,
+ flags);
+}
+
+MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoReadDataFlags flags) {
+ assert(g_thunks.BeginReadData);
+ return g_thunks.BeginReadData(data_pipe_consumer_handle, buffer,
+ buffer_num_elements, flags);
+}
+
+MojoResult MojoEndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_elements_read) {
+ assert(g_thunks.EndReadData);
+ return g_thunks.EndReadData(data_pipe_consumer_handle, num_elements_read);
+}
+
+MojoResult MojoCreateSharedBuffer(
+ const struct MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle) {
+ assert(g_thunks.CreateSharedBuffer);
+ return g_thunks.CreateSharedBuffer(options, num_bytes, shared_buffer_handle);
+}
+
+MojoResult MojoDuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const struct MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle) {
+ assert(g_thunks.DuplicateBufferHandle);
+ return g_thunks.DuplicateBufferHandle(buffer_handle, options,
+ new_buffer_handle);
+}
+
+MojoResult MojoMapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags) {
+ assert(g_thunks.MapBuffer);
+ return g_thunks.MapBuffer(buffer_handle, offset, num_bytes, buffer, flags);
+}
+
+MojoResult MojoUnmapBuffer(void* buffer) {
+ assert(g_thunks.UnmapBuffer);
+ return g_thunks.UnmapBuffer(buffer);
+}
+
+MojoResult MojoCreateWatcher(MojoWatcherCallback callback,
+ MojoHandle* watcher_handle) {
+ assert(g_thunks.CreateWatcher);
+ return g_thunks.CreateWatcher(callback, watcher_handle);
+}
+
+MojoResult MojoWatch(MojoHandle watcher_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ uintptr_t context) {
+ assert(g_thunks.Watch);
+ return g_thunks.Watch(watcher_handle, handle, signals, context);
+}
+
+MojoResult MojoCancelWatch(MojoHandle watcher_handle, uintptr_t context) {
+ assert(g_thunks.CancelWatch);
+ return g_thunks.CancelWatch(watcher_handle, context);
+}
+
+MojoResult MojoArmWatcher(MojoHandle watcher_handle,
+ uint32_t* num_ready_contexts,
+ uintptr_t* ready_contexts,
+ MojoResult* ready_results,
+ MojoHandleSignalsState* ready_signals_states) {
+ assert(g_thunks.ArmWatcher);
+ return g_thunks.ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts,
+ ready_results, ready_signals_states);
+}
+
+MojoResult MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1) {
+ assert(g_thunks.FuseMessagePipes);
+ return g_thunks.FuseMessagePipes(handle0, handle1);
+}
+
+MojoResult MojoWriteMessageNew(MojoHandle message_pipe_handle,
+ MojoMessageHandle message,
+ MojoWriteMessageFlags flags) {
+ assert(g_thunks.WriteMessageNew);
+ return g_thunks.WriteMessageNew(message_pipe_handle, message, flags);
+}
+
+MojoResult MojoReadMessageNew(MojoHandle message_pipe_handle,
+ MojoMessageHandle* message,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ assert(g_thunks.ReadMessageNew);
+ return g_thunks.ReadMessageNew(message_pipe_handle, message, num_bytes,
+ handles, num_handles, flags);
+}
+
+MojoResult MojoAllocMessage(uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoAllocMessageFlags flags,
+ MojoMessageHandle* message) {
+ assert(g_thunks.AllocMessage);
+ return g_thunks.AllocMessage(
+ num_bytes, handles, num_handles, flags, message);
+}
+
+MojoResult MojoFreeMessage(MojoMessageHandle message) {
+ assert(g_thunks.FreeMessage);
+ return g_thunks.FreeMessage(message);
+}
+
+MojoResult MojoGetMessageBuffer(MojoMessageHandle message, void** buffer) {
+ assert(g_thunks.GetMessageBuffer);
+ return g_thunks.GetMessageBuffer(message, buffer);
+}
+
+MojoResult MojoWrapPlatformHandle(
+ const struct MojoPlatformHandle* platform_handle,
+ MojoHandle* mojo_handle) {
+ assert(g_thunks.WrapPlatformHandle);
+ return g_thunks.WrapPlatformHandle(platform_handle, mojo_handle);
+}
+
+MojoResult MojoUnwrapPlatformHandle(
+ MojoHandle mojo_handle,
+ struct MojoPlatformHandle* platform_handle) {
+ assert(g_thunks.UnwrapPlatformHandle);
+ return g_thunks.UnwrapPlatformHandle(mojo_handle, platform_handle);
+}
+
+MojoResult MojoWrapPlatformSharedBufferHandle(
+ const struct MojoPlatformHandle* platform_handle,
+ size_t num_bytes,
+ MojoPlatformSharedBufferHandleFlags flags,
+ MojoHandle* mojo_handle) {
+ assert(g_thunks.WrapPlatformSharedBufferHandle);
+ return g_thunks.WrapPlatformSharedBufferHandle(platform_handle, num_bytes,
+ flags, mojo_handle);
+}
+
+MojoResult MojoUnwrapPlatformSharedBufferHandle(
+ MojoHandle mojo_handle,
+ struct MojoPlatformHandle* platform_handle,
+ size_t* num_bytes,
+ MojoPlatformSharedBufferHandleFlags* flags) {
+ assert(g_thunks.UnwrapPlatformSharedBufferHandle);
+ return g_thunks.UnwrapPlatformSharedBufferHandle(mojo_handle, platform_handle,
+ num_bytes, flags);
+}
+
+MojoResult MojoNotifyBadMessage(MojoMessageHandle message,
+ const char* error,
+ size_t error_num_bytes) {
+ assert(g_thunks.NotifyBadMessage);
+ return g_thunks.NotifyBadMessage(message, error, error_num_bytes);
+}
+
+MojoResult MojoGetProperty(MojoPropertyType type, void* value) {
+ assert(g_thunks.GetProperty);
+ return g_thunks.GetProperty(type, value);
+}
+
+} // extern "C"
+
+size_t MojoEmbedderSetSystemThunks(const MojoSystemThunks* system_thunks) {
+ if (system_thunks->size >= sizeof(g_thunks))
+ g_thunks = *system_thunks;
+ return sizeof(g_thunks);
+}
diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h
new file mode 100644
index 0000000000..e61bb46a46
--- /dev/null
+++ b/mojo/public/c/system/thunks.h
@@ -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.
+
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_THUNKS_H_
+#define MOJO_PUBLIC_C_SYSTEM_THUNKS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/c/system/system_export.h"
+
+// Structure used to bind the basic Mojo Core functions to an embedder
+// implementation. This is intended to eventually be used as a stable ABI
+// between a Mojo embedder and some loaded application code, but for now it is
+// still effectively safe to rearrange entries as needed.
+#pragma pack(push, 8)
+struct MojoSystemThunks {
+ size_t size; // Should be set to sizeof(MojoSystemThunks).
+ MojoTimeTicks (*GetTimeTicksNow)();
+ MojoResult (*Close)(MojoHandle handle);
+ MojoResult (*QueryHandleSignalsState)(
+ MojoHandle handle,
+ struct MojoHandleSignalsState* signals_state);
+ MojoResult (*CreateMessagePipe)(
+ const struct MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1);
+ MojoResult (*WriteMessage)(MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags);
+ MojoResult (*ReadMessage)(MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags);
+ MojoResult (*CreateDataPipe)(const struct MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle);
+ MojoResult (*WriteData)(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_elements,
+ MojoWriteDataFlags flags);
+ MojoResult (*BeginWriteData)(MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoWriteDataFlags flags);
+ MojoResult (*EndWriteData)(MojoHandle data_pipe_producer_handle,
+ uint32_t num_elements_written);
+ MojoResult (*ReadData)(MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_elements,
+ MojoReadDataFlags flags);
+ MojoResult (*BeginReadData)(MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoReadDataFlags flags);
+ MojoResult (*EndReadData)(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_elements_read);
+ MojoResult (*CreateSharedBuffer)(
+ const struct MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle);
+ MojoResult (*DuplicateBufferHandle)(
+ MojoHandle buffer_handle,
+ const struct MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle);
+ MojoResult (*MapBuffer)(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags);
+ MojoResult (*UnmapBuffer)(void* buffer);
+ MojoResult (*CreateWatcher)(MojoWatcherCallback callback,
+ MojoHandle* watcher_handle);
+ MojoResult (*Watch)(MojoHandle watcher_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ uintptr_t context);
+ MojoResult (*CancelWatch)(MojoHandle watcher_handle, uintptr_t context);
+ MojoResult (*ArmWatcher)(MojoHandle watcher_handle,
+ uint32_t* num_ready_contexts,
+ uintptr_t* ready_contexts,
+ MojoResult* ready_results,
+ MojoHandleSignalsState* ready_signals_states);
+ MojoResult (*FuseMessagePipes)(MojoHandle handle0, MojoHandle handle1);
+ MojoResult (*WriteMessageNew)(MojoHandle message_pipe_handle,
+ MojoMessageHandle message,
+ MojoWriteMessageFlags flags);
+ MojoResult (*ReadMessageNew)(MojoHandle message_pipe_handle,
+ MojoMessageHandle* message,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags);
+ MojoResult (*AllocMessage)(uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoAllocMessageFlags flags,
+ MojoMessageHandle* message);
+ MojoResult (*FreeMessage)(MojoMessageHandle message);
+ MojoResult (*GetMessageBuffer)(MojoMessageHandle message, void** buffer);
+ MojoResult (*WrapPlatformHandle)(
+ const struct MojoPlatformHandle* platform_handle,
+ MojoHandle* mojo_handle);
+ MojoResult (*UnwrapPlatformHandle)(
+ MojoHandle mojo_handle,
+ struct MojoPlatformHandle* platform_handle);
+ MojoResult (*WrapPlatformSharedBufferHandle)(
+ const struct MojoPlatformHandle* platform_handle,
+ size_t num_bytes,
+ MojoPlatformSharedBufferHandleFlags flags,
+ MojoHandle* mojo_handle);
+ MojoResult (*UnwrapPlatformSharedBufferHandle)(
+ MojoHandle mojo_handle,
+ struct MojoPlatformHandle* platform_handle,
+ size_t* num_bytes,
+ MojoPlatformSharedBufferHandleFlags* flags);
+ MojoResult (*NotifyBadMessage)(MojoMessageHandle message,
+ const char* error,
+ size_t error_num_bytes);
+ MojoResult (*GetProperty)(MojoPropertyType type, void* value);
+};
+#pragma pack(pop)
+
+// Use this type for the function found by dynamically discovering it in
+// a DSO linked with mojo_system. For example:
+// MojoSetSystemThunksFn mojo_set_system_thunks_fn =
+// reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer(
+// "MojoSetSystemThunks"));
+// The expected size of |system_thunks| is returned.
+// The contents of |system_thunks| are copied.
+typedef size_t (*MojoSetSystemThunksFn)(
+ const struct MojoSystemThunks* system_thunks);
+
+// A function for setting up the embedder's own system thunks. This should only
+// be called by Mojo embedder code.
+MOJO_SYSTEM_EXPORT size_t MojoEmbedderSetSystemThunks(
+ const struct MojoSystemThunks* system_thunks);
+
+#endif // MOJO_PUBLIC_C_SYSTEM_THUNKS_H_
diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h
new file mode 100644
index 0000000000..15813b6b73
--- /dev/null
+++ b/mojo/public/c/system/types.h
@@ -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.
+
+// This file contains types and constants/macros common to different Mojo system
+// APIs.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_TYPES_H_
+#define MOJO_PUBLIC_C_SYSTEM_TYPES_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+
+// |MojoTimeTicks|: A time delta, in microseconds, the meaning of which is
+// source-dependent.
+
+typedef int64_t MojoTimeTicks;
+
+// |MojoHandle|: Handles to Mojo objects.
+// |MOJO_HANDLE_INVALID| - A value that is never a valid handle.
+
+typedef uint32_t MojoHandle;
+
+#ifdef __cplusplus
+const MojoHandle MOJO_HANDLE_INVALID = 0;
+#else
+#define MOJO_HANDLE_INVALID ((MojoHandle)0)
+#endif
+
+// |MojoResult|: Result codes for Mojo operations. The only success code is zero
+// (|MOJO_RESULT_OK|); all non-zero values should be considered as error/failure
+// codes (even if the value is not recognized).
+// |MOJO_RESULT_OK| - Not an error; returned on success.
+// |MOJO_RESULT_CANCELLED| - Operation was cancelled, typically by the caller.
+// |MOJO_RESULT_UNKNOWN| - Unknown error (e.g., if not enough information is
+// available for a more specific error).
+// |MOJO_RESULT_INVALID_ARGUMENT| - Caller specified an invalid argument. This
+// differs from |MOJO_RESULT_FAILED_PRECONDITION| in that the former
+// indicates arguments that are invalid regardless of the state of the
+// system.
+// |MOJO_RESULT_DEADLINE_EXCEEDED| - Deadline expired before the operation
+// could complete.
+// |MOJO_RESULT_NOT_FOUND| - Some requested entity was not found (i.e., does
+// not exist).
+// |MOJO_RESULT_ALREADY_EXISTS| - Some entity or condition that we attempted
+// to create already exists.
+// |MOJO_RESULT_PERMISSION_DENIED| - The caller does not have permission to
+// for the operation (use |MOJO_RESULT_RESOURCE_EXHAUSTED| for rejections
+// caused by exhausting some resource instead).
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| - Some resource required for the call
+// (possibly some quota) has been exhausted.
+// |MOJO_RESULT_FAILED_PRECONDITION| - The system is not in a state required
+// for the operation (use this if the caller must do something to rectify
+// the state before retrying).
+// |MOJO_RESULT_ABORTED| - The operation was aborted by the system, possibly
+// due to a concurrency issue (use this if the caller may retry at a
+// higher level).
+// |MOJO_RESULT_OUT_OF_RANGE| - The operation was attempted past the valid
+// range. Unlike |MOJO_RESULT_INVALID_ARGUMENT|, this indicates that the
+// operation may be/become valid depending on the system state. (This
+// error is similar to |MOJO_RESULT_FAILED_PRECONDITION|, but is more
+// specific.)
+// |MOJO_RESULT_UNIMPLEMENTED| - The operation is not implemented, supported,
+// or enabled.
+// |MOJO_RESULT_INTERNAL| - Internal error: this should never happen and
+// indicates that some invariant expected by the system has been broken.
+// |MOJO_RESULT_UNAVAILABLE| - The operation is (temporarily) currently
+// unavailable. The caller may simply retry the operation (possibly with a
+// backoff).
+// |MOJO_RESULT_DATA_LOSS| - Unrecoverable data loss or corruption.
+// |MOJO_RESULT_BUSY| - One of the resources involved is currently being used
+// (possibly on another thread) in a way that prevents the current
+// operation from proceeding, e.g., if the other operation may result in
+// the resource being invalidated.
+// |MOJO_RESULT_SHOULD_WAIT| - The request cannot currently be completed
+// (e.g., if the data requested is not yet available). The caller should
+// wait for it to be feasible using a watcher.
+//
+// The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from
+// Google3's canonical error codes.
+
+typedef uint32_t MojoResult;
+
+#ifdef __cplusplus
+const MojoResult MOJO_RESULT_OK = 0;
+const MojoResult MOJO_RESULT_CANCELLED = 1;
+const MojoResult MOJO_RESULT_UNKNOWN = 2;
+const MojoResult MOJO_RESULT_INVALID_ARGUMENT = 3;
+const MojoResult MOJO_RESULT_DEADLINE_EXCEEDED = 4;
+const MojoResult MOJO_RESULT_NOT_FOUND = 5;
+const MojoResult MOJO_RESULT_ALREADY_EXISTS = 6;
+const MojoResult MOJO_RESULT_PERMISSION_DENIED = 7;
+const MojoResult MOJO_RESULT_RESOURCE_EXHAUSTED = 8;
+const MojoResult MOJO_RESULT_FAILED_PRECONDITION = 9;
+const MojoResult MOJO_RESULT_ABORTED = 10;
+const MojoResult MOJO_RESULT_OUT_OF_RANGE = 11;
+const MojoResult MOJO_RESULT_UNIMPLEMENTED = 12;
+const MojoResult MOJO_RESULT_INTERNAL = 13;
+const MojoResult MOJO_RESULT_UNAVAILABLE = 14;
+const MojoResult MOJO_RESULT_DATA_LOSS = 15;
+const MojoResult MOJO_RESULT_BUSY = 16;
+const MojoResult MOJO_RESULT_SHOULD_WAIT = 17;
+#else
+#define MOJO_RESULT_OK ((MojoResult)0)
+#define MOJO_RESULT_CANCELLED ((MojoResult)1)
+#define MOJO_RESULT_UNKNOWN ((MojoResult)2)
+#define MOJO_RESULT_INVALID_ARGUMENT ((MojoResult)3)
+#define MOJO_RESULT_DEADLINE_EXCEEDED ((MojoResult)4)
+#define MOJO_RESULT_NOT_FOUND ((MojoResult)5)
+#define MOJO_RESULT_ALREADY_EXISTS ((MojoResult)6)
+#define MOJO_RESULT_PERMISSION_DENIED ((MojoResult)7)
+#define MOJO_RESULT_RESOURCE_EXHAUSTED ((MojoResult)8)
+#define MOJO_RESULT_FAILED_PRECONDITION ((MojoResult)9)
+#define MOJO_RESULT_ABORTED ((MojoResult)10)
+#define MOJO_RESULT_OUT_OF_RANGE ((MojoResult)11)
+#define MOJO_RESULT_UNIMPLEMENTED ((MojoResult)12)
+#define MOJO_RESULT_INTERNAL ((MojoResult)13)
+#define MOJO_RESULT_UNAVAILABLE ((MojoResult)14)
+#define MOJO_RESULT_DATA_LOSS ((MojoResult)15)
+#define MOJO_RESULT_BUSY ((MojoResult)16)
+#define MOJO_RESULT_SHOULD_WAIT ((MojoResult)17)
+#endif
+
+// |MojoDeadline|: Used to specify deadlines (timeouts), in microseconds (except
+// for |MOJO_DEADLINE_INDEFINITE|).
+// |MOJO_DEADLINE_INDEFINITE| - Used to indicate "forever".
+
+typedef uint64_t MojoDeadline;
+
+#ifdef __cplusplus
+const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast<MojoDeadline>(-1);
+#else
+#define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) - 1)
+#endif
+
+// |MojoHandleSignals|: Used to specify signals that can be watched for on a
+// handle (and which can be triggered), e.g., the ability to read or write to
+// the handle.
+// |MOJO_HANDLE_SIGNAL_NONE| - No flags. A registered watch will always fail
+// to arm with |MOJO_RESULT_FAILED_PRECONDITION| when watching for this.
+// |MOJO_HANDLE_SIGNAL_READABLE| - Can read (e.g., a message) from the handle.
+// |MOJO_HANDLE_SIGNAL_WRITABLE| - Can write (e.g., a message) to the handle.
+// |MOJO_HANDLE_SIGNAL_PEER_CLOSED| - The peer handle is closed.
+// |MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE| - Can read data from a data pipe
+// consumer handle (implying MOJO_HANDLE_SIGNAL_READABLE is also set),
+// AND there is some nonzero quantity of new data available on the pipe
+// since the last |MojoReadData()| or |MojoBeginReadData()| call on the
+// handle.
+
+typedef uint32_t MojoHandleSignals;
+
+#ifdef __cplusplus
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_NONE = 0;
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_READABLE = 1 << 0;
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_WRITABLE = 1 << 1;
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_PEER_CLOSED = 1 << 2;
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE = 1 << 3;
+#else
+#define MOJO_HANDLE_SIGNAL_NONE ((MojoHandleSignals)0)
+#define MOJO_HANDLE_SIGNAL_READABLE ((MojoHandleSignals)1 << 0)
+#define MOJO_HANDLE_SIGNAL_WRITABLE ((MojoHandleSignals)1 << 1)
+#define MOJO_HANDLE_SIGNAL_PEER_CLOSED ((MojoHandleSignals)1 << 2)
+#define MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE ((MojoHandleSignals)1 << 3);
+#endif
+
+// |MojoHandleSignalsState|: Returned by watch notification callbacks and
+// |MojoQueryHandleSignalsState| functions to indicate the signaling state of
+// handles. Members are as follows:
+// - |satisfied signals|: Bitmask of signals that were satisfied at some time
+// before the call returned.
+// - |satisfiable signals|: These are the signals that are possible to
+// satisfy. For example, if the return value was
+// |MOJO_RESULT_FAILED_PRECONDITION|, you can use this field to
+// determine which, if any, of the signals can still be satisfied.
+// Note: This struct is not extensible (and only has 32-bit quantities), so it's
+// 32-bit-aligned.
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int32_t) == 4, "int32_t has weird alignment");
+struct MOJO_ALIGNAS(4) MojoHandleSignalsState {
+ MojoHandleSignals satisfied_signals;
+ MojoHandleSignals satisfiable_signals;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoHandleSignalsState) == 8,
+ "MojoHandleSignalsState has wrong size");
+
+// |MojoWatcherNotificationFlags|: Passed to a callback invoked by a watcher
+// when some observed signals are raised or a watched handle is closed. May take
+// on any combination of the following values:
+//
+// |MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being
+// invoked as a result of a system-level event rather than a direct API
+// call from user code. This may be used as an indication that user code
+// is safe to call without fear of reentry.
+
+typedef uint32_t MojoWatcherNotificationFlags;
+
+#ifdef __cplusplus
+const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_NONE = 0;
+const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM =
+ 1 << 0;
+#else
+#define MOJO_WATCHER_NOTIFICATION_FLAG_NONE ((MojoWatcherNotificationFlags)0)
+#define MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM \
+ ((MojoWatcherNotificationFlags)1 << 0);
+#endif
+
+// |MojoPropertyType|: Property types that can be passed to |MojoGetProperty()|
+// to retrieve system properties. May take the following values:
+// |MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED| - Whether making synchronous calls
+// (i.e., blocking to wait for a response to an outbound message) is
+// allowed. The property value is of boolean type. If the value is true,
+// users should refrain from making sync calls.
+typedef uint32_t MojoPropertyType;
+
+#ifdef __cplusplus
+const MojoPropertyType MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED = 0;
+#else
+#define MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED ((MojoPropertyType)0)
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_TYPES_H_
diff --git a/mojo/public/c/system/watcher.h b/mojo/public/c/system/watcher.h
new file mode 100644
index 0000000000..e32856b6d9
--- /dev/null
+++ b/mojo/public/c/system/watcher.h
@@ -0,0 +1,184 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
+#define MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// A callback used to notify watchers about events on their watched handles.
+//
+// See documentation for |MojoWatcherNotificationFlags| for details regarding
+// the possible values of |flags|.
+//
+// See documentation for |MojoWatch()| for details regarding the other arguments
+// this callback receives when called.
+typedef void (*MojoWatcherCallback)(uintptr_t context,
+ MojoResult result,
+ struct MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags);
+
+// Creates a new watcher.
+//
+// Watchers are used to trigger arbitrary code execution when one or more
+// handles change state to meet certain conditions.
+//
+// A newly registered watcher is initially disarmed and may be armed using
+// |MojoArmWatcher()|. A watcher is also always disarmed immediately before any
+// invocation of one or more notification callbacks in response to a single
+// handle's state changing in some relevant way.
+//
+// Parameters:
+// |callback|: The |MojoWatcherCallback| to invoke any time the watcher is
+// notified of an event. See |MojoWatch()| for details regarding arguments
+// passed to the callback. Note that this may be called from any arbitrary
+// thread.
+// |watcher_handle|: The address at which to store the MojoHandle
+// corresponding to the new watcher if successfully created.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the watcher has been successfully created.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for
+// this watcher.
+MOJO_SYSTEM_EXPORT MojoResult MojoCreateWatcher(MojoWatcherCallback callback,
+ MojoHandle* watcher_handle);
+
+// Adds a watch to a watcher. This allows the watcher to fire notifications
+// regarding state changes on the handle corresponding to the arguments given.
+//
+// Note that notifications for a given watch context are guaranteed to be
+// mutually exclusive in execution: the callback will never be entered for a
+// given context while another invocation of the callback is still executing for
+// the same context. As a result it is generally a good idea to ensure that
+// callbacks do as little work as necessary in order to process the
+// notification.
+//
+// Parameters:
+// |watcher_handle|: The watcher to which |handle| is to be added.
+// |handle|: The handle to add to the watcher.
+// |signals|: The signals to watch for on |handle|.
+// |context|: An arbitrary context value given to any invocation of the
+// watcher's callback when invoked as a result of some state change
+// relevant to this combination of |handle| and |signals|. Must be
+// unique within any given watcher.
+//
+// Callback parameters (see |MojoWatcherNotificationCallback| above):
+// When the watcher invokes its callback as a result of some notification
+// relevant to this watch operation, |context| receives the value given here
+// and |signals_state| receives the last known signals state of this handle.
+//
+// |result| is one of the following:
+// |MOJO_RESULT_OK| if at least one of the watched signals is satisfied. The
+// watcher must be armed for this notification to fire.
+// |MOJO_RESULT_FAILED_PRECONDITION| if all of the watched signals are
+// permanently unsatisfiable. The watcher must be armed for this
+// notification to fire.
+// |MOJO_RESULT_CANCELLED| if the watch has been cancelled. The may occur if
+// the watcher has been closed, the watched handle has been closed, or
+// the watch for |context| has been explicitly cancelled. This is always
+// the last result received for any given context, and it is guaranteed
+// to be received exactly once per watch, regardless of how the watch
+// was cancelled.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the handle is now being watched by the watcher.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle,
+// |handle| is not a valid message pipe or data pipe handle.
+// |MOJO_RESULT_ALREADY_EXISTS| if the watcher already has a watch registered
+// for the given value of |context| or for the given |handle|.
+MOJO_SYSTEM_EXPORT MojoResult MojoWatch(MojoHandle watcher_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ uintptr_t context);
+
+// Removes a watch from a watcher.
+//
+// This ensures that the watch is cancelled as soon as possible. Cancellation
+// may be deferred (or may even block) an aritrarily long time if the watch is
+// already dispatching one or more notifications.
+//
+// When cancellation is complete, the watcher's callback is invoked one final
+// time for |context|, with the result |MOJO_RESULT_CANCELLED|.
+//
+// The same behavior can be elicted by either closing the watched handle
+// associated with this context, or by closing |watcher_handle| itself. In the
+// lastter case, all registered contexts on the watcher are implicitly cancelled
+// in a similar fashion.
+//
+// Parameters:
+// |watcher_handle|: The handle of the watcher from which to remove a watch.
+// |context|: The context of the watch to be removed.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the watch has been cancelled.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle.
+// |MOJO_RESULT_NOT_FOUND| if there is no watch registered on this watcher for
+// the given value of |context|.
+MOJO_SYSTEM_EXPORT MojoResult MojoCancelWatch(MojoHandle watcher_handle,
+ uintptr_t context);
+
+// Arms a watcher, enabling a single future event on one of the watched handles
+// to trigger a single notification for each relevant watch context associated
+// with that handle.
+//
+// Parameters:
+// |watcher_handle|: The handle of the watcher.
+// |num_ready_contexts|: An address pointing to the number of elements
+// available for storage in the remaining output buffers. Optional and
+// only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below for
+// more details.
+// |ready_contexts|: An output buffer for contexts corresponding to the
+// watches which would have notified if the watcher were armed. Optional
+// and only uesd on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below
+// for more details.
+// |ready_results|: An output buffer for MojoResult values corresponding to
+// each context in |ready_contexts|. Optional and only used on failure.
+// See |MOJO_RESULT_FAILED_PRECONDITION| below for more details.
+// |ready_signals_states|: An output buffer for |MojoHandleSignalsState|
+// structures corresponding to each context in |ready_contexts|. Optional
+// and only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below
+// for more details.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the watcher has been successfully armed. All arguments
+// other than |watcher_handle| are ignored in this case.
+// |MOJO_RESULT_NOT_FOUND| if the watcher does not have any registered watch
+// contexts. All arguments other than |watcher_handle| are ignored in this
+// case.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a valid watcher
+// handle, or if |num_ready_contexts| is non-null but any of the output
+// buffer paramters is null.
+// |MOJO_RESULT_FAILED_PRECONDITION| if one or more watches would have
+// notified immediately upon arming the watcher. If |num_handles| is
+// non-null, this assumes there is enough space for |*num_handles| entries
+// in each of the subsequent output buffer arguments.
+//
+// At most that many entries are placed in the output buffers,
+// corresponding to the watches which would have signalled if the watcher
+// had been armed successfully. The actual number of entries placed in the
+// output buffers is written to |*num_ready_contexts| before returning.
+//
+// If more than (input) |*num_ready_contexts| watch contexts were ready to
+// notify, the subset presented in output buffers is arbitrary, but the
+// implementation makes a best effort to circulate the outputs across
+// consecutive calls so that callers may reliably avoid handle starvation.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoArmWatcher(MojoHandle watcher_handle,
+ uint32_t* num_ready_contexts,
+ uintptr_t* ready_contexts,
+ MojoResult* ready_results,
+ struct MojoHandleSignalsState* ready_signals_states);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
diff --git a/mojo/public/c/test_support/BUILD.gn b/mojo/public/c/test_support/BUILD.gn
new file mode 100644
index 0000000000..e2abd5807e
--- /dev/null
+++ b/mojo/public/c/test_support/BUILD.gn
@@ -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.
+
+static_library("test_support") {
+ output_name = "mojo_public_test_support"
+
+ sources = [
+ "test_support.h",
+
+ # TODO(vtl): Convert this to thunks http://crbug.com/386799
+ "../../tests/test_support_private.cc",
+ "../../tests/test_support_private.h",
+ ]
+}
diff --git a/mojo/public/c/test_support/test_support.h b/mojo/public/c/test_support/test_support.h
new file mode 100644
index 0000000000..8e50441d68
--- /dev/null
+++ b/mojo/public/c/test_support/test_support.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 MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_
+#define MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_
+
+// Note: This header should be compilable as C.
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// |sub_test_name| is optional. If not null, it usually describes one particular
+// configuration of the test. For example, if |test_name| is "TestPacketRate",
+// |sub_test_name| could be "100BytesPerPacket".
+// When the perf data is visualized by the performance dashboard, data with
+// different |sub_test_name|s (but the same |test_name|) are depicted as
+// different traces on the same chart.
+void MojoTestSupportLogPerfResult(
+ const char* test_name,
+ const char* sub_test_name,
+ double value,
+ const char* units);
+
+// Opens a "/"-delimited file path relative to the source root.
+FILE* MojoTestSupportOpenSourceRootRelativeFile(
+ const char* source_root_relative_path);
+
+// Enumerates a "/"-delimited directory path relative to the source root.
+// Returns only regular files. The return value is a heap-allocated array of
+// heap-allocated strings. Each must be free'd separately.
+//
+// The return value is built like so:
+//
+// char** rv = (char**) calloc(N + 1, sizeof(char*));
+// rv[0] = strdup("a");
+// rv[1] = strdup("b");
+// rv[2] = strdup("c");
+// ...
+// rv[N] = NULL;
+//
+char** MojoTestSupportEnumerateSourceRootRelativeDirectory(
+ const char* source_root_relative_path);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
new file mode 100644
index 0000000000..bd87965fc8
--- /dev/null
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -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.
+
+interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings"
+
+component("bindings") {
+ sources = [
+ # Normally, targets should depend on the source_sets generated by mojom
+ # targets. However, the generated source_sets use portions of the bindings
+ # library. In order to avoid linker warnings about locally-defined imports
+ # in Windows components build, this target depends on the generated C++
+ # files directly so that the EXPORT macro defintions match.
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared-internal.h",
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared.cc",
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared.h",
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom.cc",
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom.h",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared-internal.h",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared.cc",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared.h",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.cc",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.h",
+ "array_data_view.h",
+ "array_traits.h",
+ "array_traits_carray.h",
+ "array_traits_stl.h",
+ "associated_binding.h",
+ "associated_binding_set.h",
+ "associated_group.h",
+ "associated_group_controller.h",
+ "associated_interface_ptr.h",
+ "associated_interface_ptr_info.h",
+ "associated_interface_request.h",
+ "binding.h",
+ "binding_set.h",
+ "bindings_export.h",
+ "clone_traits.h",
+ "connection_error_callback.h",
+ "connector.h",
+ "disconnect_reason.h",
+ "filter_chain.h",
+ "interface_data_view.h",
+ "interface_endpoint_client.h",
+ "interface_endpoint_controller.h",
+ "interface_id.h",
+ "interface_ptr.h",
+ "interface_ptr_info.h",
+ "interface_ptr_set.h",
+ "interface_request.h",
+ "lib/array_internal.cc",
+ "lib/array_internal.h",
+ "lib/array_serialization.h",
+ "lib/associated_binding.cc",
+ "lib/associated_group.cc",
+ "lib/associated_group_controller.cc",
+ "lib/associated_interface_ptr.cc",
+ "lib/associated_interface_ptr_state.h",
+ "lib/binding_state.cc",
+ "lib/binding_state.h",
+ "lib/bindings_internal.h",
+ "lib/buffer.h",
+ "lib/connector.cc",
+ "lib/control_message_handler.cc",
+ "lib/control_message_handler.h",
+ "lib/control_message_proxy.cc",
+ "lib/control_message_proxy.h",
+ "lib/equals_traits.h",
+ "lib/filter_chain.cc",
+ "lib/fixed_buffer.cc",
+ "lib/fixed_buffer.h",
+ "lib/handle_interface_serialization.h",
+ "lib/hash_util.h",
+ "lib/interface_endpoint_client.cc",
+ "lib/interface_ptr_state.h",
+ "lib/map_data_internal.h",
+ "lib/map_serialization.h",
+ "lib/may_auto_lock.h",
+ "lib/message.cc",
+ "lib/message_buffer.cc",
+ "lib/message_buffer.h",
+ "lib/message_builder.cc",
+ "lib/message_builder.h",
+ "lib/message_header_validator.cc",
+ "lib/message_internal.h",
+ "lib/multiplex_router.cc",
+ "lib/multiplex_router.h",
+ "lib/native_enum_data.h",
+ "lib/native_enum_serialization.h",
+ "lib/native_struct.cc",
+ "lib/native_struct_data.cc",
+ "lib/native_struct_data.h",
+ "lib/native_struct_serialization.cc",
+ "lib/native_struct_serialization.h",
+ "lib/pipe_control_message_handler.cc",
+ "lib/pipe_control_message_proxy.cc",
+ "lib/scoped_interface_endpoint_handle.cc",
+ "lib/serialization.h",
+ "lib/serialization_context.cc",
+ "lib/serialization_context.h",
+ "lib/serialization_forward.h",
+ "lib/serialization_util.h",
+ "lib/string_serialization.h",
+ "lib/string_traits_string16.cc",
+ "lib/sync_call_restrictions.cc",
+ "lib/sync_event_watcher.cc",
+ "lib/sync_handle_registry.cc",
+ "lib/sync_handle_watcher.cc",
+ "lib/template_util.h",
+ "lib/union_accessor.h",
+ "lib/validate_params.h",
+ "lib/validation_context.cc",
+ "lib/validation_context.h",
+ "lib/validation_errors.cc",
+ "lib/validation_errors.h",
+ "lib/validation_util.cc",
+ "lib/validation_util.h",
+ "map.h",
+ "map_data_view.h",
+ "map_traits.h",
+ "map_traits_stl.h",
+ "message.h",
+ "message_header_validator.h",
+ "native_enum.h",
+ "native_struct.h",
+ "native_struct_data_view.h",
+ "pipe_control_message_handler.h",
+ "pipe_control_message_handler_delegate.h",
+ "pipe_control_message_proxy.h",
+ "raw_ptr_impl_ref_traits.h",
+ "scoped_interface_endpoint_handle.h",
+ "string_data_view.h",
+ "string_traits.h",
+ "string_traits_stl.h",
+ "string_traits_string16.h",
+ "string_traits_string_piece.h",
+ "strong_associated_binding.h",
+ "strong_binding.h",
+ "strong_binding_set.h",
+ "struct_ptr.h",
+ "sync_call_restrictions.h",
+ "sync_event_watcher.h",
+ "sync_handle_registry.h",
+ "sync_handle_watcher.h",
+ "thread_safe_interface_ptr.h",
+ "type_converter.h",
+ "union_traits.h",
+ "unique_ptr_impl_ref_traits.h",
+ ]
+
+ public_deps = [
+ ":struct_traits",
+ "//base",
+ "//ipc:param_traits",
+ "//mojo/public/cpp/system",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/public/interfaces/bindings:bindings__generator",
+ "//mojo/public/interfaces/bindings:bindings_shared__generator",
+ ]
+
+ defines = [ "MOJO_CPP_BINDINGS_IMPLEMENTATION" ]
+}
+
+source_set("struct_traits") {
+ sources = [
+ "enum_traits.h",
+ "struct_traits.h",
+ ]
+}
+
+if (!is_ios) {
+ # TODO(yzshen): crbug.com/617718 Consider moving this into blink.
+ source_set("wtf_support") {
+ sources = [
+ "array_traits_wtf_vector.h",
+ "lib/string_traits_wtf.cc",
+ "lib/wtf_clone_equals_util.h",
+ "lib/wtf_hash_util.h",
+ "lib/wtf_serialization.h",
+ "map_traits_wtf_hash_map.h",
+ "string_traits_wtf.h",
+ ]
+
+ public_deps = [
+ ":bindings",
+ "//third_party/WebKit/Source/wtf",
+ ]
+
+ public_configs = [ "//third_party/WebKit/Source:config" ]
+ }
+}
diff --git a/mojo/public/cpp/bindings/DEPS b/mojo/public/cpp/bindings/DEPS
new file mode 100644
index 0000000000..36eba448e8
--- /dev/null
+++ b/mojo/public/cpp/bindings/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/WebKit/Source/wtf",
+]
diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md
new file mode 100644
index 0000000000..b37267a338
--- /dev/null
+++ b/mojo/public/cpp/bindings/README.md
@@ -0,0 +1,1231 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ Bindings API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+The Mojo C++ Bindings API leverages the
+[C++ System API](/mojo/public/cpp/system) to provide a more natural set of
+primitives for communicating over Mojo message pipes. Combined with generated
+code from the [Mojom IDL and bindings generator](/mojo/public/tools/bindings),
+users can easily connect interface clients and implementations across arbitrary
+intra- and inter-process bounaries.
+
+This document provides a detailed guide to bindings API usage with example code
+snippets. For a detailed API references please consult the headers in
+[//mojo/public/cpp/bindings](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/).
+
+## Getting Started
+
+When a Mojom IDL file is processed by the bindings generator, C++ code is
+emitted in a series of `.h` and `.cc` files with names based on the input
+`.mojom` file. Suppose we create the following Mojom file at
+`//services/db/public/interfaces/db.mojom`:
+
+```
+module db.mojom;
+
+interface Table {
+ AddRow(int32 key, string data);
+};
+
+interface Database {
+ CreateTable(Table& table);
+};
+```
+
+And a GN target to generate the bindings in
+`//services/db/public/interfaces/BUILD.gn`:
+
+```
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+ sources = [
+ "db.mojom",
+ ]
+}
+```
+
+If we then build this target:
+
+```
+ninja -C out/r services/db/public/interfaces
+```
+
+This will produce several generated source files, some of which are relevant to
+C++ bindings. Two of these files are:
+
+```
+out/gen/services/business/public/interfaces/factory.mojom.cc
+out/gen/services/business/public/interfaces/factory.mojom.h
+```
+
+You can include the above generated header in your sources in order to use the
+definitions therein:
+
+``` cpp
+#include "services/business/public/interfaces/factory.mojom.h"
+
+class TableImpl : public db::mojom::Table {
+ // ...
+};
+```
+
+This document covers the different kinds of definitions generated by Mojom IDL
+for C++ consumers and how they can effectively be used to communicate across
+message pipes.
+
+*** note
+**NOTE:** Using C++ bindings from within Blink code is typically subject to
+special constraints which require the use of a different generated header.
+For details, see [Blink Type Mapping](#Blink-Type-Mapping).
+***
+
+## Interfaces
+
+Mojom IDL interfaces are translated to corresponding C++ (pure virtual) class
+interface definitions in the generated header, consisting of a single generated
+method signature for each request message on the interface. Internally there is
+also generated code for serialization and deserialization of messages, but this
+detail is hidden from bindings consumers.
+
+### Basic Usage
+
+Let's consider a new `//sample/logger.mojom` to define a simple logging
+interface which clients can use to log simple string messages:
+
+``` cpp
+module sample.mojom;
+
+interface Logger {
+ Log(string message);
+};
+```
+
+Running this through the bindings generator will produce a `logging.mojom.h`
+with the following definitions (modulo unimportant details):
+
+``` cpp
+namespace sample {
+namespace mojom {
+
+class Logger {
+ virtual ~Logger() {}
+
+ virtual void Log(const std::string& message) = 0;
+};
+
+using LoggerPtr = mojo::InterfacePtr<Logger>;
+using LoggerRequest = mojo::InterfaceRequest<Logger>;
+
+} // namespace mojom
+} // namespace sample
+```
+
+Makes sense. Let's take a closer look at those type aliases at the end.
+
+### InterfacePtr and InterfaceRequest
+
+You will notice the type aliases for `LoggerPtr` and
+`LoggerRequest` are using two of the most fundamental template types in the C++
+bindings library: **`InterfacePtr<T>`** and **`InterfaceRequest<T>`**.
+
+In the world of Mojo bindings libraries these are effectively strongly-typed
+message pipe endpoints. If an `InterfacePtr<T>` is bound to a message pipe
+endpoint, it can be dereferenced to make calls on an opaque `T` interface. These
+calls immediately serialize their arguments (using generated code) and write a
+corresponding message to the pipe.
+
+An `InterfaceRequest<T>` is essentially just a typed container to hold the other
+end of an `InterfacePtr<T>`'s pipe -- the receiving end -- until it can be
+routed to some implementation which will **bind** it. The `InterfaceRequest<T>`
+doesn't actually *do* anything other than hold onto a pipe endpoint and carry
+useful compile-time type information.
+
+![Diagram illustrating InterfacePtr and InterfaceRequest on either end of a message pipe](https://docs.google.com/drawings/d/17d5gvErbQ6DthEBMS7I1WhCh9bz0n12pvNjydzuRfTI/pub?w=600&h=100)
+
+So how do we create a strongly-typed message pipe?
+
+### Creating Interface Pipes
+
+One way to do this is by manually creating a pipe and binding each end:
+
+``` cpp
+#include "sample/logger.mojom.h"
+
+mojo::MessagePipe pipe;
+sample::mojom::LoggerPtr logger;
+sample::mojom::LoggerRequest request;
+
+logger.Bind(sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0u));
+request.Bind(std::move(pipe.handle1));
+```
+
+That's pretty verbose, but the C++ Bindings library provides more convenient
+ways to accomplish the same thing. [interface_request.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_request.h)
+defines a `MakeRequest` function:
+
+``` cpp
+sample::mojom::LoggerPtr logger;
+sample::mojom::LoggerRequest request = mojo::MakeRequest(&logger);
+```
+
+and the `InterfaceRequest<T>` constructor can also take an explicit
+`InterfacePtr<T>*` output argument:
+
+``` cpp
+sample::mojom::LoggerPtr logger;
+sample::mojom::LoggerRequest request(&logger);
+```
+
+Both of these last two snippets are equivalent to the first one.
+
+*** note
+**NOTE:** In the first example above you may notice usage of the `LoggerPtrInfo`
+type, which is a generated alias for `mojo::InterfacePtrInfo<Logger>`. This is
+similar to an `InterfaceRequest<T>` in that it merely holds onto a pipe handle
+and cannot actually read or write messages on the pipe. Both this type and
+`InterfaceRequest<T>` are safe to move freely from thread to thread, whereas a
+bound `InterfacePtr<T>` is bound to a single thread.
+
+An `InterfacePtr<T>` may be unbound by calling its `PassInterface()` method,
+which returns a new `InterfacePtrInfo<T>`. Conversely, an `InterfacePtr<T>` may
+bind (and thus take ownership of) an `InterfacePtrInfo<T>` so that interface
+calls can be made on the pipe.
+
+The thread-bound nature of `InterfacePtr<T>` is necessary to support safe
+dispatch of its [message responses](#Receiving-Responses) and
+[connection error notifications](#Connection-Errors).
+***
+
+Once the `LoggerPtr` is bound we can immediately begin calling `Logger`
+interface methods on it, which will immediately write messages into the pipe.
+These messages will stay queued on the receiving end of the pipe until someone
+binds to it and starts reading them.
+
+``` cpp
+logger->Log("Hello!");
+```
+
+This actually writes a `Log` message to the pipe.
+
+![Diagram illustrating a message traveling on a pipe from LoggerPtr to LoggerRequest](https://docs.google.com/a/google.com/drawings/d/1jWEc6jJIP2ed77Gg4JJ3EVC7hvnwcImNqQJywFwpT8g/pub?w=648&h=123)
+
+But as mentioned above, `InterfaceRequest` *doesn't actually do anything*, so
+that message will just sit on the pipe forever. We need a way to read messages
+off the other end of the pipe and dispatch them. We have to
+**bind the interface request**.
+
+### Binding an Interface Request
+
+There are many different helper classes in the bindings library for binding the
+receiving end of a message pipe. The most primitive among them is the aptly
+named `mojo::Binding<T>`. A `mojo::Binding<T>` bridges an implementation of `T`
+with a single bound message pipe endpoint (via a `mojo::InterfaceRequest<T>`),
+which it continuously watches for readability.
+
+Any time the bound pipe becomes readable, the `Binding` will schedule a task to
+read, deserialize (using generated code), and dispatch all available messages to
+the bound `T` implementation. Below is a sample implementation of the `Logger`
+interface. Notice that the implementation itself owns a `mojo::Binding`. This is
+a common pattern, since a bound implementation must outlive any `mojo::Binding`
+which binds it.
+
+``` cpp
+#include "base/logging.h"
+#include "base/macros.h"
+#include "sample/logger.mojom.h"
+
+class LoggerImpl : public sample::mojom::Logger {
+ public:
+ // NOTE: A common pattern for interface implementations which have one
+ // instance per client is to take an InterfaceRequest in the constructor.
+
+ explicit LoggerImpl(sample::mojom::LoggerRequest request)
+ : binding_(this, std::move(request)) {}
+ ~Logger() override {}
+
+ // sample::mojom::Logger:
+ void Log(const std::string& message) override {
+ LOG(ERROR) << "[Logger] " << message;
+ }
+
+ private:
+ mojo::Binding<sample::mojom::Logger> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
+};
+```
+
+Now we can construct a `LoggerImpl` over our pending `LoggerRequest`, and the
+previously queued `Log` message will be dispatched ASAP on the `LoggerImpl`'s
+thread:
+
+``` cpp
+LoggerImpl impl(std::move(request));
+```
+
+The diagram below illustrates the following sequence of events, all set in
+motion by the above line of code:
+
+1. The `LoggerImpl` constructor is called, passing the `LoggerRequest` along
+ to the `Binding`.
+2. The `Binding` takes ownership of the `LoggerRequest`'s pipe endpoint and
+ begins watching it for readability. The pipe is readable immediately, so a
+ task is scheduled to read the pending `Log` message from the pipe ASAP.
+3. The `Log` message is read and deserialized, causing the `Binding` to invoke
+ the `Logger::Log` implementation on its bound `LoggerImpl`.
+
+![Diagram illustrating the progression of binding a request, reading a pending message, and dispatching it](https://docs.google.com/drawings/d/1c73-PegT4lmjfHoxhWrHTQXRvzxgb0wdeBa35WBwZ3Q/pub?w=550&h=500)
+
+As a result, our implementation will eventually log the client's `"Hello!"`
+message via `LOG(ERROR)`.
+
+*** note
+**NOTE:** Messages will only be read and dispatched from a pipe as long as the
+object which binds it (*i.e.* the `mojo::Binding` in the above example) remains
+alive.
+***
+
+### Receiving Responses
+
+Some Mojom interface methods expect a response. Suppose we modify our `Logger`
+interface so that the last logged line can be queried like so:
+
+``` cpp
+module sample.mojom;
+
+interface Logger {
+ Log(string message);
+ GetTail() => (string message);
+};
+```
+
+The generated C++ interface will now look like:
+
+``` cpp
+namespace sample {
+namespace mojom {
+
+class Logger {
+ public:
+ virtual ~Logger() {}
+
+ virtual void Log(const std::string& message) = 0;
+
+ using GetTailCallback = base::Callback<void(const std::string& message)>;
+
+ virtual void GetTail(const GetTailCallback& callback) = 0;
+}
+
+} // namespace mojom
+} // namespace sample
+```
+
+As before, both clients and implementations of this interface use the same
+signature for the `GetTail` method: implementations use the `callback` argument
+to *respond* to the request, while clients pass a `callback` argument to
+asynchronously `receive` the response. Here's an updated implementation:
+
+```cpp
+class LoggerImpl : public sample::mojom::Logger {
+ public:
+ // NOTE: A common pattern for interface implementations which have one
+ // instance per client is to take an InterfaceRequest in the constructor.
+
+ explicit LoggerImpl(sample::mojom::LoggerRequest request)
+ : binding_(this, std::move(request)) {}
+ ~Logger() override {}
+
+ // sample::mojom::Logger:
+ void Log(const std::string& message) override {
+ LOG(ERROR) << "[Logger] " << message;
+ lines_.push_back(message);
+ }
+
+ void GetTail(const GetTailCallback& callback) override {
+ callback.Run(lines_.back());
+ }
+
+ private:
+ mojo::Binding<sample::mojom::Logger> binding_;
+ std::vector<std::string> lines_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
+};
+```
+
+And an updated client call:
+
+``` cpp
+void OnGetTail(const std::string& message) {
+ LOG(ERROR) << "Tail was: " << message;
+}
+
+logger->GetTail(base::Bind(&OnGetTail));
+```
+
+Behind the scenes, the implementation-side callback is actually serializing the
+response arguments and writing them onto the pipe for delivery back to the
+client. Meanwhile the client-side callback is invoked by some internal logic
+which watches the pipe for an incoming response message, reads and deserializes
+it once it arrives, and then invokes the callback with the deserialized
+parameters.
+
+### Connection Errors
+
+If there are no remaining messages available on a pipe and the remote end has
+been closed, a connection error will be triggered on the local end. Connection
+errors may also be triggered by automatic forced local pipe closure due to
+*e.g.* a validation error when processing a received message.
+
+Regardless of the underlying cause, when a connection error is encountered on
+a binding endpoint, that endpoint's **connection error handler** (if set) is
+invoked. This handler is a simple `base::Closure` and may only be invoked
+*once* as long as the endpoint is bound to the same pipe. Typically clients and
+implementations use this handler to do some kind of cleanup or -- particuarly if
+the error was unexpected -- create a new pipe and attempt to establish a new
+connection with it.
+
+All message pipe-binding C++ objects (*e.g.*, `mojo::Binding<T>`,
+`mojo::InterfacePtr<T>`, *etc.*) support setting their connection error handler
+via a `set_connection_error_handler` method.
+
+We can set up another end-to-end `Logger` example to demonstrate error handler
+invocation:
+
+``` cpp
+sample::mojom::LoggerPtr logger;
+LoggerImpl impl(mojo::MakeRequest(&logger));
+impl.set_connection_error_handler(base::Bind([] { LOG(ERROR) << "Bye."; }));
+logger->Log("OK cool");
+logger.reset(); // Closes the client end.
+```
+
+As long as `impl` stays alive here, it will eventually receive the `Log` message
+followed immediately by an invocation of the bound callback which outputs
+`"Bye."`. Like all other bindings callbacks, a connection error handler will
+**never** be invoked once its corresponding binding object has been destroyed.
+
+In fact, suppose instead that `LoggerImpl` had set up the following error
+handler within its constructor:
+
+``` cpp
+LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request)
+ : binding_(this, std::move(request)) {
+ binding_.set_connection_error_handler(
+ base::Bind(&LoggerImpl::OnError, base::Unretained(this)));
+}
+
+void LoggerImpl::OnError() {
+ LOG(ERROR) << "Client disconnected! Purging log lines.";
+ lines_.clear();
+}
+```
+
+The use of `base::Unretained` is *safe* because the error handler will never be
+invoked beyond the lifetime of `binding_`, and `this` owns `binding_`.
+
+### A Note About Ordering
+
+As mentioned in the previous section, closing one end of a pipe will eventually
+trigger a connection error on the other end. However it's important to note that
+this event is itself ordered with respect to any other event (*e.g.* writing a
+message) on the pipe.
+
+This means that it's safe to write something contrived like:
+
+``` cpp
+void GoBindALogger(sample::mojom::LoggerRequest request) {
+ LoggerImpl impl(std::move(request));
+ base::RunLoop loop;
+ impl.set_connection_error_handler(loop.QuitClosure());
+ loop.Run();
+}
+
+void LogSomething() {
+ sample::mojom::LoggerPtr logger;
+ bg_thread->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&GoBindALogger, mojo::MakeRequest(&logger)));
+ logger->Log("OK Computer");
+}
+```
+
+When `logger` goes out of scope it immediately closes its end of the message
+pipe, but the impl-side won't notice this until it receives the sent `Log`
+message. Thus the `impl` above will first log our message and *then* see a
+connection error and break out of the run loop.
+
+### Sending Interfaces Over Interfaces
+
+Now we know how to create interface pipes and use their Ptr and Request
+endpoints in some interesting ways. This still doesn't add up to interesting
+IPC! The bread and butter of Mojo IPC is the ability to transfer interface
+endpoints across other interfaces, so let's take a look at how to accomplish
+that.
+
+#### Sending Interface Requests
+
+Consider a new example Mojom in `//sample/db.mojom`:
+
+``` cpp
+module db.mojom;
+
+interface Table {
+ void AddRow(int32 key, string data);
+};
+
+interface Database {
+ AddTable(Table& table);
+};
+```
+
+As noted in the
+[Mojom IDL documentation](/mojo/public/tools/bindings#Primitive-Types),
+the `Table&` syntax denotes a `Table` interface request. This corresponds
+precisely to the `InterfaceRequest<T>` type discussed in the sections above, and
+in fact the generated code for these interfaces is approximately:
+
+``` cpp
+namespace db {
+namespace mojom {
+
+class Table {
+ public:
+ virtual ~Table() {}
+
+ virtual void AddRow(int32_t key, const std::string& data) = 0;
+}
+
+using TablePtr = mojo::InterfacePtr<Table>;
+using TableRequest = mojo::InterfaceRequest<Table>;
+
+class Database {
+ public:
+ virtual ~Database() {}
+
+ virtual void AddTable(TableRequest table);
+};
+
+using DatabasePtr = mojo::InterfacePtr<Database>;
+using DatabaseRequest = mojo::InterfaceRequest<Database>;
+
+} // namespace mojom
+} // namespace db
+```
+
+We can put this all together now with an implementation of `Table` and
+`Database`:
+
+``` cpp
+#include "sample/db.mojom.h"
+
+class TableImpl : public db::mojom:Table {
+ public:
+ explicit TableImpl(db::mojom::TableRequest request)
+ : binding_(this, std::move(request)) {}
+ ~TableImpl() override {}
+
+ // db::mojom::Table:
+ void AddRow(int32_t key, const std::string& data) override {
+ rows_.insert({key, data});
+ }
+
+ private:
+ mojo::Binding<db::mojom::Table> binding_;
+ std::map<int32_t, std::string> rows_;
+};
+
+class DatabaseImpl : public db::mojom::Database {
+ public:
+ explicit DatabaseImpl(db::mojom::DatabaseRequest request)
+ : binding_(this, std::move(request)) {}
+ ~DatabaseImpl() override {}
+
+ // db::mojom::Database:
+ void AddTable(db::mojom::TableRequest table) {
+ tables_.emplace_back(base::MakeUnique<TableImpl>(std::move(table)));
+ }
+
+ private:
+ mojo::Binding<db::mojom::Database> binding_;
+ std::vector<std::unique_ptr<TableImpl>> tables_;
+};
+```
+
+Pretty straightforward. The `Table&` Mojom paramter to `AddTable` translates to
+a C++ `db::mojom::TableRequest`, aliased from
+`mojo::InterfaceRequest<db::mojom::Table>`, which we know is just a
+strongly-typed message pipe handle. When `DatabaseImpl` gets an `AddTable` call,
+it constructs a new `TableImpl` and binds it to the received `TableRequest`.
+
+Let's see how this can be used.
+
+``` cpp
+db::mojom::DatabasePtr database;
+DatabaseImpl db_impl(mojo::MakeRequest(&database));
+
+db::mojom::TablePtr table1, table2;
+database->AddTable(mojo::MakeRequest(&table1));
+database->AddTable(mojo::MakeRequest(&table2));
+
+table1->AddRow(1, "hiiiiiiii");
+table2->AddRow(2, "heyyyyyy");
+```
+
+Notice that we can again start using the new `Table` pipes immediately, even
+while their `TableRequest` endpoints are still in transit.
+
+#### Sending InterfacePtrs
+
+Of course we can also send `InterfacePtr`s:
+
+``` cpp
+interface TableListener {
+ OnRowAdded(int32 key, string data);
+};
+
+interface Table {
+ AddRow(int32 key, string data);
+
+ AddListener(TableListener listener);
+};
+```
+
+This would generate a `Table::AddListener` signature like so:
+
+``` cpp
+ virtual void AddListener(TableListenerPtr listener) = 0;
+```
+
+and this could be used like so:
+
+``` cpp
+db::mojom::TableListenerPtr listener;
+TableListenerImpl impl(mojo::MakeRequest(&listener));
+table->AddListener(std::move(listener));
+```
+
+## Other Interface Binding Types
+
+The [Interfaces](#Interfaces) section above covers basic usage of the most
+common bindings object types: `InterfacePtr`, `InterfaceRequest`, and `Binding`.
+While these types are probably the most commonly used in practice, there are
+several other ways of binding both client- and implementation-side interface
+pipes.
+
+### Strong Bindings
+
+A **strong binding** exists as a standalone object which owns its interface
+implementation and automatically cleans itself up when its bound interface
+endpoint detects an error. The
+[**`MakeStrongBinding`**](https://cs.chromim.org/chromium/src//mojo/public/cpp/bindings/strong_binding.h)
+function is used to create such a binding.
+.
+
+``` cpp
+class LoggerImpl : public sample::mojom::Logger {
+ public:
+ LoggerImpl() {}
+ ~LoggerImpl() override {}
+
+ // sample::mojom::Logger:
+ void Log(const std::string& message) override {
+ LOG(ERROR) << "[Logger] " << message;
+ }
+
+ private:
+ // NOTE: This doesn't own any Binding object!
+};
+
+db::mojom::LoggerPtr logger;
+mojo::MakeStrongBinding(base::MakeUnique<DatabaseImpl>(),
+ mojo::MakeRequest(&logger));
+
+logger->Log("NOM NOM NOM MESSAGES");
+```
+
+Now as long as `logger` remains open somewhere in the system, the bound
+`DatabaseImpl` on the other end will remain alive.
+
+### Binding Sets
+
+Sometimes it's useful to share a single implementation instance with multiple
+clients. [**`BindingSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/binding_set.h)
+makes this easy. Consider the Mojom:
+
+``` cpp
+module system.mojom;
+
+interface Logger {
+ Log(string message);
+};
+
+interface LoggerProvider {
+ GetLogger(Logger& logger);
+};
+```
+
+We can use `BindingSet` to bind multiple `Logger` requests to a single
+implementation instance:
+
+``` cpp
+class LogManager : public system::mojom::LoggerProvider,
+ public system::mojom::Logger {
+ public:
+ explicit LogManager(system::mojom::LoggerProviderRequest request)
+ : provider_binding_(this, std::move(request)) {}
+ ~LogManager() {}
+
+ // system::mojom::LoggerProvider:
+ void GetLogger(LoggerRequest request) override {
+ logger_bindings_.AddBinding(this, std::move(request));
+ }
+
+ // system::mojom::Logger:
+ void Log(const std::string& message) override {
+ LOG(ERROR) << "[Logger] " << message;
+ }
+
+ private:
+ mojo::Binding<system::mojom::LoggerProvider> provider_binding_;
+ mojo::BindingSet<system::mojom::Logger> logger_bindings_;
+};
+
+```
+
+
+### InterfacePtr Sets
+
+Similar to the `BindingSet` above, sometimes it's useful to maintain a set of
+`InterfacePtr`s for *e.g.* a set of clients observing some event.
+[**`InterfacePtrSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_ptr_set.h)
+is here to help. Take the Mojom:
+
+``` cpp
+module db.mojom;
+
+interface TableListener {
+ OnRowAdded(int32 key, string data);
+};
+
+interface Table {
+ AddRow(int32 key, string data);
+ AddListener(TableListener listener);
+};
+```
+
+An implementation of `Table` might look something like like this:
+
+``` cpp
+class TableImpl : public db::mojom::Table {
+ public:
+ TableImpl() {}
+ ~TableImpl() override {}
+
+ // db::mojom::Table:
+ void AddRow(int32_t key, const std::string& data) override {
+ rows_.insert({key, data});
+ listeners_.ForEach([key, &data](db::mojom::TableListener* listener) {
+ listener->OnRowAdded(key, data);
+ });
+ }
+
+ void AddListener(db::mojom::TableListenerPtr listener) {
+ listeners_.AddPtr(std::move(listener));
+ }
+
+ private:
+ mojo::InterfacePtrSet<db::mojom::Table> listeners_;
+ std::map<int32_t, std::string> rows_;
+};
+```
+
+## Associated Interfaces
+
+See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces).
+
+TODO: Move the above doc into the repository markdown docs.
+
+## Synchronous Calls
+
+See [this document](https://www.chromium.org/developers/design-documents/mojo/synchronous-calls)
+
+TODO: Move the above doc into the repository markdown docs.
+
+## Type Mapping
+
+In many instances you might prefer that your generated C++ bindings use a more
+natural type to represent certain Mojom types in your interface methods. For one
+example consider a Mojom struct such as the `Rect` below:
+
+``` cpp
+module gfx.mojom;
+
+struct Rect {
+ int32 x;
+ int32 y;
+ int32 width;
+ int32 height;
+};
+
+interface Canvas {
+ void FillRect(Rect rect);
+};
+```
+
+The `Canvas` Mojom interface would normally generate a C++ interface like:
+
+``` cpp
+class Canvas {
+ public:
+ virtual void FillRect(RectPtr rect) = 0;
+};
+```
+
+However, the Chromium tree already defines a native
+[`gfx::Rect`](https://cs.chromium.org/chromium/src/ui/gfx/geometry/rect.h) which
+is equivalent in meaning but which also has useful helper methods. Instead of
+manually converting between a `gfx::Rect` and the Mojom-generated `RectPtr` at
+every message boundary, wouldn't it be nice if the Mojom bindings generator
+could instead generate:
+
+``` cpp
+class Canvas {
+ public:
+ virtual void FillRect(const gfx::Rect& rect) = 0;
+}
+```
+
+The correct answer is, "Yes! That would be nice!" And fortunately, it can!
+
+### Global Configuration
+
+While this feature is quite powerful, it introduces some unavoidable complexity
+into build system. This stems from the fact that type-mapping is an inherently
+viral concept: if `gfx::mojom::Rect` is mapped to `gfx::Rect` anywhere, the
+mapping needs to apply *everywhere*.
+
+For this reason we have a few global typemap configurations defined in
+[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni)
+and
+[blink_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). These configure the two supported [variants](#Variants) of Mojom generated
+bindings in the repository. Read more on this in the sections that follow.
+
+For now, let's take a look at how to express the mapping from `gfx::mojom::Rect`
+to `gfx::Rect`.
+
+### Defining `StructTraits`
+
+In order to teach generated bindings code how to serialize an arbitrary native
+type `T` as an arbitrary Mojom type `mojom::U`, we need to define an appropriate
+specialization of the
+[`mojo::StructTraits`](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/struct_traits.h)
+template.
+
+A valid specialization of `StructTraits` MUST define the following static
+methods:
+
+* A single static accessor for every field of the Mojom struct, with the exact
+ same name as the struct field. These accessors must all take a const ref to
+ an object of the native type, and must return a value compatible with the
+ Mojom struct field's type. This is used to safely and consistently extract
+ data from the native type during message serialization without incurring extra
+ copying costs.
+
+* A single static `Read` method which initializes an instance of the the native
+ type given a serialized representation of the Mojom struct. The `Read` method
+ must return a `bool` to indicate whether the incoming data is accepted
+ (`true`) or rejected (`false`).
+
+There are other methods a `StructTraits` specialization may define to satisfy
+some less common requirements. See
+[Advanced StructTraits Usage](#Advanced-StructTraits-Usage) for details.
+
+In order to define the mapping for `gfx::Rect`, we want the following
+`StructTraits` specialization, which we'll define in
+`//ui/gfx/geometry/mojo/geometry_struct_traits.h`:
+
+``` cpp
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/mojo/geometry.mojom.h"
+
+namespace mojo {
+
+template <>
+class StructTraits<gfx::mojom::RectDataView, gfx::Rect> {
+ public:
+ static int32_t x(const gfx::Rect& r) { return r.x(); }
+ static int32_t y(const gfx::Rect& r) { return r.y(); }
+ static int32_t width(const gfx::Rect& r) { return r.width(); }
+ static int32_t height(const gfx::Rect& r) { return r.height(); }
+
+ static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect);
+};
+
+} // namespace mojo
+```
+
+And in `//ui/gfx/geometry/mojo/geometry_struct_traits.cc`:
+
+``` cpp
+#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
+
+namespace mojo {
+
+// static
+template <>
+bool StructTraits<gfx::mojom::RectDataView, gfx::Rect>::Read(
+ gfx::mojom::RectDataView data,
+ gfx::Rect* out_rect) {
+ if (data.width() < 0 || data.height() < 0)
+ return false;
+
+ out_rect->SetRect(data.x(), data.y(), data.width(), data.height());
+ return true;
+};
+
+} // namespace mojo
+```
+
+Note that the `Read()` method returns `false` if either the incoming `width` or
+`height` fields are negative. This acts as a validation step during
+deserialization: if a client sends a `gfx::Rect` with a negative width or
+height, its message will be rejected and the pipe will be closed. In this way,
+type mapping can serve to enable custom validation logic in addition to making
+callsites and interface implemention more convenient.
+
+### Enabling a New Type Mapping
+
+We've defined the `StructTraits` necessary, but we still need to teach the
+bindings generator (and hence the build system) about the mapping. To do this we
+must create a **typemap** file, which uses familiar GN syntax to describe the
+new type mapping.
+
+Let's place this `geometry.typemap` file alongside our Mojom file:
+
+```
+mojom = "//ui/gfx/geometry/mojo/geometry.mojom"
+public_headers = [ "//ui/gfx/geometry/rect.h" ]
+traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ]
+sources = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.cc" ]
+public_deps = [ "//ui/gfx/geometry" ]
+type_mappings = [
+ "gfx.mojom.Rect=gfx::Rect",
+]
+```
+
+Let's look at each of the variables above:
+
+* `mojom`: Specifies the `mojom` file to which the typemap applies. Many
+ typemaps may apply to the same `mojom` file, but any given typemap may only
+ apply to a single `mojom` file.
+* `public_headers`: Additional headers required by any code which would depend
+ on the Mojom definition of `gfx.mojom.Rect` now that the typemap is applied.
+ Any headers required for the native target type definition should be listed
+ here.
+* `traits_headers`: Headers which contain the relevant `StructTraits`
+ specialization(s) for any type mappings described by this file.
+* `sources`: Any private implementation sources needed for the `StructTraits`
+ definition.
+* `public_deps`: Target dependencies exposed by the `public_headers` and
+ `traits_headers`.
+* `deps`: Target dependencies exposed by `sources` but not already covered by
+ `public_deps`.
+* `type_mappings`: A list of type mappings to be applied for this typemap. The
+ strings in this list are of the format `"MojomType=CppType"`, where
+ `MojomType` must be a fully qualified Mojom typename and `CppType` must be a
+ fully qualified C++ typename. Additional attributes may be specified in square
+ brackets following the `CppType`:
+ * `move_only`: The `CppType` is move-only and should be passed by value
+ in any generated method signatures. Note that `move_only` is transitive,
+ so containers of `MojomType` will translate to containers of `CppType`
+ also passed by value.
+ * `copyable_pass_by_value`: Forces values of type `CppType` to be passed by
+ value without moving them. Unlike `move_only`, this is not transitive.
+ * `nullable_is_same_type`: By default a non-nullable `MojomType` will be
+ mapped to `CppType` while a nullable `MojomType?` will be mapped to
+ `base::Optional<CppType>`. If this attribute is set, the `base::Optional`
+ wrapper is omitted for nullable `MojomType?` values, but the
+ `StructTraits` definition for this type mapping must define additional
+ `IsNull` and `SetToNull` methods. See
+ [Specializing Nullability](#Specializing-Nullability) below.
+
+
+Now that we have the typemap file we need to add it to a local list of typemaps
+that can be added to the global configuration. We create a new
+`//ui/gfx/typemaps.gni` file with the following contents:
+
+```
+typemaps = [
+ "//ui/gfx/geometry/mojo/geometry.typemap",
+]
+```
+
+And finally we can reference this file in the global default (Chromium) bindings
+configuration by adding it to `_typemap_imports` in
+[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni):
+
+```
+_typemap_imports = [
+ ...,
+ "//ui/gfx/typemaps.gni",
+ ...,
+]
+```
+
+### StructTraits Reference
+
+Each of a `StructTraits` specialization's static getter methods -- one per
+struct field -- must return a type which can be used as a data source for the
+field during serialization. This is a quick reference mapping Mojom field type
+to valid getter return types:
+
+| Mojom Field Type | C++ Getter Return Type |
+|------------------------------|------------------------|
+| `bool` | `bool`
+| `int8` | `int8_t`
+| `uint8` | `uint8_t`
+| `int16` | `int16_t`
+| `uint16` | `uint16_t`
+| `int32` | `int32_t`
+| `uint32` | `uint32_t`
+| `int64` | `int64_t`
+| `uint64` | `uint64_t`
+| `float` | `float`
+| `double` | `double`
+| `handle` | `mojo::ScopedHandle`
+| `handle<message_pipe>` | `mojo::ScopedMessagePipeHandle`
+| `handle<data_pipe_consumer>` | `mojo::ScopedDataPipeConsumerHandle`
+| `handle<data_pipe_producer>` | `mojo::ScopedDataPipeProducerHandle`
+| `handle<shared_buffer>` | `mojo::ScopedSharedBufferHandle`
+| `FooInterface` | `FooInterfacePtr`
+| `FooInterface&` | `FooInterfaceRequest`
+| `associated FooInterface` | `FooAssociatedInterfacePtr`
+| `associated FooInterface&` | `FooAssociatedInterfaceRequest`
+| `string` | Value or reference to any type `T` that has a `mojo::StringTraits` specialization defined. By default this includes `std::string`, `base::StringPiece`, and `WTF::String` (Blink).
+| `array<T>` | Value or reference to any type `T` that has a `mojo::ArrayTraits` specialization defined. By default this includes `std::vector<T>`, `mojo::CArray<T>`, and `WTF::Vector<T>` (Blink).
+| `map<K, V>` | Value or reference to any type `T` that has a `mojo::MapTraits` specialization defined. By default this includes `std::map<T>`, `mojo::unordered_map<T>`, and `WTF::HashMap<T>` (Blink).
+| `FooEnum` | Value of any type that has an appropriate `EnumTraits` specialization defined. By default this inlcudes only the generated `FooEnum` type.
+| `FooStruct` | Value or reference to any type that has an appropriate `StructTraits` specialization defined. By default this includes only the generated `FooStructPtr` type.
+| `FooUnion` | Value of reference to any type that has an appropriate `UnionTraits` specialization defined. By default this includes only the generated `FooUnionPtr` type.
+
+### Using Generated DataView Types
+
+Static `Read` methods on `StructTraits` specializations get a generated
+`FooDataView` argument (such as the `RectDataView` in the example above) which
+exposes a direct view of the serialized Mojom structure within an incoming
+message's contents. In order to make this as easy to work with as possible, the
+generated `FooDataView` types have a generated method corresponding to every
+struct field:
+
+* For POD field types (*e.g.* bools, floats, integers) these are simple accessor
+ methods with names identical to the field name. Hence in the `Rect` example we
+ can access things like `data.x()` and `data.width()`. The return types
+ correspond exactly to the mappings listed in the table above, under
+ [StructTraits Reference](#StructTraits-Reference).
+
+* For handle and interface types (*e.g* `handle` or `FooInterface&`) these
+ are named `TakeFieldName` (for a field named `field_name`) and they return an
+ appropriate move-only handle type by value. The return types correspond
+ exactly to the mappings listed in the table above, under
+ [StructTraits Reference](#StructTraits-Reference).
+
+* For all other field types (*e.g.*, enums, strings, arrays, maps, structs)
+ these are named `ReadFieldName` (for a field named `field_name`) and they
+ return a `bool` (to indicate success or failure in reading). On success they
+ fill their output argument with the deserialized field value. The output
+ argument may be a pointer to any type with an appropriate `StructTraits`
+ specialization defined, as mentioned in the table above, under
+ [StructTraits Reference](#StructTraits-Reference).
+
+An example would be useful here. Suppose we introduced a new Mojom struct:
+
+``` cpp
+struct RectPair {
+ Rect left;
+ Rect right;
+};
+```
+
+and a corresponding C++ type:
+
+``` cpp
+class RectPair {
+ public:
+ RectPair() {}
+
+ const gfx::Rect& left() const { return left_; }
+ const gfx::Rect& right() const { return right_; }
+
+ void Set(const gfx::Rect& left, const gfx::Rect& right) {
+ left_ = left;
+ right_ = right;
+ }
+
+ // ... some other stuff
+
+ private:
+ gfx::Rect left_;
+ gfx::Rect right_;
+};
+```
+
+Our traits to map `gfx::mojom::RectPair` to `gfx::RectPair` might look like
+this:
+
+``` cpp
+namespace mojo {
+
+template <>
+class StructTraits
+ public:
+ static const gfx::Rect& left(const gfx::RectPair& pair) {
+ return pair.left();
+ }
+
+ static const gfx::Rect& right(const gfx::RectPair& pair) {
+ return pair.right();
+ }
+
+ static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) {
+ gfx::Rect left, right;
+ if (!data.ReadLeft(&left) || !data.ReadRight(&right))
+ return false;
+ out_pair->Set(left, right);
+ return true;
+ }
+} // namespace mojo
+```
+
+Generated `ReadFoo` methods always convert `multi_word_field_name` fields to
+`ReadMultiWordFieldName` methods.
+
+### Variants
+
+By now you may have noticed that additional C++ sources are generated when a
+Mojom is processed. These exist due to type mapping, and the source files we
+refer to throughout this docuemnt (namely `foo.mojom.cc` and `foo.mojom.h`) are
+really only one **variant** (the *default* or *chromium* variant) of the C++
+bindings for a given Mojom file.
+
+The only other variant currently defined in the tree is the *blink* variant,
+which produces a few additional files:
+
+```
+out/gen/sample/db.mojom-blink.cc
+out/gen/sample/db.mojom-blink.h
+```
+
+These files mirror the definitions in the default variant but with different
+C++ types in place of certain builtin field and parameter types. For example,
+Mojom strings are represented by `WTF::String` instead of `std::string`. To
+avoid symbol collisions, the variant's symbols are nested in an extra inner
+namespace, so Blink consumer of the interface might write something like:
+
+```
+#include "sample/db.mojom-blink.h"
+
+class TableImpl : public db::mojom::blink::Table {
+ public:
+ void AddRow(int32_t key, const WTF::String& data) override {
+ // ...
+ }
+};
+```
+
+In addition to using different C++ types for builtin strings, arrays, and maps,
+the global typemap configuration for default and "blink" variants are completely
+separate. To add a typemap for the Blink configuration, you can modify
+[blink_bindings_configuration.gni](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni).
+
+All variants share some definitions which are unaffected by differences in the
+type mapping configuration (enums, for example). These definitions are generated
+in *shared* sources:
+
+```
+out/gen/sample/db.mojom-shared.cc
+out/gen/sample/db.mojom-shared.h
+out/gen/sample/db.mojom-shared-internal.h
+```
+
+Including either variant's header (`db.mojom.h` or `db.mojom-blink.h`)
+implicitly includes the shared header, but you have on some occasions wish to
+include *only* the shared header in some instances.
+
+Finally, note that for `mojom` GN targets, there is implicitly a corresponding
+`mojom_{variant}` target defined for any supported bindings configuration. So
+for example if you've defined in `//sample/BUILD.gn`:
+
+```
+import("mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+ sources = [
+ "db.mojom",
+ ]
+}
+```
+
+Code in Blink which wishes to use the generated Blink-variant definitions must
+depend on `"//sample:interfaces_blink"`.
+
+## Versioning Considerations
+
+For general documentation of versioning in the Mojom IDL see
+[Versioning](/mojo/public/tools/bindings#Versioning).
+
+This section briefly discusses some C++-specific considerations relevant to
+versioned Mojom types.
+
+### Querying Interface Versions
+
+`InterfacePtr` defines the following methods to query or assert remote interface
+version:
+
+```cpp
+void QueryVersion(const base::Callback<void(uint32_t)>& callback);
+```
+
+This queries the remote endpoint for the version number of its binding. When a
+response is received `callback` is invoked with the remote version number. Note
+that this value is cached by the `InterfacePtr` instance to avoid redundant
+queries.
+
+```cpp
+void RequireVersion(uint32_t version);
+```
+
+Informs the remote endpoint that a minimum version of `version` is required by
+the client. If the remote endpoint cannot support that version, it will close
+its end of the pipe immediately, preventing any other requests from being
+received.
+
+### Versioned Enums
+
+For convenience, every extensible enum has a generated helper function to
+determine whether a received enum value is known by the implementation's current
+version of the enum definition. For example:
+
+```cpp
+[Extensible]
+enum Department {
+ SALES,
+ DEV,
+ RESEARCH,
+};
+```
+
+generates the function in the same namespace as the generated C++ enum type:
+
+```cpp
+inline bool IsKnownEnumValue(Department value);
+```
+
+### Additional Documentation
+
+[Calling Mojo From Blink](https://www.chromium.org/developers/design-documents/mojo/calling-mojo-from-blink)
+: A brief overview of what it looks like to use Mojom C++ bindings from
+ within Blink code.
diff --git a/mojo/public/cpp/bindings/array_data_view.h b/mojo/public/cpp/bindings/array_data_view.h
new file mode 100644
index 0000000000..d02a8846ec
--- /dev/null
+++ b/mojo/public/cpp/bindings/array_data_view.h
@@ -0,0 +1,244 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_
+
+#include <type_traits>
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename T, typename EnableType = void>
+class ArrayDataViewImpl;
+
+template <typename T>
+class ArrayDataViewImpl<
+ T,
+ typename std::enable_if<
+ BelongsTo<T, MojomTypeCategory::POD>::value>::type> {
+ public:
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ T operator[](size_t index) const { return data_->at(index); }
+
+ const T* data() const { return data_->storage(); }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+template <typename T>
+class ArrayDataViewImpl<
+ T,
+ typename std::enable_if<
+ BelongsTo<T, MojomTypeCategory::BOOLEAN>::value>::type> {
+ public:
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ bool operator[](size_t index) const { return data_->at(index); }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+template <typename T>
+class ArrayDataViewImpl<
+ T,
+ typename std::enable_if<
+ BelongsTo<T, MojomTypeCategory::ENUM>::value>::type> {
+ public:
+ static_assert(sizeof(T) == sizeof(int32_t), "Unexpected enum size");
+
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ T operator[](size_t index) const { return static_cast<T>(data_->at(index)); }
+
+ const T* data() const { return reinterpret_cast<const T*>(data_->storage()); }
+
+ template <typename U>
+ bool Read(size_t index, U* output) {
+ return Deserialize<T>(data_->at(index), output);
+ }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+template <typename T>
+class ArrayDataViewImpl<
+ T,
+ typename std::enable_if<
+ BelongsTo<T,
+ MojomTypeCategory::ASSOCIATED_INTERFACE |
+ MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST |
+ MojomTypeCategory::INTERFACE |
+ MojomTypeCategory::INTERFACE_REQUEST>::value>::type> {
+ public:
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ template <typename U>
+ U Take(size_t index) {
+ U result;
+ bool ret = Deserialize<T>(&data_->at(index), &result, context_);
+ DCHECK(ret);
+ return result;
+ }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+template <typename T>
+class ArrayDataViewImpl<
+ T,
+ typename std::enable_if<
+ BelongsTo<T, MojomTypeCategory::HANDLE>::value>::type> {
+ public:
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ T Take(size_t index) {
+ T result;
+ bool ret = Deserialize<T>(&data_->at(index), &result, context_);
+ DCHECK(ret);
+ return result;
+ }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+template <typename T>
+class ArrayDataViewImpl<T,
+ typename std::enable_if<BelongsTo<
+ T,
+ MojomTypeCategory::ARRAY | MojomTypeCategory::MAP |
+ MojomTypeCategory::STRING |
+ MojomTypeCategory::STRUCT>::value>::type> {
+ public:
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ void GetDataView(size_t index, T* output) {
+ *output = T(data_->at(index).Get(), context_);
+ }
+
+ template <typename U>
+ bool Read(size_t index, U* output) {
+ return Deserialize<T>(data_->at(index).Get(), output, context_);
+ }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+template <typename T>
+class ArrayDataViewImpl<
+ T,
+ typename std::enable_if<
+ BelongsTo<T, MojomTypeCategory::UNION>::value>::type> {
+ public:
+ using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data;
+
+ ArrayDataViewImpl(Data_* data, SerializationContext* context)
+ : data_(data), context_(context) {}
+
+ void GetDataView(size_t index, T* output) {
+ *output = T(&data_->at(index), context_);
+ }
+
+ template <typename U>
+ bool Read(size_t index, U* output) {
+ return Deserialize<T>(&data_->at(index), output, context_);
+ }
+
+ protected:
+ Data_* data_;
+ SerializationContext* context_;
+};
+
+} // namespace internal
+
+template <typename K, typename V>
+class MapDataView;
+
+template <typename T>
+class ArrayDataView : public internal::ArrayDataViewImpl<T> {
+ public:
+ using Element = T;
+ using Data_ = typename internal::ArrayDataViewImpl<T>::Data_;
+
+ ArrayDataView() : internal::ArrayDataViewImpl<T>(nullptr, nullptr) {}
+
+ ArrayDataView(Data_* data, internal::SerializationContext* context)
+ : internal::ArrayDataViewImpl<T>(data, context) {}
+
+ bool is_null() const { return !this->data_; }
+
+ size_t size() const { return this->data_->size(); }
+
+ // Methods to access elements are different for different element types. They
+ // are inherited from internal::ArrayDataViewImpl:
+
+ // POD types except boolean and enums:
+ // T operator[](size_t index) const;
+ // const T* data() const;
+
+ // Boolean:
+ // bool operator[](size_t index) const;
+
+ // Enums:
+ // T operator[](size_t index) const;
+ // const T* data() const;
+ // template <typename U>
+ // bool Read(size_t index, U* output);
+
+ // Handles:
+ // T Take(size_t index);
+
+ // Interfaces:
+ // template <typename U>
+ // U Take(size_t index);
+
+ // Object types:
+ // void GetDataView(size_t index, T* output);
+ // template <typename U>
+ // bool Read(size_t index, U* output);
+
+ private:
+ template <typename K, typename V>
+ friend class MapDataView;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_
diff --git a/mojo/public/cpp/bindings/array_traits.h b/mojo/public/cpp/bindings/array_traits.h
new file mode 100644
index 0000000000..594b2e0789
--- /dev/null
+++ b/mojo/public/cpp/bindings/array_traits.h
@@ -0,0 +1,71 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_
+
+namespace mojo {
+
+// This must be specialized for any type |T| to be serialized/deserialized as
+// a mojom array.
+//
+// Usually you would like to do a partial specialization for a container (e.g.
+// vector) template. Imagine you want to specialize it for Container<>, you need
+// to implement:
+//
+// template <typename T>
+// struct ArrayTraits<Container<T>> {
+// using Element = T;
+// // These two statements are optional. Use them if you'd like to serialize
+// // a container that supports iterators but does not support O(1) random
+// // access and so GetAt(...) would be expensive.
+// // using Iterator = T::iterator;
+// // using ConstIterator = T::const_iterator;
+//
+// // These two methods are optional. Please see comments in struct_traits.h
+// static bool IsNull(const Container<T>& input);
+// static void SetToNull(Container<T>* output);
+//
+// static size_t GetSize(const Container<T>& input);
+//
+// // These two methods are optional. They are used to access the
+// // underlying storage of the array to speed up copy of POD types.
+// static T* GetData(Container<T>& input);
+// static const T* GetData(const Container<T>& input);
+//
+// // The following six methods are optional if the GetAt(...) methods are
+// // implemented. These methods specify how to read the elements of
+// // Container in some sequential order specified by the iterator.
+// //
+// // Acquires an iterator positioned at the first element in the container.
+// static ConstIterator GetBegin(const Container<T>& input);
+// static Iterator GetBegin(Container<T>& input);
+//
+// // Advances |iterator| to the next position within the container.
+// static void AdvanceIterator(ConstIterator& iterator);
+// static void AdvanceIterator(Iterator& iterator);
+//
+// // Returns a reference to the value at the current position of
+// // |iterator|. Optionally, the ConstIterator version of GetValue can
+// // return by value instead of by reference if it makes sense for the
+// // type.
+// static const T& GetValue(ConstIterator& iterator);
+// static T& GetValue(Iterator& iterator);
+//
+// // These two methods are optional if the iterator methods are
+// // implemented.
+// static T& GetAt(Container<T>& input, size_t index);
+// static const T& GetAt(const Container<T>& input, size_t index);
+//
+// // Returning false results in deserialization failure and causes the
+// // message pipe receiving it to be disconnected.
+// static bool Resize(Container<T>& input, size_t size);
+// };
+//
+template <typename T>
+struct ArrayTraits;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/array_traits_carray.h b/mojo/public/cpp/bindings/array_traits_carray.h
new file mode 100644
index 0000000000..3ff694b882
--- /dev/null
+++ b/mojo/public/cpp/bindings/array_traits_carray.h
@@ -0,0 +1,76 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_
+
+#include "mojo/public/cpp/bindings/array_traits.h"
+
+namespace mojo {
+
+template <typename T>
+struct CArray {
+ CArray() : size(0), max_size(0), data(nullptr) {}
+ CArray(size_t size, size_t max_size, T* data)
+ : size(size), max_size(max_size), data(data) {}
+ size_t size;
+ const size_t max_size;
+ T* data;
+};
+
+template <typename T>
+struct ConstCArray {
+ ConstCArray() : size(0), data(nullptr) {}
+ ConstCArray(size_t size, const T* data) : size(size), data(data) {}
+ size_t size;
+ const T* data;
+};
+
+template <typename T>
+struct ArrayTraits<CArray<T>> {
+ using Element = T;
+
+ static bool IsNull(const CArray<T>& input) { return !input.data; }
+
+ static void SetToNull(CArray<T>* output) { output->data = nullptr; }
+
+ static size_t GetSize(const CArray<T>& input) { return input.size; }
+
+ static T* GetData(CArray<T>& input) { return input.data; }
+
+ static const T* GetData(const CArray<T>& input) { return input.data; }
+
+ static T& GetAt(CArray<T>& input, size_t index) { return input.data[index]; }
+
+ static const T& GetAt(const CArray<T>& input, size_t index) {
+ return input.data[index];
+ }
+
+ static bool Resize(CArray<T>& input, size_t size) {
+ if (size > input.max_size)
+ return false;
+
+ input.size = size;
+ return true;
+ }
+};
+
+template <typename T>
+struct ArrayTraits<ConstCArray<T>> {
+ using Element = T;
+
+ static bool IsNull(const ConstCArray<T>& input) { return !input.data; }
+
+ static size_t GetSize(const ConstCArray<T>& input) { return input.size; }
+
+ static const T* GetData(const ConstCArray<T>& input) { return input.data; }
+
+ static const T& GetAt(const ConstCArray<T>& input, size_t index) {
+ return input.data[index];
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_
diff --git a/mojo/public/cpp/bindings/array_traits_stl.h b/mojo/public/cpp/bindings/array_traits_stl.h
new file mode 100644
index 0000000000..dec47bfe7d
--- /dev/null
+++ b/mojo/public/cpp/bindings/array_traits_stl.h
@@ -0,0 +1,127 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/array_traits.h"
+
+namespace mojo {
+
+template <typename T>
+struct ArrayTraits<std::vector<T>> {
+ using Element = T;
+
+ static bool IsNull(const std::vector<T>& input) {
+ // std::vector<> is always converted to non-null mojom array.
+ return false;
+ }
+
+ static void SetToNull(std::vector<T>* output) {
+ // std::vector<> doesn't support null state. Set it to empty instead.
+ output->clear();
+ }
+
+ static size_t GetSize(const std::vector<T>& input) { return input.size(); }
+
+ static T* GetData(std::vector<T>& input) { return input.data(); }
+
+ static const T* GetData(const std::vector<T>& input) { return input.data(); }
+
+ static typename std::vector<T>::reference GetAt(std::vector<T>& input,
+ size_t index) {
+ return input[index];
+ }
+
+ static typename std::vector<T>::const_reference GetAt(
+ const std::vector<T>& input,
+ size_t index) {
+ return input[index];
+ }
+
+ static inline bool Resize(std::vector<T>& input, size_t size) {
+ // Instead of calling std::vector<T>::resize() directly, this is a hack to
+ // make compilers happy. Some compilers (e.g., Mac, Android, Linux MSan)
+ // currently don't allow resizing types like
+ // std::vector<std::vector<MoveOnlyType>>.
+ // Because the deserialization code doesn't care about the original contents
+ // of |input|, we discard them directly.
+ //
+ // The "inline" keyword of this method matters. Without it, we have observed
+ // significant perf regression with some tests on Mac. crbug.com/631415
+ if (input.size() != size) {
+ std::vector<T> temp(size);
+ input.swap(temp);
+ }
+
+ return true;
+ }
+};
+
+// This ArrayTraits specialization is used only for serialization.
+template <typename T>
+struct ArrayTraits<std::set<T>> {
+ using Element = T;
+ using ConstIterator = typename std::set<T>::const_iterator;
+
+ static bool IsNull(const std::set<T>& input) {
+ // std::set<> is always converted to non-null mojom array.
+ return false;
+ }
+
+ static size_t GetSize(const std::set<T>& input) { return input.size(); }
+
+ static ConstIterator GetBegin(const std::set<T>& input) {
+ return input.begin();
+ }
+ static void AdvanceIterator(ConstIterator& iterator) {
+ ++iterator;
+ }
+ static const T& GetValue(ConstIterator& iterator) {
+ return *iterator;
+ }
+};
+
+template <typename K, typename V>
+struct MapValuesArrayView {
+ explicit MapValuesArrayView(const std::map<K, V>& map) : map(map) {}
+ const std::map<K, V>& map;
+};
+
+// Convenience function to create a MapValuesArrayView<> that infers the
+// template arguments from its argument type.
+template <typename K, typename V>
+MapValuesArrayView<K, V> MapValuesToArray(const std::map<K, V>& map) {
+ return MapValuesArrayView<K, V>(map);
+}
+
+// This ArrayTraits specialization is used only for serialization and converts
+// a map<K, V> into an array<V>, discarding the keys.
+template <typename K, typename V>
+struct ArrayTraits<MapValuesArrayView<K, V>> {
+ using Element = V;
+ using ConstIterator = typename std::map<K, V>::const_iterator;
+
+ static bool IsNull(const MapValuesArrayView<K, V>& input) {
+ // std::map<> is always converted to non-null mojom array.
+ return false;
+ }
+
+ static size_t GetSize(const MapValuesArrayView<K, V>& input) {
+ return input.map.size();
+ }
+ static ConstIterator GetBegin(const MapValuesArrayView<K, V>& input) {
+ return input.map.begin();
+ }
+ static void AdvanceIterator(ConstIterator& iterator) { ++iterator; }
+ static const V& GetValue(ConstIterator& iterator) { return iterator->second; }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_
diff --git a/mojo/public/cpp/bindings/array_traits_wtf_vector.h b/mojo/public/cpp/bindings/array_traits_wtf_vector.h
new file mode 100644
index 0000000000..6e207351fd
--- /dev/null
+++ b/mojo/public/cpp/bindings/array_traits_wtf_vector.h
@@ -0,0 +1,47 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_
+
+#include "mojo/public/cpp/bindings/array_traits.h"
+#include "third_party/WebKit/Source/wtf/Vector.h"
+
+namespace mojo {
+
+template <typename U>
+struct ArrayTraits<WTF::Vector<U>> {
+ using Element = U;
+
+ static bool IsNull(const WTF::Vector<U>& input) {
+ // WTF::Vector<> is always converted to non-null mojom array.
+ return false;
+ }
+
+ static void SetToNull(WTF::Vector<U>* output) {
+ // WTF::Vector<> doesn't support null state. Set it to empty instead.
+ output->clear();
+ }
+
+ static size_t GetSize(const WTF::Vector<U>& input) { return input.size(); }
+
+ static U* GetData(WTF::Vector<U>& input) { return input.data(); }
+
+ static const U* GetData(const WTF::Vector<U>& input) { return input.data(); }
+
+ static U& GetAt(WTF::Vector<U>& input, size_t index) { return input[index]; }
+
+ static const U& GetAt(const WTF::Vector<U>& input, size_t index) {
+ return input[index];
+ }
+
+ static bool Resize(WTF::Vector<U>& input, size_t size) {
+ input.resize(size);
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_
diff --git a/mojo/public/cpp/bindings/associated_binding.h b/mojo/public/cpp/bindings/associated_binding.h
new file mode 100644
index 0000000000..59411666f5
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_binding.h
@@ -0,0 +1,171 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+
+class MessageReceiver;
+
+// Base class used to factor out code in AssociatedBinding<T> expansions, in
+// particular for Bind().
+class MOJO_CPP_BINDINGS_EXPORT AssociatedBindingBase {
+ public:
+ AssociatedBindingBase();
+ ~AssociatedBindingBase();
+
+ // Adds a message filter to be notified of each incoming message before
+ // dispatch. If a filter returns |false| from Accept(), the message is not
+ // dispatched and the pipe is closed. Filters cannot be removed.
+ void AddFilter(std::unique_ptr<MessageReceiver> filter);
+
+ // Closes the associated interface. Puts this object into a state where it can
+ // be rebound.
+ void Close();
+
+ // Similar to the method above, but also specifies a disconnect reason.
+ void CloseWithReason(uint32_t custom_reason, const std::string& description);
+
+ // Sets an error handler that will be called if a connection error occurs.
+ //
+ // This method may only be called after this AssociatedBinding has been bound
+ // to a message pipe. The error handler will be reset when this
+ // AssociatedBinding is unbound or closed.
+ void set_connection_error_handler(const base::Closure& error_handler);
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler);
+
+ // Indicates whether the associated binding has been completed.
+ bool is_bound() const { return !!endpoint_client_; }
+
+ // Sends a message on the underlying message pipe and runs the current
+ // message loop until its response is received. This can be used in tests to
+ // verify that no message was sent on a message pipe in response to some
+ // stimulus.
+ void FlushForTesting();
+
+ protected:
+ void BindImpl(ScopedInterfaceEndpointHandle handle,
+ MessageReceiverWithResponderStatus* receiver,
+ std::unique_ptr<MessageReceiver> payload_validator,
+ bool expect_sync_requests,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ uint32_t interface_version);
+
+ std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+};
+
+// Represents the implementation side of an associated interface. It is similar
+// to Binding, except that it doesn't own a message pipe handle.
+//
+// When you bind this class to a request, optionally you can specify a
+// base::SingleThreadTaskRunner. This task runner must belong to the same
+// thread. It will be used to dispatch incoming method calls and connection
+// error notification. It is useful when you attach multiple task runners to a
+// single thread for the purposes of task scheduling. Please note that incoming
+// synchrounous method calls may not be run from this task runner, when they
+// reenter outgoing synchrounous calls on the same thread.
+template <typename Interface,
+ typename ImplRefTraits = RawPtrImplRefTraits<Interface>>
+class AssociatedBinding : public AssociatedBindingBase {
+ public:
+ using ImplPointerType = typename ImplRefTraits::PointerType;
+
+ // Constructs an incomplete associated binding that will use the
+ // implementation |impl|. It may be completed with a subsequent call to the
+ // |Bind| method. Does not take ownership of |impl|, which must outlive this
+ // object.
+ explicit AssociatedBinding(ImplPointerType impl) { stub_.set_sink(impl); }
+
+ // Constructs a completed associated binding of |impl|. The output |ptr_info|
+ // should be sent by another interface. |impl| must outlive this object.
+ AssociatedBinding(ImplPointerType impl,
+ AssociatedInterfacePtrInfo<Interface>* ptr_info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get())
+ : AssociatedBinding(std::move(impl)) {
+ Bind(ptr_info, std::move(runner));
+ }
+
+ // Constructs a completed associated binding of |impl|. |impl| must outlive
+ // the binding.
+ AssociatedBinding(ImplPointerType impl,
+ AssociatedInterfaceRequest<Interface> request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get())
+ : AssociatedBinding(std::move(impl)) {
+ Bind(std::move(request), std::move(runner));
+ }
+
+ ~AssociatedBinding() {}
+
+ // Creates an associated inteface and sets up this object as the
+ // implementation side. The output |ptr_info| should be sent by another
+ // interface.
+ void Bind(AssociatedInterfacePtrInfo<Interface>* ptr_info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ auto request = MakeRequest(ptr_info);
+ ptr_info->set_version(Interface::Version_);
+ Bind(std::move(request), std::move(runner));
+ }
+
+ // Sets up this object as the implementation side of an associated interface.
+ void Bind(AssociatedInterfaceRequest<Interface> request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ BindImpl(request.PassHandle(), &stub_,
+ base::WrapUnique(new typename Interface::RequestValidator_()),
+ Interface::HasSyncMethods_, std::move(runner),
+ Interface::Version_);
+ }
+
+ // Unbinds and returns the associated interface request so it can be
+ // used in another context, such as on another thread or with a different
+ // implementation. Puts this object into a state where it can be rebound.
+ AssociatedInterfaceRequest<Interface> Unbind() {
+ DCHECK(endpoint_client_);
+
+ AssociatedInterfaceRequest<Interface> request;
+ request.Bind(endpoint_client_->PassHandle());
+
+ endpoint_client_.reset();
+
+ return request;
+ }
+
+ // Returns the interface implementation that was previously specified.
+ Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); }
+
+ private:
+ typename Interface::template Stub_<ImplRefTraits> stub_;
+
+ DISALLOW_COPY_AND_ASSIGN(AssociatedBinding);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_
diff --git a/mojo/public/cpp/bindings/associated_binding_set.h b/mojo/public/cpp/bindings/associated_binding_set.h
new file mode 100644
index 0000000000..59600c64f3
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_binding_set.h
@@ -0,0 +1,29 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_
+
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+
+namespace mojo {
+
+template <typename Interface, typename ImplRefTraits>
+struct BindingSetTraits<AssociatedBinding<Interface, ImplRefTraits>> {
+ using ProxyType = AssociatedInterfacePtr<Interface>;
+ using RequestType = AssociatedInterfaceRequest<Interface>;
+ using BindingType = AssociatedBinding<Interface, ImplRefTraits>;
+ using ImplPointerType = typename BindingType::ImplPointerType;
+};
+
+template <typename Interface, typename ContextType = void>
+using AssociatedBindingSet =
+ BindingSetBase<Interface, AssociatedBinding<Interface>, ContextType>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_
diff --git a/mojo/public/cpp/bindings/associated_group.h b/mojo/public/cpp/bindings/associated_group.h
new file mode 100644
index 0000000000..14e78ec3f9
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_group.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+
+class AssociatedGroupController;
+
+// AssociatedGroup refers to all the interface endpoints running at one end of a
+// message pipe.
+// It is thread safe and cheap to make copies.
+class MOJO_CPP_BINDINGS_EXPORT AssociatedGroup {
+ public:
+ AssociatedGroup();
+
+ explicit AssociatedGroup(scoped_refptr<AssociatedGroupController> controller);
+
+ explicit AssociatedGroup(const ScopedInterfaceEndpointHandle& handle);
+
+ AssociatedGroup(const AssociatedGroup& other);
+
+ ~AssociatedGroup();
+
+ AssociatedGroup& operator=(const AssociatedGroup& other);
+
+ // The return value of this getter if this object is initialized with a
+ // ScopedInterfaceEndpointHandle:
+ // - If the handle is invalid, the return value will always be null.
+ // - If the handle is valid and non-pending, the return value will be
+ // non-null and remain unchanged even if the handle is later reset.
+ // - If the handle is pending asssociation, the return value will initially
+ // be null, change to non-null when/if the handle is associated, and
+ // remain unchanged ever since.
+ AssociatedGroupController* GetController();
+
+ private:
+ base::Callback<AssociatedGroupController*()> controller_getter_;
+ scoped_refptr<AssociatedGroupController> controller_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_
diff --git a/mojo/public/cpp/bindings/associated_group_controller.h b/mojo/public/cpp/bindings/associated_group_controller.h
new file mode 100644
index 0000000000..d33c2776d5
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_group_controller.h
@@ -0,0 +1,93 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/disconnect_reason.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+
+class InterfaceEndpointClient;
+class InterfaceEndpointController;
+
+// An internal interface used to manage endpoints within an associated group,
+// which corresponds to one end of a message pipe.
+class MOJO_CPP_BINDINGS_EXPORT AssociatedGroupController
+ : public base::RefCountedThreadSafe<AssociatedGroupController> {
+ public:
+ // Associates an interface with this AssociatedGroupController's message pipe.
+ // It takes ownership of |handle_to_send| and returns an interface ID that
+ // could be sent by any endpoints within the same associated group.
+ // If |handle_to_send| is not in pending association state, it returns
+ // kInvalidInterfaceId. Otherwise, the peer handle of |handle_to_send| joins
+ // the associated group and is no longer pending.
+ virtual InterfaceId AssociateInterface(
+ ScopedInterfaceEndpointHandle handle_to_send) = 0;
+
+ // Creates an interface endpoint handle from a given interface ID. The handle
+ // joins this associated group.
+ // Typically, this method is used to (1) create an endpoint handle for the
+ // master interface; or (2) create an endpoint handle on receiving an
+ // interface ID from the message pipe.
+ //
+ // On failure, the method returns an invalid handle. Usually that is because
+ // the ID has already been used to create a handle.
+ virtual ScopedInterfaceEndpointHandle CreateLocalEndpointHandle(
+ InterfaceId id) = 0;
+
+ // Closes an interface endpoint handle.
+ virtual void CloseEndpointHandle(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) = 0;
+
+ // Attaches a client to the specified endpoint to send and receive messages.
+ // The returned object is still owned by the controller. It must only be used
+ // on the same thread as this call, and only before the client is detached
+ // using DetachEndpointClient().
+ virtual InterfaceEndpointController* AttachEndpointClient(
+ const ScopedInterfaceEndpointHandle& handle,
+ InterfaceEndpointClient* endpoint_client,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) = 0;
+
+ // Detaches the client attached to the specified endpoint. It must be called
+ // on the same thread as the corresponding AttachEndpointClient() call.
+ virtual void DetachEndpointClient(
+ const ScopedInterfaceEndpointHandle& handle) = 0;
+
+ // Raises an error on the underlying message pipe. It disconnects the pipe
+ // and notifies all interfaces running on this pipe.
+ virtual void RaiseError() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<AssociatedGroupController>;
+
+ // Creates a new ScopedInterfaceEndpointHandle within this associated group.
+ ScopedInterfaceEndpointHandle CreateScopedInterfaceEndpointHandle(
+ InterfaceId id);
+
+ // Notifies that the interface represented by |handle_to_send| and its peer
+ // has been associated with this AssociatedGroupController's message pipe, and
+ // |handle_to_send|'s peer has joined this associated group. (Note: it is the
+ // peer who has joined the associated group; |handle_to_send| will be sent to
+ // the remote side.)
+ // Returns false if |handle_to_send|'s peer has closed.
+ bool NotifyAssociation(ScopedInterfaceEndpointHandle* handle_to_send,
+ InterfaceId id);
+
+ virtual ~AssociatedGroupController();
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_
diff --git a/mojo/public/cpp/bindings/associated_interface_ptr.h b/mojo/public/cpp/bindings/associated_interface_ptr.h
new file mode 100644
index 0000000000..8806a3e090
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_interface_ptr.h
@@ -0,0 +1,283 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h"
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// Represents the client side of an associated interface. It is similar to
+// InterfacePtr, except that it doesn't own a message pipe handle.
+template <typename Interface>
+class AssociatedInterfacePtr {
+ public:
+ using InterfaceType = Interface;
+ using PtrInfoType = AssociatedInterfacePtrInfo<Interface>;
+
+ // Constructs an unbound AssociatedInterfacePtr.
+ AssociatedInterfacePtr() {}
+ AssociatedInterfacePtr(decltype(nullptr)) {}
+
+ AssociatedInterfacePtr(AssociatedInterfacePtr&& other) {
+ internal_state_.Swap(&other.internal_state_);
+ }
+
+ AssociatedInterfacePtr& operator=(AssociatedInterfacePtr&& other) {
+ reset();
+ internal_state_.Swap(&other.internal_state_);
+ return *this;
+ }
+
+ // Assigning nullptr to this class causes it to closes the associated
+ // interface (if any) and returns the pointer to the unbound state.
+ AssociatedInterfacePtr& operator=(decltype(nullptr)) {
+ reset();
+ return *this;
+ }
+
+ ~AssociatedInterfacePtr() {}
+
+ // Sets up this object as the client side of an associated interface.
+ // Calling with an invalid |info| has the same effect as reset(). In this
+ // case, the AssociatedInterfacePtr is not considered as bound.
+ //
+ // |runner| must belong to the same thread. It will be used to dispatch all
+ // callbacks and connection error notification. It is useful when you attach
+ // multiple task runners to a single thread for the purposes of task
+ // scheduling.
+ //
+ // NOTE: The corresponding AssociatedInterfaceRequest must be sent over
+ // another interface before using this object to make calls. Please see the
+ // comments of MakeRequest(AssociatedInterfacePtr<Interface>*) for more
+ // details.
+ void Bind(AssociatedInterfacePtrInfo<Interface> info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ reset();
+
+ if (info.is_valid())
+ internal_state_.Bind(std::move(info), std::move(runner));
+ }
+
+ bool is_bound() const { return internal_state_.is_bound(); }
+
+ Interface* get() const { return internal_state_.instance(); }
+
+ // Functions like a pointer to Interface. Must already be bound.
+ Interface* operator->() const { return get(); }
+ Interface& operator*() const { return *get(); }
+
+ // Returns the version number of the interface that the remote side supports.
+ uint32_t version() const { return internal_state_.version(); }
+
+ // Queries the max version that the remote side supports. On completion, the
+ // result will be returned as the input of |callback|. The version number of
+ // this object will also be updated.
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback) {
+ internal_state_.QueryVersion(callback);
+ }
+
+ // If the remote side doesn't support the specified version, it will close the
+ // associated interface asynchronously. This does nothing if it's already
+ // known that the remote side supports the specified version, i.e., if
+ // |version <= this->version()|.
+ //
+ // After calling RequireVersion() with a version not supported by the remote
+ // side, all subsequent calls to interface methods will be ignored.
+ void RequireVersion(uint32_t version) {
+ internal_state_.RequireVersion(version);
+ }
+
+ // Sends a message on the underlying message pipe and runs the current
+ // message loop until its response is received. This can be used in tests to
+ // verify that no message was sent on a message pipe in response to some
+ // stimulus.
+ void FlushForTesting() { internal_state_.FlushForTesting(); }
+
+ // Closes the associated interface (if any) and returns the pointer to the
+ // unbound state.
+ void reset() {
+ State doomed;
+ internal_state_.Swap(&doomed);
+ }
+
+ // Similar to the method above, but also specifies a disconnect reason.
+ void ResetWithReason(uint32_t custom_reason, const std::string& description) {
+ if (internal_state_.is_bound())
+ internal_state_.CloseWithReason(custom_reason, description);
+ reset();
+ }
+
+ // Indicates whether an error has been encountered. If true, method calls made
+ // on this interface will be dropped (and may already have been dropped).
+ bool encountered_error() const { return internal_state_.encountered_error(); }
+
+ // Registers a handler to receive error notifications.
+ //
+ // This method may only be called after the AssociatedInterfacePtr has been
+ // bound.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ internal_state_.set_connection_error_handler(error_handler);
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ internal_state_.set_connection_error_with_reason_handler(error_handler);
+ }
+
+ // Unbinds and returns the associated interface pointer information which
+ // could be used to setup an AssociatedInterfacePtr again. This method may be
+ // used to move the proxy to a different thread.
+ //
+ // It is an error to call PassInterface() while there are pending responses.
+ // TODO: fix this restriction, it's not always obvious when there is a
+ // pending response.
+ AssociatedInterfacePtrInfo<Interface> PassInterface() {
+ DCHECK(!internal_state_.has_pending_callbacks());
+ State state;
+ internal_state_.Swap(&state);
+
+ return state.PassInterface();
+ }
+
+ // DO NOT USE. Exposed only for internal use and for testing.
+ internal::AssociatedInterfacePtrState<Interface>* internal_state() {
+ return &internal_state_;
+ }
+
+ // Allow AssociatedInterfacePtr<> to be used in boolean expressions, but not
+ // implicitly convertible to a real bool (which is dangerous).
+ private:
+ // TODO(dcheng): Use an explicit conversion operator.
+ typedef internal::AssociatedInterfacePtrState<Interface>
+ AssociatedInterfacePtr::*Testable;
+
+ public:
+ operator Testable() const {
+ return internal_state_.is_bound() ? &AssociatedInterfacePtr::internal_state_
+ : nullptr;
+ }
+
+ private:
+ // Forbid the == and != operators explicitly, otherwise AssociatedInterfacePtr
+ // will be converted to Testable to do == or != comparison.
+ template <typename T>
+ bool operator==(const AssociatedInterfacePtr<T>& other) const = delete;
+ template <typename T>
+ bool operator!=(const AssociatedInterfacePtr<T>& other) const = delete;
+
+ typedef internal::AssociatedInterfacePtrState<Interface> State;
+ mutable State internal_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtr);
+};
+
+// Creates an associated interface. The returned request is supposed to be sent
+// over another interface (either associated or non-associated).
+//
+// NOTE: |ptr| must NOT be used to make calls before the request is sent.
+// Violating that will lead to crash. On the other hand, as soon as the request
+// is sent, |ptr| is usable. There is no need to wait until the request is bound
+// to an implementation at the remote side.
+template <typename Interface>
+AssociatedInterfaceRequest<Interface> MakeRequest(
+ AssociatedInterfacePtr<Interface>* ptr,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ AssociatedInterfacePtrInfo<Interface> ptr_info;
+ auto request = MakeRequest(&ptr_info);
+ ptr->Bind(std::move(ptr_info), std::move(runner));
+ return request;
+}
+
+// Creates an associated interface. One of the two endpoints is supposed to be
+// sent over another interface (either associated or non-associated); while the
+// other is used locally.
+//
+// NOTE: If |ptr_info| is used locally and bound to an AssociatedInterfacePtr,
+// the interface pointer must NOT be used to make calls before the request is
+// sent. Please see NOTE of the previous function for more details.
+template <typename Interface>
+AssociatedInterfaceRequest<Interface> MakeRequest(
+ AssociatedInterfacePtrInfo<Interface>* ptr_info) {
+ ScopedInterfaceEndpointHandle handle0;
+ ScopedInterfaceEndpointHandle handle1;
+ ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&handle0,
+ &handle1);
+
+ ptr_info->set_handle(std::move(handle0));
+ ptr_info->set_version(0);
+
+ AssociatedInterfaceRequest<Interface> request;
+ request.Bind(std::move(handle1));
+ return request;
+}
+
+// Like MakeRequest() above, but it creates a dedicated message pipe. The
+// returned request can be bound directly to an implementation, without being
+// first passed through a message pipe endpoint.
+//
+// This function has two main uses:
+//
+// * In testing, where the returned request is bound to e.g. a mock and there
+// are no other interfaces involved.
+//
+// * When discarding messages sent on an interface, which can be done by
+// discarding the returned request.
+template <typename Interface>
+AssociatedInterfaceRequest<Interface> MakeIsolatedRequest(
+ AssociatedInterfacePtr<Interface>* ptr) {
+ MessagePipe pipe;
+ scoped_refptr<internal::MultiplexRouter> router0 =
+ new internal::MultiplexRouter(std::move(pipe.handle0),
+ internal::MultiplexRouter::MULTI_INTERFACE,
+ false, base::ThreadTaskRunnerHandle::Get());
+ scoped_refptr<internal::MultiplexRouter> router1 =
+ new internal::MultiplexRouter(std::move(pipe.handle1),
+ internal::MultiplexRouter::MULTI_INTERFACE,
+ true, base::ThreadTaskRunnerHandle::Get());
+
+ ScopedInterfaceEndpointHandle endpoint0, endpoint1;
+ ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0,
+ &endpoint1);
+ InterfaceId id = router1->AssociateInterface(std::move(endpoint0));
+ endpoint0 = router0->CreateLocalEndpointHandle(id);
+
+ ptr->Bind(AssociatedInterfacePtrInfo<Interface>(std::move(endpoint0),
+ Interface::Version_));
+
+ AssociatedInterfaceRequest<Interface> request;
+ request.Bind(std::move(endpoint1));
+ return request;
+}
+
+// |handle| is supposed to be the request of an associated interface. This
+// method associates the interface with a dedicated, disconnected message pipe.
+// That way, the corresponding associated interface pointer of |handle| can
+// safely make calls (although those calls are silently dropped).
+MOJO_CPP_BINDINGS_EXPORT void GetIsolatedInterface(
+ ScopedInterfaceEndpointHandle handle);
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_
diff --git a/mojo/public/cpp/bindings/associated_interface_ptr_info.h b/mojo/public/cpp/bindings/associated_interface_ptr_info.h
new file mode 100644
index 0000000000..3c6ca54603
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_interface_ptr_info.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 MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+
+// AssociatedInterfacePtrInfo stores necessary information to construct an
+// associated interface pointer. It is similar to InterfacePtrInfo except that
+// it doesn't own a message pipe handle.
+template <typename Interface>
+class AssociatedInterfacePtrInfo {
+ public:
+ AssociatedInterfacePtrInfo() : version_(0u) {}
+ AssociatedInterfacePtrInfo(std::nullptr_t) : version_(0u) {}
+
+ AssociatedInterfacePtrInfo(AssociatedInterfacePtrInfo&& other)
+ : handle_(std::move(other.handle_)), version_(other.version_) {
+ other.version_ = 0u;
+ }
+
+ AssociatedInterfacePtrInfo(ScopedInterfaceEndpointHandle handle,
+ uint32_t version)
+ : handle_(std::move(handle)), version_(version) {}
+
+ ~AssociatedInterfacePtrInfo() {}
+
+ AssociatedInterfacePtrInfo& operator=(AssociatedInterfacePtrInfo&& other) {
+ if (this != &other) {
+ handle_ = std::move(other.handle_);
+ version_ = other.version_;
+ other.version_ = 0u;
+ }
+
+ return *this;
+ }
+
+ bool is_valid() const { return handle_.is_valid(); }
+
+ ScopedInterfaceEndpointHandle PassHandle() {
+ return std::move(handle_);
+ }
+ const ScopedInterfaceEndpointHandle& handle() const { return handle_; }
+ void set_handle(ScopedInterfaceEndpointHandle handle) {
+ handle_ = std::move(handle);
+ }
+
+ uint32_t version() const { return version_; }
+ void set_version(uint32_t version) { version_ = version; }
+
+ bool Equals(const AssociatedInterfacePtrInfo& other) const {
+ if (this == &other)
+ return true;
+
+ // Now that the two refer to different objects, they are equivalent if
+ // and only if they are both invalid.
+ return !is_valid() && !other.is_valid();
+ }
+
+ private:
+ ScopedInterfaceEndpointHandle handle_;
+ uint32_t version_;
+
+ DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrInfo);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_
diff --git a/mojo/public/cpp/bindings/associated_interface_request.h b/mojo/public/cpp/bindings/associated_interface_request.h
new file mode 100644
index 0000000000..c37636c9f3
--- /dev/null
+++ b/mojo/public/cpp/bindings/associated_interface_request.h
@@ -0,0 +1,90 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+
+// AssociatedInterfaceRequest represents an associated interface request. It is
+// similar to InterfaceRequest except that it doesn't own a message pipe handle.
+template <typename Interface>
+class AssociatedInterfaceRequest {
+ public:
+ // Constructs an empty AssociatedInterfaceRequest, representing that the
+ // client is not requesting an implementation of Interface.
+ AssociatedInterfaceRequest() {}
+ AssociatedInterfaceRequest(decltype(nullptr)) {}
+
+ // Takes the interface endpoint handle from another
+ // AssociatedInterfaceRequest.
+ AssociatedInterfaceRequest(AssociatedInterfaceRequest&& other) {
+ handle_ = std::move(other.handle_);
+ }
+ AssociatedInterfaceRequest& operator=(AssociatedInterfaceRequest&& other) {
+ if (this != &other)
+ handle_ = std::move(other.handle_);
+ return *this;
+ }
+
+ // Assigning to nullptr resets the AssociatedInterfaceRequest to an empty
+ // state, closing the interface endpoint handle currently bound to it (if
+ // any).
+ AssociatedInterfaceRequest& operator=(decltype(nullptr)) {
+ handle_.reset();
+ return *this;
+ }
+
+ // Indicates whether the request currently contains a valid interface endpoint
+ // handle.
+ bool is_pending() const { return handle_.is_valid(); }
+
+ void Bind(ScopedInterfaceEndpointHandle handle) {
+ handle_ = std::move(handle);
+ }
+
+ ScopedInterfaceEndpointHandle PassHandle() {
+ return std::move(handle_);
+ }
+
+ const ScopedInterfaceEndpointHandle& handle() const { return handle_; }
+
+ bool Equals(const AssociatedInterfaceRequest& other) const {
+ if (this == &other)
+ return true;
+
+ // Now that the two refer to different objects, they are equivalent if
+ // and only if they are both invalid.
+ return !is_pending() && !other.is_pending();
+ }
+
+ void ResetWithReason(uint32_t custom_reason, const std::string& description) {
+ handle_.ResetWithReason(custom_reason, description);
+ }
+
+ private:
+ ScopedInterfaceEndpointHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(AssociatedInterfaceRequest);
+};
+
+// Makes an AssociatedInterfaceRequest bound to the specified associated
+// endpoint.
+template <typename Interface>
+AssociatedInterfaceRequest<Interface> MakeAssociatedRequest(
+ ScopedInterfaceEndpointHandle handle) {
+ AssociatedInterfaceRequest<Interface> request;
+ request.Bind(std::move(handle));
+ return request;
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_
diff --git a/mojo/public/cpp/bindings/binding.h b/mojo/public/cpp/bindings/binding.h
new file mode 100644
index 0000000000..88d2f4ba3e
--- /dev/null
+++ b/mojo/public/cpp/bindings/binding.h
@@ -0,0 +1,276 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_
+
+#include <string>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/lib/binding_state.h"
+#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+class MessageReceiver;
+
+// Represents the binding of an interface implementation to a message pipe.
+// When the |Binding| object is destroyed, the binding between the message pipe
+// and the interface is torn down and the message pipe is closed, leaving the
+// interface implementation in an unbound state.
+//
+// Example:
+//
+// #include "foo.mojom.h"
+//
+// class FooImpl : public Foo {
+// public:
+// explicit FooImpl(InterfaceRequest<Foo> request)
+// : binding_(this, std::move(request)) {}
+//
+// // Foo implementation here.
+//
+// private:
+// Binding<Foo> binding_;
+// };
+//
+// class MyFooFactory : public InterfaceFactory<Foo> {
+// public:
+// void Create(..., InterfaceRequest<Foo> request) override {
+// auto f = new FooImpl(std::move(request));
+// // Do something to manage the lifetime of |f|. Use StrongBinding<> to
+// // delete FooImpl on connection errors.
+// }
+// };
+//
+// This class is thread hostile while bound to a message pipe. All calls to this
+// class must be from the thread that bound it. The interface implementation's
+// methods will be called from the thread that bound this. If a Binding is not
+// bound to a message pipe, it may be bound or destroyed on any thread.
+//
+// When you bind this class to a message pipe, optionally you can specify a
+// base::SingleThreadTaskRunner. This task runner must belong to the same
+// thread. It will be used to dispatch incoming method calls and connection
+// error notification. It is useful when you attach multiple task runners to a
+// single thread for the purposes of task scheduling. Please note that incoming
+// synchrounous method calls may not be run from this task runner, when they
+// reenter outgoing synchrounous calls on the same thread.
+template <typename Interface,
+ typename ImplRefTraits = RawPtrImplRefTraits<Interface>>
+class Binding {
+ public:
+ using ImplPointerType = typename ImplRefTraits::PointerType;
+
+ // Constructs an incomplete binding that will use the implementation |impl|.
+ // The binding may be completed with a subsequent call to the |Bind| method.
+ // Does not take ownership of |impl|, which must outlive the binding.
+ explicit Binding(ImplPointerType impl) : internal_state_(std::move(impl)) {}
+
+ // Constructs a completed binding of message pipe |handle| to implementation
+ // |impl|. Does not take ownership of |impl|, which must outlive the binding.
+ Binding(ImplPointerType impl,
+ ScopedMessagePipeHandle handle,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get())
+ : Binding(std::move(impl)) {
+ Bind(std::move(handle), std::move(runner));
+ }
+
+ // Constructs a completed binding of |impl| to a new message pipe, passing the
+ // client end to |ptr|, which takes ownership of it. The caller is expected to
+ // pass |ptr| on to the client of the service. Does not take ownership of any
+ // of the parameters. |impl| must outlive the binding. |ptr| only needs to
+ // last until the constructor returns.
+ Binding(ImplPointerType impl,
+ InterfacePtr<Interface>* ptr,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get())
+ : Binding(std::move(impl)) {
+ Bind(ptr, std::move(runner));
+ }
+
+ // Constructs a completed binding of |impl| to the message pipe endpoint in
+ // |request|, taking ownership of the endpoint. Does not take ownership of
+ // |impl|, which must outlive the binding.
+ Binding(ImplPointerType impl,
+ InterfaceRequest<Interface> request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get())
+ : Binding(std::move(impl)) {
+ Bind(request.PassMessagePipe(), std::move(runner));
+ }
+
+ // Tears down the binding, closing the message pipe and leaving the interface
+ // implementation unbound.
+ ~Binding() {}
+
+ // Returns an InterfacePtr bound to one end of a pipe whose other end is
+ // bound to |this|.
+ InterfacePtr<Interface> CreateInterfacePtrAndBind(
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ InterfacePtr<Interface> interface_ptr;
+ Bind(&interface_ptr, std::move(runner));
+ return interface_ptr;
+ }
+
+ // Completes a binding that was constructed with only an interface
+ // implementation. Takes ownership of |handle| and binds it to the previously
+ // specified implementation.
+ void Bind(ScopedMessagePipeHandle handle,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ internal_state_.Bind(std::move(handle), std::move(runner));
+ }
+
+ // Completes a binding that was constructed with only an interface
+ // implementation by creating a new message pipe, binding one end of it to the
+ // previously specified implementation, and passing the other to |ptr|, which
+ // takes ownership of it. The caller is expected to pass |ptr| on to the
+ // eventual client of the service. Does not take ownership of |ptr|.
+ void Bind(InterfacePtr<Interface>* ptr,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ MessagePipe pipe;
+ ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0),
+ Interface::Version_),
+ runner);
+ Bind(std::move(pipe.handle1), std::move(runner));
+ }
+
+ // Completes a binding that was constructed with only an interface
+ // implementation by removing the message pipe endpoint from |request| and
+ // binding it to the previously specified implementation.
+ void Bind(InterfaceRequest<Interface> request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ Bind(request.PassMessagePipe(), std::move(runner));
+ }
+
+ // Adds a message filter to be notified of each incoming message before
+ // dispatch. If a filter returns |false| from Accept(), the message is not
+ // dispatched and the pipe is closed. Filters cannot be removed.
+ void AddFilter(std::unique_ptr<MessageReceiver> filter) {
+ DCHECK(is_bound());
+ internal_state_.AddFilter(std::move(filter));
+ }
+
+ // Whether there are any associated interfaces running on the pipe currently.
+ bool HasAssociatedInterfaces() const {
+ return internal_state_.HasAssociatedInterfaces();
+ }
+
+ // Stops processing incoming messages until
+ // ResumeIncomingMethodCallProcessing(), or WaitForIncomingMethodCall().
+ // Outgoing messages are still sent.
+ //
+ // No errors are detected on the message pipe while paused.
+ //
+ // This method may only be called if the object has been bound to a message
+ // pipe and there are no associated interfaces running.
+ void PauseIncomingMethodCallProcessing() {
+ CHECK(!HasAssociatedInterfaces());
+ internal_state_.PauseIncomingMethodCallProcessing();
+ }
+ void ResumeIncomingMethodCallProcessing() {
+ internal_state_.ResumeIncomingMethodCallProcessing();
+ }
+
+ // Blocks the calling thread until either a call arrives on the previously
+ // bound message pipe, the deadline is exceeded, or an error occurs. Returns
+ // true if a method was successfully read and dispatched.
+ //
+ // This method may only be called if the object has been bound to a message
+ // pipe. This returns once a message is received either on the master
+ // interface or any associated interfaces.
+ bool WaitForIncomingMethodCall(
+ MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) {
+ return internal_state_.WaitForIncomingMethodCall(deadline);
+ }
+
+ // Closes the message pipe that was previously bound. Put this object into a
+ // state where it can be rebound to a new pipe.
+ void Close() { internal_state_.Close(); }
+
+ // Similar to the method above, but also specifies a disconnect reason.
+ void CloseWithReason(uint32_t custom_reason, const std::string& description) {
+ internal_state_.CloseWithReason(custom_reason, description);
+ }
+
+ // Unbinds the underlying pipe from this binding and returns it so it can be
+ // used in another context, such as on another thread or with a different
+ // implementation. Put this object into a state where it can be rebound to a
+ // new pipe.
+ //
+ // This method may only be called if the object has been bound to a message
+ // pipe and there are no associated interfaces running.
+ //
+ // TODO(yzshen): For now, users need to make sure there is no one holding
+ // on to associated interface endpoint handles at both sides of the
+ // message pipe in order to call this method. We need a way to forcefully
+ // invalidate associated interface endpoint handles.
+ InterfaceRequest<Interface> Unbind() {
+ CHECK(!HasAssociatedInterfaces());
+ return internal_state_.Unbind();
+ }
+
+ // Sets an error handler that will be called if a connection error occurs on
+ // the bound message pipe.
+ //
+ // This method may only be called after this Binding has been bound to a
+ // message pipe. The error handler will be reset when this Binding is unbound
+ // or closed.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(is_bound());
+ internal_state_.set_connection_error_handler(error_handler);
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(is_bound());
+ internal_state_.set_connection_error_with_reason_handler(error_handler);
+ }
+
+ // Returns the interface implementation that was previously specified. Caller
+ // does not take ownership.
+ Interface* impl() { return internal_state_.impl(); }
+
+ // Indicates whether the binding has been completed (i.e., whether a message
+ // pipe has been bound to the implementation).
+ bool is_bound() const { return internal_state_.is_bound(); }
+
+ // Returns the value of the handle currently bound to this Binding which can
+ // be used to make explicit Wait/WaitMany calls. Requires that the Binding be
+ // bound. Ownership of the handle is retained by the Binding, it is not
+ // transferred to the caller.
+ MessagePipeHandle handle() const { return internal_state_.handle(); }
+
+ // Sends a no-op message on the underlying message pipe and runs the current
+ // message loop until its response is received. This can be used in tests to
+ // verify that no message was sent on a message pipe in response to some
+ // stimulus.
+ void FlushForTesting() { internal_state_.FlushForTesting(); }
+
+ // Exposed for testing, should not generally be used.
+ void EnableTestingMode() { internal_state_.EnableTestingMode(); }
+
+ private:
+ internal::BindingState<Interface, ImplRefTraits> internal_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(Binding);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_
diff --git a/mojo/public/cpp/bindings/binding_set.h b/mojo/public/cpp/bindings/binding_set.h
new file mode 100644
index 0000000000..919f9c09ad
--- /dev/null
+++ b/mojo/public/cpp/bindings/binding_set.h
@@ -0,0 +1,267 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+
+template <typename BindingType>
+struct BindingSetTraits;
+
+template <typename Interface, typename ImplRefTraits>
+struct BindingSetTraits<Binding<Interface, ImplRefTraits>> {
+ using ProxyType = InterfacePtr<Interface>;
+ using RequestType = InterfaceRequest<Interface>;
+ using BindingType = Binding<Interface, ImplRefTraits>;
+ using ImplPointerType = typename BindingType::ImplPointerType;
+
+ static RequestType MakeRequest(ProxyType* proxy) {
+ return mojo::MakeRequest(proxy);
+ }
+};
+
+using BindingId = size_t;
+
+template <typename ContextType>
+struct BindingSetContextTraits {
+ using Type = ContextType;
+
+ static constexpr bool SupportsContext() { return true; }
+};
+
+template <>
+struct BindingSetContextTraits<void> {
+ // NOTE: This choice of Type only matters insofar as it affects the size of
+ // the |context_| field of a BindingSetBase::Entry with void context. The
+ // context value is never used in this case.
+ using Type = bool;
+
+ static constexpr bool SupportsContext() { return false; }
+};
+
+// Generic definition used for BindingSet and AssociatedBindingSet to own a
+// collection of bindings which point to the same implementation.
+//
+// If |ContextType| is non-void, then every added binding must include a context
+// value of that type, and |dispatch_context()| will return that value during
+// the extent of any message dispatch targeting that specific binding.
+template <typename Interface, typename BindingType, typename ContextType>
+class BindingSetBase {
+ public:
+ using ContextTraits = BindingSetContextTraits<ContextType>;
+ using Context = typename ContextTraits::Type;
+ using PreDispatchCallback = base::Callback<void(const Context&)>;
+ using Traits = BindingSetTraits<BindingType>;
+ using ProxyType = typename Traits::ProxyType;
+ using RequestType = typename Traits::RequestType;
+ using ImplPointerType = typename Traits::ImplPointerType;
+
+ BindingSetBase() {}
+
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ error_handler_ = error_handler;
+ error_with_reason_handler_.Reset();
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ error_with_reason_handler_ = error_handler;
+ error_handler_.Reset();
+ }
+
+ // Sets a callback to be invoked immediately before dispatching any message or
+ // error received by any of the bindings in the set. This may only be used
+ // with a non-void |ContextType|.
+ void set_pre_dispatch_handler(const PreDispatchCallback& handler) {
+ static_assert(ContextTraits::SupportsContext(),
+ "Pre-dispatch handler usage requires non-void context type.");
+ pre_dispatch_handler_ = handler;
+ }
+
+ // Adds a new binding to the set which binds |request| to |impl| with no
+ // additional context.
+ BindingId AddBinding(ImplPointerType impl, RequestType request) {
+ static_assert(!ContextTraits::SupportsContext(),
+ "Context value required for non-void context type.");
+ return AddBindingImpl(std::move(impl), std::move(request), false);
+ }
+
+ // Adds a new binding associated with |context|.
+ BindingId AddBinding(ImplPointerType impl,
+ RequestType request,
+ Context context) {
+ static_assert(ContextTraits::SupportsContext(),
+ "Context value unsupported for void context type.");
+ return AddBindingImpl(std::move(impl), std::move(request),
+ std::move(context));
+ }
+
+ // Removes a binding from the set. Note that this is safe to call even if the
+ // binding corresponding to |id| has already been removed.
+ //
+ // Returns |true| if the binding was removed and |false| if it didn't exist.
+ bool RemoveBinding(BindingId id) {
+ auto it = bindings_.find(id);
+ if (it == bindings_.end())
+ return false;
+ bindings_.erase(it);
+ return true;
+ }
+
+ // Returns a proxy bound to one end of a pipe whose other end is bound to
+ // |this|. If |id_storage| is not null, |*id_storage| will be set to the ID
+ // of the added binding.
+ ProxyType CreateInterfacePtrAndBind(ImplPointerType impl,
+ BindingId* id_storage = nullptr) {
+ ProxyType proxy;
+ BindingId id = AddBinding(std::move(impl), Traits::MakeRequest(&proxy));
+ if (id_storage)
+ *id_storage = id;
+ return proxy;
+ }
+
+ void CloseAllBindings() { bindings_.clear(); }
+
+ bool empty() const { return bindings_.empty(); }
+
+ // Implementations may call this when processing a dispatched message or
+ // error. During the extent of message or error dispatch, this will return the
+ // context associated with the specific binding which received the message or
+ // error. Use AddBinding() to associated a context with a specific binding.
+ const Context& dispatch_context() const {
+ static_assert(ContextTraits::SupportsContext(),
+ "dispatch_context() requires non-void context type.");
+ DCHECK(dispatch_context_);
+ return *dispatch_context_;
+ }
+
+ void FlushForTesting() {
+ for (auto& binding : bindings_)
+ binding.second->FlushForTesting();
+ }
+
+ private:
+ friend class Entry;
+
+ class Entry {
+ public:
+ Entry(ImplPointerType impl,
+ RequestType request,
+ BindingSetBase* binding_set,
+ BindingId binding_id,
+ Context context)
+ : binding_(std::move(impl), std::move(request)),
+ binding_set_(binding_set),
+ binding_id_(binding_id),
+ context_(std::move(context)) {
+ if (ContextTraits::SupportsContext())
+ binding_.AddFilter(base::MakeUnique<DispatchFilter>(this));
+ binding_.set_connection_error_with_reason_handler(
+ base::Bind(&Entry::OnConnectionError, base::Unretained(this)));
+ }
+
+ void FlushForTesting() { binding_.FlushForTesting(); }
+
+ private:
+ class DispatchFilter : public MessageReceiver {
+ public:
+ explicit DispatchFilter(Entry* entry) : entry_(entry) {}
+ ~DispatchFilter() override {}
+
+ private:
+ // MessageReceiver:
+ bool Accept(Message* message) override {
+ entry_->WillDispatch();
+ return true;
+ }
+
+ Entry* entry_;
+
+ DISALLOW_COPY_AND_ASSIGN(DispatchFilter);
+ };
+
+ void WillDispatch() {
+ DCHECK(ContextTraits::SupportsContext());
+ binding_set_->SetDispatchContext(&context_);
+ }
+
+ void OnConnectionError(uint32_t custom_reason,
+ const std::string& description) {
+ if (ContextTraits::SupportsContext())
+ WillDispatch();
+ binding_set_->OnConnectionError(binding_id_, custom_reason, description);
+ }
+
+ BindingType binding_;
+ BindingSetBase* const binding_set_;
+ const BindingId binding_id_;
+ Context const context_;
+
+ DISALLOW_COPY_AND_ASSIGN(Entry);
+ };
+
+ void SetDispatchContext(const Context* context) {
+ DCHECK(ContextTraits::SupportsContext());
+ dispatch_context_ = context;
+ if (!pre_dispatch_handler_.is_null())
+ pre_dispatch_handler_.Run(*context);
+ }
+
+ BindingId AddBindingImpl(ImplPointerType impl,
+ RequestType request,
+ Context context) {
+ BindingId id = next_binding_id_++;
+ DCHECK_GE(next_binding_id_, 0u);
+ auto entry = base::MakeUnique<Entry>(std::move(impl), std::move(request),
+ this, id, std::move(context));
+ bindings_.insert(std::make_pair(id, std::move(entry)));
+ return id;
+ }
+
+ void OnConnectionError(BindingId id,
+ uint32_t custom_reason,
+ const std::string& description) {
+ auto it = bindings_.find(id);
+ DCHECK(it != bindings_.end());
+
+ // We keep the Entry alive throughout error dispatch.
+ std::unique_ptr<Entry> entry = std::move(it->second);
+ bindings_.erase(it);
+
+ if (!error_handler_.is_null())
+ error_handler_.Run();
+ else if (!error_with_reason_handler_.is_null())
+ error_with_reason_handler_.Run(custom_reason, description);
+ }
+
+ base::Closure error_handler_;
+ ConnectionErrorWithReasonCallback error_with_reason_handler_;
+ PreDispatchCallback pre_dispatch_handler_;
+ BindingId next_binding_id_ = 0;
+ std::map<BindingId, std::unique_ptr<Entry>> bindings_;
+ const Context* dispatch_context_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(BindingSetBase);
+};
+
+template <typename Interface, typename ContextType = void>
+using BindingSet = BindingSetBase<Interface, Binding<Interface>, ContextType>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_
diff --git a/mojo/public/cpp/bindings/bindings_export.h b/mojo/public/cpp/bindings/bindings_export.h
new file mode 100644
index 0000000000..9fd7a2784e
--- /dev/null
+++ b/mojo/public/cpp/bindings/bindings_export.h
@@ -0,0 +1,34 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(MOJO_CPP_BINDINGS_IMPLEMENTATION)
+#define MOJO_CPP_BINDINGS_EXPORT __declspec(dllexport)
+#else
+#define MOJO_CPP_BINDINGS_EXPORT __declspec(dllimport)
+#endif
+
+#else // !defined(WIN32)
+
+#if defined(MOJO_CPP_BINDINGS_IMPLEMENTATION)
+#define MOJO_CPP_BINDINGS_EXPORT __attribute((visibility("default")))
+#else
+#define MOJO_CPP_BINDINGS_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+#else // !defined(COMPONENT_BUILD)
+
+#define MOJO_CPP_BINDINGS_EXPORT
+
+#endif // defined(COMPONENT_BUILD)
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_
diff --git a/mojo/public/cpp/bindings/clone_traits.h b/mojo/public/cpp/bindings/clone_traits.h
new file mode 100644
index 0000000000..203ab34189
--- /dev/null
+++ b/mojo/public/cpp/bindings/clone_traits.h
@@ -0,0 +1,86 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_
+
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
+namespace mojo {
+
+template <typename T>
+struct HasCloneMethod {
+ template <typename U>
+ static char Test(decltype(&U::Clone));
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ internal::EnsureTypeIsComplete<T> check_t_;
+};
+
+template <typename T, bool has_clone_method = HasCloneMethod<T>::value>
+struct CloneTraits;
+
+template <typename T>
+T Clone(const T& input);
+
+template <typename T>
+struct CloneTraits<T, true> {
+ static T Clone(const T& input) { return input.Clone(); }
+};
+
+template <typename T>
+struct CloneTraits<T, false> {
+ static T Clone(const T& input) { return input; }
+};
+
+template <typename T>
+struct CloneTraits<base::Optional<T>, false> {
+ static base::Optional<T> Clone(const base::Optional<T>& input) {
+ if (!input)
+ return base::nullopt;
+
+ return base::Optional<T>(mojo::Clone(*input));
+ }
+};
+
+template <typename T>
+struct CloneTraits<std::vector<T>, false> {
+ static std::vector<T> Clone(const std::vector<T>& input) {
+ std::vector<T> result;
+ result.reserve(input.size());
+ for (const auto& element : input)
+ result.push_back(mojo::Clone(element));
+
+ return result;
+ }
+};
+
+template <typename K, typename V>
+struct CloneTraits<std::unordered_map<K, V>, false> {
+ static std::unordered_map<K, V> Clone(const std::unordered_map<K, V>& input) {
+ std::unordered_map<K, V> result;
+ for (const auto& element : input) {
+ result.insert(std::make_pair(mojo::Clone(element.first),
+ mojo::Clone(element.second)));
+ }
+ return result;
+ }
+};
+
+template <typename T>
+T Clone(const T& input) {
+ return CloneTraits<T>::Clone(input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/connection_error_callback.h b/mojo/public/cpp/bindings/connection_error_callback.h
new file mode 100644
index 0000000000..306e99e45b
--- /dev/null
+++ b/mojo/public/cpp/bindings/connection_error_callback.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 MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_
+
+#include "base/callback.h"
+
+namespace mojo {
+
+// This callback type accepts user-defined disconnect reason and description. If
+// the other side specifies a reason on closing the connection, it will be
+// passed to the error handler.
+using ConnectionErrorWithReasonCallback =
+ base::Callback<void(uint32_t /* custom_reason */,
+ const std::string& /* description */)>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_
diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h
new file mode 100644
index 0000000000..cb065c174d
--- /dev/null
+++ b/mojo/public/cpp/bindings/connector.h
@@ -0,0 +1,241 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/sync_handle_watcher.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+
+namespace base {
+class Lock;
+}
+
+namespace mojo {
+
+// The Connector class is responsible for performing read/write operations on a
+// MessagePipe. It writes messages it receives through the MessageReceiver
+// interface that it subclasses, and it forwards messages it reads through the
+// MessageReceiver interface assigned as its incoming receiver.
+//
+// NOTE:
+// - MessagePipe I/O is non-blocking.
+// - Sending messages can be configured to be thread safe (please see comments
+// of the constructor). Other than that, the object should only be accessed
+// on the creating thread.
+class MOJO_CPP_BINDINGS_EXPORT Connector
+ : NON_EXPORTED_BASE(public MessageReceiver) {
+ public:
+ enum ConnectorConfig {
+ // Connector::Accept() is only called from a single thread.
+ SINGLE_THREADED_SEND,
+ // Connector::Accept() is allowed to be called from multiple threads.
+ MULTI_THREADED_SEND
+ };
+
+ // The Connector takes ownership of |message_pipe|.
+ Connector(ScopedMessagePipeHandle message_pipe,
+ ConnectorConfig config,
+ scoped_refptr<base::SingleThreadTaskRunner> runner);
+ ~Connector() override;
+
+ // Sets the receiver to handle messages read from the message pipe. The
+ // Connector will read messages from the pipe regardless of whether or not an
+ // incoming receiver has been set.
+ void set_incoming_receiver(MessageReceiver* receiver) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ incoming_receiver_ = receiver;
+ }
+
+ // Errors from incoming receivers will force the connector into an error
+ // state, where no more messages will be processed. This method is used
+ // during testing to prevent that from happening.
+ void set_enforce_errors_from_incoming_receiver(bool enforce) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ enforce_errors_from_incoming_receiver_ = enforce;
+ }
+
+ // Sets the error handler to receive notifications when an error is
+ // encountered while reading from the pipe or waiting to read from the pipe.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ connection_error_handler_ = error_handler;
+ }
+
+ // Returns true if an error was encountered while reading from the pipe or
+ // waiting to read from the pipe.
+ bool encountered_error() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return error_;
+ }
+
+ // Closes the pipe. The connector is put into a quiescent state.
+ //
+ // Please note that this method shouldn't be called unless it results from an
+ // explicit request of the user of bindings (e.g., the user sets an
+ // InterfacePtr to null or closes a Binding).
+ void CloseMessagePipe();
+
+ // Releases the pipe. Connector is put into a quiescent state.
+ ScopedMessagePipeHandle PassMessagePipe();
+
+ // Enters the error state. The upper layer may do this for unrecoverable
+ // issues such as invalid messages are received. If a connection error handler
+ // has been set, it will be called asynchronously.
+ //
+ // It is a no-op if the connector is already in the error state or there isn't
+ // a bound message pipe. Otherwise, it closes the message pipe, which notifies
+ // the other end and also prevents potential danger (say, the caller raises
+ // an error because it believes the other end is malicious). In order to
+ // appear to the user that the connector still binds to a message pipe, it
+ // creates a new message pipe, closes one end and binds to the other.
+ void RaiseError();
+
+ // Is the connector bound to a MessagePipe handle?
+ bool is_valid() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return message_pipe_.is_valid();
+ }
+
+ // Waits for the next message on the pipe, blocking until one arrives,
+ // |deadline| elapses, or an error happens. Returns |true| if a message has
+ // been delivered, |false| otherwise.
+ bool WaitForIncomingMessage(MojoDeadline deadline);
+
+ // See Binding for details of pause/resume.
+ void PauseIncomingMethodCallProcessing();
+ void ResumeIncomingMethodCallProcessing();
+
+ // MessageReceiver implementation:
+ bool Accept(Message* message) override;
+
+ MessagePipeHandle handle() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return message_pipe_.get();
+ }
+
+ // Allows |message_pipe_| to be watched while others perform sync handle
+ // watching on the same thread. Please see comments of
+ // SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread().
+ void AllowWokenUpBySyncWatchOnSameThread();
+
+ // Watches |message_pipe_| (as well as other handles registered to be watched
+ // together) synchronously.
+ // This method:
+ // - returns true when |should_stop| is set to true;
+ // - return false when any error occurs, including |message_pipe_| being
+ // closed.
+ bool SyncWatch(const bool* should_stop);
+
+ // Whether currently the control flow is inside the sync handle watcher
+ // callback.
+ // It always returns false after CloseMessagePipe()/PassMessagePipe().
+ bool during_sync_handle_watcher_callback() const {
+ return sync_handle_watcher_callback_count_ > 0;
+ }
+
+ base::SingleThreadTaskRunner* task_runner() const {
+ return task_runner_.get();
+ }
+
+ // Sets the tag used by the heap profiler.
+ // |tag| must be a const string literal.
+ void SetWatcherHeapProfilerTag(const char* tag);
+
+ private:
+ class ActiveDispatchTracker;
+ class MessageLoopNestingObserver;
+
+ // Callback of mojo::SimpleWatcher.
+ void OnWatcherHandleReady(MojoResult result);
+ // Callback of SyncHandleWatcher.
+ void OnSyncHandleWatcherHandleReady(MojoResult result);
+ void OnHandleReadyInternal(MojoResult result);
+
+ void WaitToReadMore();
+
+ // Returns false if it is impossible to receive more messages in the future.
+ // |this| may have been destroyed in that case.
+ WARN_UNUSED_RESULT bool ReadSingleMessage(MojoResult* read_result);
+
+ // |this| can be destroyed during message dispatch.
+ void ReadAllAvailableMessages();
+
+ // If |force_pipe_reset| is true, this method replaces the existing
+ // |message_pipe_| with a dummy message pipe handle (whose peer is closed).
+ // If |force_async_handler| is true, |connection_error_handler_| is called
+ // asynchronously.
+ void HandleError(bool force_pipe_reset, bool force_async_handler);
+
+ // Cancels any calls made to |waiter_|.
+ void CancelWait();
+
+ void EnsureSyncWatcherExists();
+
+ base::Closure connection_error_handler_;
+
+ ScopedMessagePipeHandle message_pipe_;
+ MessageReceiver* incoming_receiver_ = nullptr;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ std::unique_ptr<SimpleWatcher> handle_watcher_;
+
+ bool error_ = false;
+ bool drop_writes_ = false;
+ bool enforce_errors_from_incoming_receiver_ = true;
+
+ bool paused_ = false;
+
+ // If sending messages is allowed from multiple threads, |lock_| is used to
+ // protect modifications to |message_pipe_| and |drop_writes_|.
+ base::Optional<base::Lock> lock_;
+
+ std::unique_ptr<SyncHandleWatcher> sync_watcher_;
+ bool allow_woken_up_by_others_ = false;
+ // If non-zero, currently the control flow is inside the sync handle watcher
+ // callback.
+ size_t sync_handle_watcher_callback_count_ = 0;
+
+ base::ThreadChecker thread_checker_;
+
+ base::Lock connected_lock_;
+ bool connected_ = true;
+
+ // The tag used to track heap allocations that originated from a Watcher
+ // notification.
+ const char* heap_profiler_tag_ = nullptr;
+
+ // A cached pointer to the MessageLoopNestingObserver for the MessageLoop on
+ // which this Connector was created.
+ MessageLoopNestingObserver* const nesting_observer_;
+
+ // |true| iff the Connector is currently dispatching a message. Used to detect
+ // nested dispatch operations.
+ bool is_dispatching_ = false;
+
+ // Create a single weak ptr and use it everywhere, to avoid the malloc/free
+ // cost of creating a new weak ptr whenever it is needed.
+ // NOTE: This weak pointer is invalidated when the message pipe is closed or
+ // transferred (i.e., when |connected_| is set to false).
+ base::WeakPtr<Connector> weak_self_;
+ base::WeakPtrFactory<Connector> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Connector);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_
diff --git a/mojo/public/cpp/bindings/disconnect_reason.h b/mojo/public/cpp/bindings/disconnect_reason.h
new file mode 100644
index 0000000000..c04e8ada53
--- /dev/null
+++ b/mojo/public/cpp/bindings/disconnect_reason.h
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace mojo {
+
+struct DisconnectReason {
+ public:
+ DisconnectReason(uint32_t in_custom_reason, const std::string& in_description)
+ : custom_reason(in_custom_reason), description(in_description) {}
+
+ uint32_t custom_reason;
+ std::string description;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_
diff --git a/mojo/public/cpp/bindings/enum_traits.h b/mojo/public/cpp/bindings/enum_traits.h
new file mode 100644
index 0000000000..2c528f3226
--- /dev/null
+++ b/mojo/public/cpp/bindings/enum_traits.h
@@ -0,0 +1,27 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_
+
+namespace mojo {
+
+// This must be specialized for any type |T| to be serialized/deserialized as a
+// mojom enum |MojomType|. Each specialization needs to implement:
+//
+// template <>
+// struct EnumTraits<MojomType, T> {
+// static MojomType ToMojom(T input);
+//
+// // Returning false results in deserialization failure and causes the
+// // message pipe receiving it to be disconnected.
+// static bool FromMojom(MojomType input, T* output);
+// };
+//
+template <typename MojomType, typename T>
+struct EnumTraits;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/filter_chain.h b/mojo/public/cpp/bindings/filter_chain.h
new file mode 100644
index 0000000000..1262f39b80
--- /dev/null
+++ b/mojo/public/cpp/bindings/filter_chain.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 MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+
+class MOJO_CPP_BINDINGS_EXPORT FilterChain
+ : NON_EXPORTED_BASE(public MessageReceiver) {
+ public:
+ // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while
+ // this object is alive.
+ explicit FilterChain(MessageReceiver* sink = nullptr);
+
+ FilterChain(FilterChain&& other);
+ FilterChain& operator=(FilterChain&& other);
+ ~FilterChain() override;
+
+ template <typename FilterType, typename... Args>
+ inline void Append(Args&&... args);
+
+ void Append(std::unique_ptr<MessageReceiver> filter);
+
+ // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while
+ // this object is alive.
+ void SetSink(MessageReceiver* sink);
+
+ // MessageReceiver:
+ bool Accept(Message* message) override;
+
+ private:
+ std::vector<std::unique_ptr<MessageReceiver>> filters_;
+
+ MessageReceiver* sink_;
+
+ DISALLOW_COPY_AND_ASSIGN(FilterChain);
+};
+
+template <typename FilterType, typename... Args>
+inline void FilterChain::Append(Args&&... args) {
+ Append(base::MakeUnique<FilterType>(std::forward<Args>(args)...));
+}
+
+template <>
+inline void FilterChain::Append<PassThroughFilter>() {
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_
diff --git a/mojo/public/cpp/bindings/interface_data_view.h b/mojo/public/cpp/bindings/interface_data_view.h
new file mode 100644
index 0000000000..ef1225431c
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_data_view.h
@@ -0,0 +1,25 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_
+
+namespace mojo {
+
+// They are used for type identification purpose only.
+template <typename Interface>
+class AssociatedInterfacePtrInfoDataView {};
+
+template <typename Interface>
+class AssociatedInterfaceRequestDataView {};
+
+template <typename Interface>
+class InterfacePtrDataView {};
+
+template <typename Interface>
+class InterfaceRequestDataView {};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_
diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
new file mode 100644
index 0000000000..b519fe92bb
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
@@ -0,0 +1,193 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/disconnect_reason.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/lib/control_message_handler.h"
+#include "mojo/public/cpp/bindings/lib/control_message_proxy.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+
+class AssociatedGroup;
+class InterfaceEndpointController;
+
+// InterfaceEndpointClient handles message sending and receiving of an interface
+// endpoint, either the implementation side or the client side.
+// It should only be accessed and destructed on the creating thread.
+class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient
+ : NON_EXPORTED_BASE(public MessageReceiverWithResponder) {
+ public:
+ // |receiver| is okay to be null. If it is not null, it must outlive this
+ // object.
+ InterfaceEndpointClient(ScopedInterfaceEndpointHandle handle,
+ MessageReceiverWithResponderStatus* receiver,
+ std::unique_ptr<MessageReceiver> payload_validator,
+ bool expect_sync_requests,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ uint32_t interface_version);
+ ~InterfaceEndpointClient() override;
+
+ // Sets the error handler to receive notifications when an error is
+ // encountered.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ error_handler_ = error_handler;
+ error_with_reason_handler_.Reset();
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ error_with_reason_handler_ = error_handler;
+ error_handler_.Reset();
+ }
+
+ // Returns true if an error was encountered.
+ bool encountered_error() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return encountered_error_;
+ }
+
+ // Returns true if this endpoint has any pending callbacks.
+ bool has_pending_responders() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return !async_responders_.empty() || !sync_responses_.empty();
+ }
+
+ AssociatedGroup* associated_group();
+
+ // Adds a MessageReceiver which can filter a message after validation but
+ // before dispatch.
+ void AddFilter(std::unique_ptr<MessageReceiver> filter);
+
+ // After this call the object is in an invalid state and shouldn't be reused.
+ ScopedInterfaceEndpointHandle PassHandle();
+
+ // Raises an error on the underlying message pipe. It disconnects the pipe
+ // and notifies all interfaces running on this pipe.
+ void RaiseError();
+
+ void CloseWithReason(uint32_t custom_reason, const std::string& description);
+
+ // MessageReceiverWithResponder implementation:
+ // They must only be called when the handle is not in pending association
+ // state.
+ bool Accept(Message* message) override;
+ bool AcceptWithResponder(Message* message,
+ std::unique_ptr<MessageReceiver> responder) override;
+
+ // The following methods are called by the router. They must be called
+ // outside of the router's lock.
+
+ // NOTE: |message| must have passed message header validation.
+ bool HandleIncomingMessage(Message* message);
+ void NotifyError(const base::Optional<DisconnectReason>& reason);
+
+ // The following methods send interface control messages.
+ // They must only be called when the handle is not in pending association
+ // state.
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback);
+ void RequireVersion(uint32_t version);
+ void FlushForTesting();
+
+ private:
+ // Maps from the id of a response to the MessageReceiver that handles the
+ // response.
+ using AsyncResponderMap =
+ std::map<uint64_t, std::unique_ptr<MessageReceiver>>;
+
+ struct SyncResponseInfo {
+ public:
+ explicit SyncResponseInfo(bool* in_response_received);
+ ~SyncResponseInfo();
+
+ Message response;
+
+ // Points to a stack-allocated variable.
+ bool* response_received;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SyncResponseInfo);
+ };
+
+ using SyncResponseMap = std::map<uint64_t, std::unique_ptr<SyncResponseInfo>>;
+
+ // Used as the sink for |payload_validator_| and forwards messages to
+ // HandleValidatedMessage().
+ class HandleIncomingMessageThunk : public MessageReceiver {
+ public:
+ explicit HandleIncomingMessageThunk(InterfaceEndpointClient* owner);
+ ~HandleIncomingMessageThunk() override;
+
+ // MessageReceiver implementation:
+ bool Accept(Message* message) override;
+
+ private:
+ InterfaceEndpointClient* const owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandleIncomingMessageThunk);
+ };
+
+ void InitControllerIfNecessary();
+
+ void OnAssociationEvent(
+ ScopedInterfaceEndpointHandle::AssociationEvent event);
+
+ bool HandleValidatedMessage(Message* message);
+
+ const bool expect_sync_requests_ = false;
+
+ ScopedInterfaceEndpointHandle handle_;
+ std::unique_ptr<AssociatedGroup> associated_group_;
+ InterfaceEndpointController* controller_ = nullptr;
+
+ MessageReceiverWithResponderStatus* const incoming_receiver_ = nullptr;
+ HandleIncomingMessageThunk thunk_;
+ FilterChain filters_;
+
+ AsyncResponderMap async_responders_;
+ SyncResponseMap sync_responses_;
+
+ uint64_t next_request_id_ = 1;
+
+ base::Closure error_handler_;
+ ConnectionErrorWithReasonCallback error_with_reason_handler_;
+ bool encountered_error_ = false;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ internal::ControlMessageProxy control_message_proxy_;
+ internal::ControlMessageHandler control_message_handler_;
+
+ base::ThreadChecker thread_checker_;
+
+ base::WeakPtrFactory<InterfaceEndpointClient> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfaceEndpointClient);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_
diff --git a/mojo/public/cpp/bindings/interface_endpoint_controller.h b/mojo/public/cpp/bindings/interface_endpoint_controller.h
new file mode 100644
index 0000000000..8d99d4a45f
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_endpoint_controller.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_
+
+namespace mojo {
+
+class Message;
+
+// A control interface exposed by AssociatedGroupController for interface
+// endpoints.
+class InterfaceEndpointController {
+ public:
+ virtual ~InterfaceEndpointController() {}
+
+ virtual bool SendMessage(Message* message) = 0;
+
+ // Allows the interface endpoint to watch for incoming sync messages while
+ // others perform sync handle watching on the same thread. Please see comments
+ // of SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread().
+ virtual void AllowWokenUpBySyncWatchOnSameThread() = 0;
+
+ // Watches the interface endpoint for incoming sync messages. (It also watches
+ // other other handles registered to be watched together.)
+ // This method:
+ // - returns true when |should_stop| is set to true;
+ // - return false otherwise, including
+ // MultiplexRouter::DetachEndpointClient() being called for the same
+ // interface endpoint.
+ virtual bool SyncWatch(const bool* should_stop) = 0;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_
diff --git a/mojo/public/cpp/bindings/interface_id.h b/mojo/public/cpp/bindings/interface_id.h
new file mode 100644
index 0000000000..53475d6f78
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_
+
+#include <stdint.h>
+
+namespace mojo {
+
+// The size of the type matters because it is directly used in messages.
+using InterfaceId = uint32_t;
+
+// IDs of associated interface can be generated at both sides of the message
+// pipe. In order to avoid collision, the highest bit is used as namespace bit:
+// at the side where the client-side of the master interface lives, IDs are
+// generated with the namespace bit set to 1; at the opposite side IDs are
+// generated with the namespace bit set to 0.
+const uint32_t kInterfaceIdNamespaceMask = 0x80000000;
+
+const InterfaceId kMasterInterfaceId = 0x00000000;
+const InterfaceId kInvalidInterfaceId = 0xFFFFFFFF;
+
+inline bool IsMasterInterfaceId(InterfaceId id) {
+ return id == kMasterInterfaceId;
+}
+
+inline bool IsValidInterfaceId(InterfaceId id) {
+ return id != kInvalidInterfaceId;
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_
diff --git a/mojo/public/cpp/bindings/interface_ptr.h b/mojo/public/cpp/bindings/interface_ptr.h
new file mode 100644
index 0000000000..e88be7436f
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_ptr.h
@@ -0,0 +1,242 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/lib/interface_ptr_state.h"
+
+namespace mojo {
+
+// A pointer to a local proxy of a remote Interface implementation. Uses a
+// message pipe to communicate with the remote implementation, and automatically
+// closes the pipe and deletes the proxy on destruction. The pointer must be
+// bound to a message pipe before the interface methods can be called.
+//
+// This class is thread hostile, as is the local proxy it manages, while bound
+// to a message pipe. All calls to this class or the proxy should be from the
+// same thread that bound it. If you need to move the proxy to a different
+// thread, extract the InterfacePtrInfo (containing just the message pipe and
+// any version information) using PassInterface(), pass it to a different
+// thread, and create and bind a new InterfacePtr from that thread. If an
+// InterfacePtr is not bound to a message pipe, it may be bound or destroyed on
+// any thread.
+template <typename Interface>
+class InterfacePtr {
+ public:
+ using InterfaceType = Interface;
+ using PtrInfoType = InterfacePtrInfo<Interface>;
+
+ // Constructs an unbound InterfacePtr.
+ InterfacePtr() {}
+ InterfacePtr(decltype(nullptr)) {}
+
+ // Takes over the binding of another InterfacePtr.
+ InterfacePtr(InterfacePtr&& other) {
+ internal_state_.Swap(&other.internal_state_);
+ }
+
+ // Takes over the binding of another InterfacePtr, and closes any message pipe
+ // already bound to this pointer.
+ InterfacePtr& operator=(InterfacePtr&& other) {
+ reset();
+ internal_state_.Swap(&other.internal_state_);
+ return *this;
+ }
+
+ // Assigning nullptr to this class causes it to close the currently bound
+ // message pipe (if any) and returns the pointer to the unbound state.
+ InterfacePtr& operator=(decltype(nullptr)) {
+ reset();
+ return *this;
+ }
+
+ // Closes the bound message pipe (if any) on destruction.
+ ~InterfacePtr() {}
+
+ // Binds the InterfacePtr to a remote implementation of Interface.
+ //
+ // Calling with an invalid |info| (containing an invalid message pipe handle)
+ // has the same effect as reset(). In this case, the InterfacePtr is not
+ // considered as bound.
+ //
+ // |runner| must belong to the same thread. It will be used to dispatch all
+ // callbacks and connection error notification. It is useful when you attach
+ // multiple task runners to a single thread for the purposes of task
+ // scheduling.
+ void Bind(InterfacePtrInfo<Interface> info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ reset();
+ if (info.is_valid())
+ internal_state_.Bind(std::move(info), std::move(runner));
+ }
+
+ // Returns whether or not this InterfacePtr is bound to a message pipe.
+ bool is_bound() const { return internal_state_.is_bound(); }
+
+ // Returns a raw pointer to the local proxy. Caller does not take ownership.
+ // Note that the local proxy is thread hostile, as stated above.
+ Interface* get() const { return internal_state_.instance(); }
+
+ // Functions like a pointer to Interface. Must already be bound.
+ Interface* operator->() const { return get(); }
+ Interface& operator*() const { return *get(); }
+
+ // Returns the version number of the interface that the remote side supports.
+ uint32_t version() const { return internal_state_.version(); }
+
+ // Queries the max version that the remote side supports. On completion, the
+ // result will be returned as the input of |callback|. The version number of
+ // this interface pointer will also be updated.
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback) {
+ internal_state_.QueryVersion(callback);
+ }
+
+ // If the remote side doesn't support the specified version, it will close its
+ // end of the message pipe asynchronously. This does nothing if it's already
+ // known that the remote side supports the specified version, i.e., if
+ // |version <= this->version()|.
+ //
+ // After calling RequireVersion() with a version not supported by the remote
+ // side, all subsequent calls to interface methods will be ignored.
+ void RequireVersion(uint32_t version) {
+ internal_state_.RequireVersion(version);
+ }
+
+ // Sends a no-op message on the underlying message pipe and runs the current
+ // message loop until its response is received. This can be used in tests to
+ // verify that no message was sent on a message pipe in response to some
+ // stimulus.
+ void FlushForTesting() { internal_state_.FlushForTesting(); }
+
+ // Closes the bound message pipe (if any) and returns the pointer to the
+ // unbound state.
+ void reset() {
+ State doomed;
+ internal_state_.Swap(&doomed);
+ }
+
+ // Similar to the method above, but also specifies a disconnect reason.
+ void ResetWithReason(uint32_t custom_reason, const std::string& description) {
+ if (internal_state_.is_bound())
+ internal_state_.CloseWithReason(custom_reason, description);
+ reset();
+ }
+
+ // Whether there are any associated interfaces running on the pipe currently.
+ bool HasAssociatedInterfaces() const {
+ return internal_state_.HasAssociatedInterfaces();
+ }
+
+ // Indicates whether the message pipe has encountered an error. If true,
+ // method calls made on this interface will be dropped (and may already have
+ // been dropped).
+ bool encountered_error() const { return internal_state_.encountered_error(); }
+
+ // Registers a handler to receive error notifications. The handler will be
+ // called from the thread that owns this InterfacePtr.
+ //
+ // This method may only be called after the InterfacePtr has been bound to a
+ // message pipe.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ internal_state_.set_connection_error_handler(error_handler);
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ internal_state_.set_connection_error_with_reason_handler(error_handler);
+ }
+
+ // Unbinds the InterfacePtr and returns the information which could be used
+ // to setup an InterfacePtr again. This method may be used to move the proxy
+ // to a different thread (see class comments for details).
+ //
+ // It is an error to call PassInterface() while:
+ // - there are pending responses; or
+ // TODO: fix this restriction, it's not always obvious when there is a
+ // pending response.
+ // - there are associated interfaces running.
+ // TODO(yzshen): For now, users need to make sure there is no one holding
+ // on to associated interface endpoint handles at both sides of the
+ // message pipe in order to call this method. We need a way to forcefully
+ // invalidate associated interface endpoint handles.
+ InterfacePtrInfo<Interface> PassInterface() {
+ CHECK(!HasAssociatedInterfaces());
+ CHECK(!internal_state_.has_pending_callbacks());
+ State state;
+ internal_state_.Swap(&state);
+
+ return state.PassInterface();
+ }
+
+ bool Equals(const InterfacePtr& other) const {
+ if (this == &other)
+ return true;
+
+ // Now that the two refer to different objects, they are equivalent if
+ // and only if they are both null.
+ return !(*this) && !other;
+ }
+
+ // DO NOT USE. Exposed only for internal use and for testing.
+ internal::InterfacePtrState<Interface>* internal_state() {
+ return &internal_state_;
+ }
+
+ // Allow InterfacePtr<> to be used in boolean expressions, but not
+ // implicitly convertible to a real bool (which is dangerous).
+ private:
+ // TODO(dcheng): Use an explicit conversion operator.
+ typedef internal::InterfacePtrState<Interface> InterfacePtr::*Testable;
+
+ public:
+ operator Testable() const {
+ return internal_state_.is_bound() ? &InterfacePtr::internal_state_
+ : nullptr;
+ }
+
+ private:
+ // Forbid the == and != operators explicitly, otherwise InterfacePtr will be
+ // converted to Testable to do == or != comparison.
+ template <typename T>
+ bool operator==(const InterfacePtr<T>& other) const = delete;
+ template <typename T>
+ bool operator!=(const InterfacePtr<T>& other) const = delete;
+
+ typedef internal::InterfacePtrState<Interface> State;
+ mutable State internal_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfacePtr);
+};
+
+// If |info| is valid (containing a valid message pipe handle), returns an
+// InterfacePtr bound to it. Otherwise, returns an unbound InterfacePtr.
+template <typename Interface>
+InterfacePtr<Interface> MakeProxy(
+ InterfacePtrInfo<Interface> info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ InterfacePtr<Interface> ptr;
+ if (info.is_valid())
+ ptr.Bind(std::move(info), std::move(runner));
+ return std::move(ptr);
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_
diff --git a/mojo/public/cpp/bindings/interface_ptr_info.h b/mojo/public/cpp/bindings/interface_ptr_info.h
new file mode 100644
index 0000000000..0b2d8089c4
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_ptr_info.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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// InterfacePtrInfo stores necessary information to communicate with a remote
+// interface implementation, which could be used to construct an InterfacePtr.
+template <typename Interface>
+class InterfacePtrInfo {
+ public:
+ InterfacePtrInfo() : version_(0u) {}
+
+ InterfacePtrInfo(ScopedMessagePipeHandle handle, uint32_t version)
+ : handle_(std::move(handle)), version_(version) {}
+
+ InterfacePtrInfo(InterfacePtrInfo&& other)
+ : handle_(std::move(other.handle_)), version_(other.version_) {
+ other.version_ = 0u;
+ }
+
+ ~InterfacePtrInfo() {}
+
+ InterfacePtrInfo& operator=(InterfacePtrInfo&& other) {
+ if (this != &other) {
+ handle_ = std::move(other.handle_);
+ version_ = other.version_;
+ other.version_ = 0u;
+ }
+
+ return *this;
+ }
+
+ bool is_valid() const { return handle_.is_valid(); }
+
+ ScopedMessagePipeHandle PassHandle() { return std::move(handle_); }
+ const ScopedMessagePipeHandle& handle() const { return handle_; }
+ void set_handle(ScopedMessagePipeHandle handle) {
+ handle_ = std::move(handle);
+ }
+
+ uint32_t version() const { return version_; }
+ void set_version(uint32_t version) { version_ = version; }
+
+ private:
+ ScopedMessagePipeHandle handle_;
+ uint32_t version_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfacePtrInfo);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_
diff --git a/mojo/public/cpp/bindings/interface_ptr_set.h b/mojo/public/cpp/bindings/interface_ptr_set.h
new file mode 100644
index 0000000000..09a268229d
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_ptr_set.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 MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+
+namespace mojo {
+namespace internal {
+
+// TODO(blundell): This class should be rewritten to be structured
+// similarly to BindingSet if possible, with PtrSet owning its
+// Elements and those Elements calling back into PtrSet on connection
+// error.
+template <typename Interface, template <typename> class Ptr>
+class PtrSet {
+ public:
+ PtrSet() {}
+ ~PtrSet() { CloseAll(); }
+
+ void AddPtr(Ptr<Interface> ptr) {
+ auto weak_interface_ptr = new Element(std::move(ptr));
+ ptrs_.push_back(weak_interface_ptr->GetWeakPtr());
+ ClearNullPtrs();
+ }
+
+ template <typename FunctionType>
+ void ForAllPtrs(FunctionType function) {
+ for (const auto& it : ptrs_) {
+ if (it)
+ function(it->get());
+ }
+ ClearNullPtrs();
+ }
+
+ void CloseAll() {
+ for (const auto& it : ptrs_) {
+ if (it)
+ it->Close();
+ }
+ ptrs_.clear();
+ }
+
+ private:
+ class Element {
+ public:
+ explicit Element(Ptr<Interface> ptr)
+ : ptr_(std::move(ptr)), weak_ptr_factory_(this) {
+ ptr_.set_connection_error_handler(base::Bind(&DeleteElement, this));
+ }
+
+ ~Element() {}
+
+ void Close() {
+ ptr_.reset();
+
+ // Resetting the interface ptr means that it won't call this object back
+ // on connection error anymore, so this object must delete itself now.
+ DeleteElement(this);
+ }
+
+ Interface* get() { return ptr_.get(); }
+
+ base::WeakPtr<Element> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ static void DeleteElement(Element* element) { delete element; }
+
+ Ptr<Interface> ptr_;
+ base::WeakPtrFactory<Element> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Element);
+ };
+
+ void ClearNullPtrs() {
+ ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(),
+ [](const base::WeakPtr<Element>& p) {
+ return p.get() == nullptr;
+ }),
+ ptrs_.end());
+ }
+
+ std::vector<base::WeakPtr<Element>> ptrs_;
+};
+
+} // namespace internal
+
+template <typename Interface>
+using InterfacePtrSet = internal::PtrSet<Interface, InterfacePtr>;
+
+template <typename Interface>
+using AssociatedInterfacePtrSet =
+ internal::PtrSet<Interface, AssociatedInterfacePtr>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_
diff --git a/mojo/public/cpp/bindings/interface_request.h b/mojo/public/cpp/bindings/interface_request.h
new file mode 100644
index 0000000000..29d883615e
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_request.h
@@ -0,0 +1,178 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/disconnect_reason.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// Represents a request from a remote client for an implementation of Interface
+// over a specified message pipe. The implementor of the interface should
+// remove the message pipe by calling PassMessagePipe() and bind it to the
+// implementation. If this is not done, the InterfaceRequest will automatically
+// close the pipe on destruction. Can also represent the absence of a request
+// if the client did not provide a message pipe.
+template <typename Interface>
+class InterfaceRequest {
+ public:
+ // Constructs an empty InterfaceRequest, representing that the client is not
+ // requesting an implementation of Interface.
+ InterfaceRequest() {}
+ InterfaceRequest(decltype(nullptr)) {}
+
+ // Creates a new message pipe over which Interface is to be served, binding
+ // the specified InterfacePtr to one end of the message pipe and this
+ // InterfaceRequest to the other. For example usage, see comments on
+ // MakeRequest(InterfacePtr*) below.
+ explicit InterfaceRequest(InterfacePtr<Interface>* ptr,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ MessagePipe pipe;
+ ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u),
+ std::move(runner));
+ Bind(std::move(pipe.handle1));
+ }
+
+ // Takes the message pipe from another InterfaceRequest.
+ InterfaceRequest(InterfaceRequest&& other) {
+ handle_ = std::move(other.handle_);
+ }
+ InterfaceRequest& operator=(InterfaceRequest&& other) {
+ handle_ = std::move(other.handle_);
+ return *this;
+ }
+
+ // Assigning to nullptr resets the InterfaceRequest to an empty state,
+ // closing the message pipe currently bound to it (if any).
+ InterfaceRequest& operator=(decltype(nullptr)) {
+ handle_.reset();
+ return *this;
+ }
+
+ // Binds the request to a message pipe over which Interface is to be
+ // requested. If the request is already bound to a message pipe, the current
+ // message pipe will be closed.
+ void Bind(ScopedMessagePipeHandle handle) { handle_ = std::move(handle); }
+
+ // Indicates whether the request currently contains a valid message pipe.
+ bool is_pending() const { return handle_.is_valid(); }
+
+ // Removes the message pipe from the request and returns it.
+ ScopedMessagePipeHandle PassMessagePipe() { return std::move(handle_); }
+
+ bool Equals(const InterfaceRequest& other) const {
+ if (this == &other)
+ return true;
+
+ // Now that the two refer to different objects, they are equivalent if
+ // and only if they are both invalid.
+ return !is_pending() && !other.is_pending();
+ }
+
+ void ResetWithReason(uint32_t custom_reason, const std::string& description) {
+ if (!handle_.is_valid())
+ return;
+
+ Message message =
+ PipeControlMessageProxy::ConstructPeerEndpointClosedMessage(
+ kMasterInterfaceId, DisconnectReason(custom_reason, description));
+ MojoResult result = WriteMessageNew(
+ handle_.get(), message.TakeMojoMessage(), MOJO_WRITE_MESSAGE_FLAG_NONE);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+
+ handle_.reset();
+ }
+
+ private:
+ ScopedMessagePipeHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfaceRequest);
+};
+
+// Makes an InterfaceRequest bound to the specified message pipe. If |handle|
+// is empty or invalid, the resulting InterfaceRequest will represent the
+// absence of a request.
+template <typename Interface>
+InterfaceRequest<Interface> MakeRequest(ScopedMessagePipeHandle handle) {
+ InterfaceRequest<Interface> request;
+ request.Bind(std::move(handle));
+ return std::move(request);
+}
+
+// Creates a new message pipe over which Interface is to be served. Binds the
+// specified InterfacePtr to one end of the message pipe, and returns an
+// InterfaceRequest bound to the other. The InterfacePtr should be passed to
+// the client, and the InterfaceRequest should be passed to whatever will
+// provide the implementation. The implementation should typically be bound to
+// the InterfaceRequest using the Binding or StrongBinding classes. The client
+// may begin to issue calls even before an implementation has been bound, since
+// messages sent over the pipe will just queue up until they are consumed by
+// the implementation.
+//
+// Example #1: Requesting a remote implementation of an interface.
+// ===============================================================
+//
+// Given the following interface:
+//
+// interface Database {
+// OpenTable(Table& table);
+// }
+//
+// The client would have code similar to the following:
+//
+// DatabasePtr database = ...; // Connect to database.
+// TablePtr table;
+// database->OpenTable(MakeRequest(&table));
+//
+// Upon return from MakeRequest, |table| is ready to have methods called on it.
+//
+// Example #2: Registering a local implementation with a remote service.
+// =====================================================================
+//
+// Given the following interface
+// interface Collector {
+// RegisterSource(Source source);
+// }
+//
+// The client would have code similar to the following:
+//
+// CollectorPtr collector = ...; // Connect to Collector.
+// SourcePtr source;
+// InterfaceRequest<Source> source_request(&source);
+// collector->RegisterSource(std::move(source));
+// CreateSource(std::move(source_request)); // Create implementation locally.
+//
+template <typename Interface>
+InterfaceRequest<Interface> MakeRequest(
+ InterfacePtr<Interface>* ptr,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get()) {
+ return InterfaceRequest<Interface>(ptr, runner);
+}
+
+// Fuses an InterfaceRequest<T> endpoint with an InterfacePtrInfo<T> endpoint.
+// Returns |true| on success or |false| on failure.
+template <typename Interface>
+bool FuseInterface(InterfaceRequest<Interface> request,
+ InterfacePtrInfo<Interface> proxy_info) {
+ MojoResult result = FuseMessagePipes(request.PassMessagePipe(),
+ proxy_info.PassHandle());
+ return result == MOJO_RESULT_OK;
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_
diff --git a/mojo/public/cpp/bindings/lib/array_internal.cc b/mojo/public/cpp/bindings/lib/array_internal.cc
new file mode 100644
index 0000000000..dd24eac470
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/array_internal.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 "mojo/public/cpp/bindings/lib/array_internal.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <sstream>
+
+namespace mojo {
+namespace internal {
+
+std::string MakeMessageWithArrayIndex(const char* message,
+ size_t size,
+ size_t index) {
+ std::ostringstream stream;
+ stream << message << ": array size - " << size << "; index - " << index;
+ return stream.str();
+}
+
+std::string MakeMessageWithExpectedArraySize(const char* message,
+ size_t size,
+ size_t expected_size) {
+ std::ostringstream stream;
+ stream << message << ": array size - " << size << "; expected size - "
+ << expected_size;
+ return stream.str();
+}
+
+ArrayDataTraits<bool>::BitRef::~BitRef() {
+}
+
+ArrayDataTraits<bool>::BitRef::BitRef(uint8_t* storage, uint8_t mask)
+ : storage_(storage), mask_(mask) {
+}
+
+ArrayDataTraits<bool>::BitRef& ArrayDataTraits<bool>::BitRef::operator=(
+ bool value) {
+ if (value) {
+ *storage_ |= mask_;
+ } else {
+ *storage_ &= ~mask_;
+ }
+ return *this;
+}
+
+ArrayDataTraits<bool>::BitRef& ArrayDataTraits<bool>::BitRef::operator=(
+ const BitRef& value) {
+ return (*this) = static_cast<bool>(value);
+}
+
+ArrayDataTraits<bool>::BitRef::operator bool() const {
+ return (*storage_ & mask_) != 0;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h
new file mode 100644
index 0000000000..eecfcfbc28
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/array_internal.h
@@ -0,0 +1,368 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <new>
+
+#include "base/logging.h"
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/public/cpp/bindings/lib/validate_params.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename K, typename V>
+class Map_Data;
+
+MOJO_CPP_BINDINGS_EXPORT std::string
+MakeMessageWithArrayIndex(const char* message, size_t size, size_t index);
+
+MOJO_CPP_BINDINGS_EXPORT std::string MakeMessageWithExpectedArraySize(
+ const char* message,
+ size_t size,
+ size_t expected_size);
+
+template <typename T>
+struct ArrayDataTraits {
+ using StorageType = T;
+ using Ref = T&;
+ using ConstRef = const T&;
+
+ static const uint32_t kMaxNumElements =
+ (std::numeric_limits<uint32_t>::max() - sizeof(ArrayHeader)) /
+ sizeof(StorageType);
+
+ static uint32_t GetStorageSize(uint32_t num_elements) {
+ DCHECK(num_elements <= kMaxNumElements);
+ return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements;
+ }
+ static Ref ToRef(StorageType* storage, size_t offset) {
+ return storage[offset];
+ }
+ static ConstRef ToConstRef(const StorageType* storage, size_t offset) {
+ return storage[offset];
+ }
+};
+
+// Specialization of Arrays for bools, optimized for space. It has the
+// following differences from a generalized Array:
+// * Each element takes up a single bit of memory.
+// * Accessing a non-const single element uses a helper class |BitRef|, which
+// emulates a reference to a bool.
+template <>
+struct ArrayDataTraits<bool> {
+ // Helper class to emulate a reference to a bool, used for direct element
+ // access.
+ class MOJO_CPP_BINDINGS_EXPORT BitRef {
+ public:
+ ~BitRef();
+ BitRef& operator=(bool value);
+ BitRef& operator=(const BitRef& value);
+ operator bool() const;
+
+ private:
+ friend struct ArrayDataTraits<bool>;
+ BitRef(uint8_t* storage, uint8_t mask);
+ BitRef();
+ uint8_t* storage_;
+ uint8_t mask_;
+ };
+
+ // Because each element consumes only 1/8 byte.
+ static const uint32_t kMaxNumElements = std::numeric_limits<uint32_t>::max();
+
+ using StorageType = uint8_t;
+ using Ref = BitRef;
+ using ConstRef = bool;
+
+ static uint32_t GetStorageSize(uint32_t num_elements) {
+ return sizeof(ArrayHeader) + ((num_elements + 7) / 8);
+ }
+ static BitRef ToRef(StorageType* storage, size_t offset) {
+ return BitRef(&storage[offset / 8], 1 << (offset % 8));
+ }
+ static bool ToConstRef(const StorageType* storage, size_t offset) {
+ return (storage[offset / 8] & (1 << (offset % 8))) != 0;
+ }
+};
+
+// What follows is code to support the serialization/validation of
+// Array_Data<T>. There are four interesting cases: arrays of primitives,
+// arrays of handles/interfaces, arrays of objects and arrays of unions.
+// Arrays of objects are represented as arrays of pointers to objects. Arrays
+// of unions are inlined so they are not pointers, but comparing with primitives
+// they require more work for serialization/validation.
+//
+// TODO(yzshen): Validation code should be organzied in a way similar to
+// Serializer<>, or merged into it. It should be templatized with the mojo
+// data view type instead of the data type, that way we can use MojomTypeTraits
+// to determine the categories.
+
+template <typename T, bool is_union, bool is_handle_or_interface>
+struct ArraySerializationHelper;
+
+template <typename T>
+struct ArraySerializationHelper<T, false, false> {
+ using ElementType = typename ArrayDataTraits<T>::StorageType;
+
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ DCHECK(!validate_params->element_is_nullable)
+ << "Primitive type should be non-nullable";
+ DCHECK(!validate_params->element_validate_params)
+ << "Primitive type should not have array validate params";
+
+ if (!validate_params->validate_enum_func)
+ return true;
+
+ // Enum validation.
+ for (uint32_t i = 0; i < header->num_elements; ++i) {
+ if (!validate_params->validate_enum_func(elements[i], validation_context))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename T>
+struct ArraySerializationHelper<T, false, true> {
+ using ElementType = typename ArrayDataTraits<T>::StorageType;
+
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ DCHECK(!validate_params->element_validate_params)
+ << "Handle or interface type should not have array validate params";
+
+ for (uint32_t i = 0; i < header->num_elements; ++i) {
+ if (!validate_params->element_is_nullable &&
+ !IsHandleOrInterfaceValid(elements[i])) {
+ static const ValidationError kError =
+ std::is_same<T, Interface_Data>::value ||
+ std::is_same<T, Handle_Data>::value
+ ? VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE
+ : VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID;
+ ReportValidationError(
+ validation_context, kError,
+ MakeMessageWithArrayIndex(
+ "invalid handle or interface ID in array expecting valid "
+ "handles or interface IDs",
+ header->num_elements, i)
+ .c_str());
+ return false;
+ }
+ if (!ValidateHandleOrInterface(elements[i], validation_context))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename T>
+struct ArraySerializationHelper<Pointer<T>, false, false> {
+ using ElementType = typename ArrayDataTraits<Pointer<T>>::StorageType;
+
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ for (uint32_t i = 0; i < header->num_elements; ++i) {
+ if (!validate_params->element_is_nullable && !elements[i].offset) {
+ ReportValidationError(
+ validation_context,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex("null in array expecting valid pointers",
+ header->num_elements,
+ i).c_str());
+ return false;
+ }
+ if (!ValidateCaller<T>::Run(elements[i], validation_context,
+ validate_params->element_validate_params)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ template <typename U,
+ bool is_array_or_map = IsSpecializationOf<Array_Data, U>::value ||
+ IsSpecializationOf<Map_Data, U>::value>
+ struct ValidateCaller {
+ static bool Run(const Pointer<U>& data,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ DCHECK(!validate_params)
+ << "Struct type should not have array validate params";
+
+ return ValidateStruct(data, validation_context);
+ }
+ };
+
+ template <typename U>
+ struct ValidateCaller<U, true> {
+ static bool Run(const Pointer<U>& data,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ return ValidateContainer(data, validation_context, validate_params);
+ }
+ };
+};
+
+template <typename U>
+struct ArraySerializationHelper<U, true, false> {
+ using ElementType = typename ArrayDataTraits<U>::StorageType;
+
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ for (uint32_t i = 0; i < header->num_elements; ++i) {
+ if (!validate_params->element_is_nullable && elements[i].is_null()) {
+ ReportValidationError(
+ validation_context,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex("null in array expecting valid unions",
+ header->num_elements, i)
+ .c_str());
+ return false;
+ }
+ if (!ValidateInlinedUnion(elements[i], validation_context))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename T>
+class Array_Data {
+ public:
+ using Traits = ArrayDataTraits<T>;
+ using StorageType = typename Traits::StorageType;
+ using Ref = typename Traits::Ref;
+ using ConstRef = typename Traits::ConstRef;
+ using Helper = ArraySerializationHelper<
+ T,
+ IsUnionDataType<T>::value,
+ std::is_same<T, AssociatedInterface_Data>::value ||
+ std::is_same<T, AssociatedEndpointHandle_Data>::value ||
+ std::is_same<T, Interface_Data>::value ||
+ std::is_same<T, Handle_Data>::value>;
+ using Element = T;
+
+ // Returns null if |num_elements| or the corresponding storage size cannot be
+ // stored in uint32_t.
+ static Array_Data<T>* New(size_t num_elements, Buffer* buf) {
+ if (num_elements > Traits::kMaxNumElements)
+ return nullptr;
+
+ uint32_t num_bytes =
+ Traits::GetStorageSize(static_cast<uint32_t>(num_elements));
+ return new (buf->Allocate(num_bytes))
+ Array_Data<T>(num_bytes, static_cast<uint32_t>(num_elements));
+ }
+
+ static bool Validate(const void* data,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ if (!data)
+ return true;
+ if (!IsAligned(data)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MISALIGNED_OBJECT);
+ return false;
+ }
+ if (!validation_context->IsValidRange(data, sizeof(ArrayHeader))) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE);
+ return false;
+ }
+ const ArrayHeader* header = static_cast<const ArrayHeader*>(data);
+ if (header->num_elements > Traits::kMaxNumElements ||
+ header->num_bytes < Traits::GetStorageSize(header->num_elements)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER);
+ return false;
+ }
+ if (validate_params->expected_num_elements != 0 &&
+ header->num_elements != validate_params->expected_num_elements) {
+ ReportValidationError(
+ validation_context,
+ VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ MakeMessageWithExpectedArraySize(
+ "fixed-size array has wrong number of elements",
+ header->num_elements,
+ validate_params->expected_num_elements).c_str());
+ return false;
+ }
+ if (!validation_context->ClaimMemory(data, header->num_bytes)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE);
+ return false;
+ }
+
+ const Array_Data<T>* object = static_cast<const Array_Data<T>*>(data);
+ return Helper::ValidateElements(&object->header_, object->storage(),
+ validation_context, validate_params);
+ }
+
+ size_t size() const { return header_.num_elements; }
+
+ Ref at(size_t offset) {
+ DCHECK(offset < static_cast<size_t>(header_.num_elements));
+ return Traits::ToRef(storage(), offset);
+ }
+
+ ConstRef at(size_t offset) const {
+ DCHECK(offset < static_cast<size_t>(header_.num_elements));
+ return Traits::ToConstRef(storage(), offset);
+ }
+
+ StorageType* storage() {
+ return reinterpret_cast<StorageType*>(reinterpret_cast<char*>(this) +
+ sizeof(*this));
+ }
+
+ const StorageType* storage() const {
+ return reinterpret_cast<const StorageType*>(
+ reinterpret_cast<const char*>(this) + sizeof(*this));
+ }
+
+ private:
+ Array_Data(uint32_t num_bytes, uint32_t num_elements) {
+ header_.num_bytes = num_bytes;
+ header_.num_elements = num_elements;
+ }
+ ~Array_Data() = delete;
+
+ internal::ArrayHeader header_;
+
+ // Elements of type internal::ArrayDataTraits<T>::StorageType follow.
+};
+static_assert(sizeof(Array_Data<char>) == 8, "Bad sizeof(Array_Data)");
+
+// UTF-8 encoded
+using String_Data = Array_Data<char>;
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/lib/array_serialization.h b/mojo/public/cpp/bindings/lib/array_serialization.h
new file mode 100644
index 0000000000..d2f8ecfd72
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/array_serialization.h
@@ -0,0 +1,555 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_
+
+#include <stddef.h>
+#include <string.h> // For |memcpy()|.
+
+#include <limits>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/array_data_view.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename Traits,
+ typename MaybeConstUserType,
+ bool HasGetBegin =
+ HasGetBeginMethod<Traits, MaybeConstUserType>::value>
+class ArrayIterator {};
+
+// Used as the UserTypeIterator template parameter of ArraySerializer.
+template <typename Traits, typename MaybeConstUserType>
+class ArrayIterator<Traits, MaybeConstUserType, true> {
+ public:
+ using IteratorType = decltype(
+ CallGetBeginIfExists<Traits>(std::declval<MaybeConstUserType&>()));
+
+ explicit ArrayIterator(MaybeConstUserType& input)
+ : input_(input), iter_(CallGetBeginIfExists<Traits>(input)) {}
+ ~ArrayIterator() {}
+
+ size_t GetSize() const { return Traits::GetSize(input_); }
+
+ using GetNextResult =
+ decltype(Traits::GetValue(std::declval<IteratorType&>()));
+ GetNextResult GetNext() {
+ GetNextResult value = Traits::GetValue(iter_);
+ Traits::AdvanceIterator(iter_);
+ return value;
+ }
+
+ using GetDataIfExistsResult = decltype(
+ CallGetDataIfExists<Traits>(std::declval<MaybeConstUserType&>()));
+ GetDataIfExistsResult GetDataIfExists() {
+ return CallGetDataIfExists<Traits>(input_);
+ }
+
+ private:
+ MaybeConstUserType& input_;
+ IteratorType iter_;
+};
+
+// Used as the UserTypeIterator template parameter of ArraySerializer.
+template <typename Traits, typename MaybeConstUserType>
+class ArrayIterator<Traits, MaybeConstUserType, false> {
+ public:
+ explicit ArrayIterator(MaybeConstUserType& input) : input_(input), iter_(0) {}
+ ~ArrayIterator() {}
+
+ size_t GetSize() const { return Traits::GetSize(input_); }
+
+ using GetNextResult =
+ decltype(Traits::GetAt(std::declval<MaybeConstUserType&>(), 0));
+ GetNextResult GetNext() {
+ DCHECK_LT(iter_, Traits::GetSize(input_));
+ return Traits::GetAt(input_, iter_++);
+ }
+
+ using GetDataIfExistsResult = decltype(
+ CallGetDataIfExists<Traits>(std::declval<MaybeConstUserType&>()));
+ GetDataIfExistsResult GetDataIfExists() {
+ return CallGetDataIfExists<Traits>(input_);
+ }
+
+ private:
+ MaybeConstUserType& input_;
+ size_t iter_;
+};
+
+// ArraySerializer is also used to serialize map keys and values. Therefore, it
+// has a UserTypeIterator parameter which is an adaptor for reading to hide the
+// difference between ArrayTraits and MapTraits.
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator,
+ typename EnableType = void>
+struct ArraySerializer;
+
+// Handles serialization and deserialization of arrays of pod types.
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator>
+struct ArraySerializer<
+ MojomType,
+ MaybeConstUserType,
+ UserTypeIterator,
+ typename std::enable_if<BelongsTo<typename MojomType::Element,
+ MojomTypeCategory::POD>::value>::type> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Data = typename MojomTypeTraits<MojomType>::Data;
+ using DataElement = typename Data::Element;
+ using Element = typename MojomType::Element;
+ using Traits = ArrayTraits<UserType>;
+
+ static_assert(std::is_same<Element, DataElement>::value,
+ "Incorrect array serializer");
+ static_assert(std::is_same<Element, typename Traits::Element>::value,
+ "Incorrect array serializer");
+
+ static size_t GetSerializedSize(UserTypeIterator* input,
+ SerializationContext* context) {
+ return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement));
+ }
+
+ static void SerializeElements(UserTypeIterator* input,
+ Buffer* buf,
+ Data* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ DCHECK(!validate_params->element_is_nullable)
+ << "Primitive type should be non-nullable";
+ DCHECK(!validate_params->element_validate_params)
+ << "Primitive type should not have array validate params";
+
+ size_t size = input->GetSize();
+ if (size == 0)
+ return;
+
+ auto data = input->GetDataIfExists();
+ if (data) {
+ memcpy(output->storage(), data, size * sizeof(DataElement));
+ } else {
+ for (size_t i = 0; i < size; ++i)
+ output->at(i) = input->GetNext();
+ }
+ }
+
+ static bool DeserializeElements(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!Traits::Resize(*output, input->size()))
+ return false;
+ ArrayIterator<Traits, UserType> iterator(*output);
+ if (input->size()) {
+ auto data = iterator.GetDataIfExists();
+ if (data) {
+ memcpy(data, input->storage(), input->size() * sizeof(DataElement));
+ } else {
+ for (size_t i = 0; i < input->size(); ++i)
+ iterator.GetNext() = input->at(i);
+ }
+ }
+ return true;
+ }
+};
+
+// Handles serialization and deserialization of arrays of enum types.
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator>
+struct ArraySerializer<
+ MojomType,
+ MaybeConstUserType,
+ UserTypeIterator,
+ typename std::enable_if<BelongsTo<typename MojomType::Element,
+ MojomTypeCategory::ENUM>::value>::type> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Data = typename MojomTypeTraits<MojomType>::Data;
+ using DataElement = typename Data::Element;
+ using Element = typename MojomType::Element;
+ using Traits = ArrayTraits<UserType>;
+
+ static_assert(sizeof(Element) == sizeof(DataElement),
+ "Incorrect array serializer");
+
+ static size_t GetSerializedSize(UserTypeIterator* input,
+ SerializationContext* context) {
+ return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement));
+ }
+
+ static void SerializeElements(UserTypeIterator* input,
+ Buffer* buf,
+ Data* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ DCHECK(!validate_params->element_is_nullable)
+ << "Primitive type should be non-nullable";
+ DCHECK(!validate_params->element_validate_params)
+ << "Primitive type should not have array validate params";
+
+ size_t size = input->GetSize();
+ for (size_t i = 0; i < size; ++i)
+ Serialize<Element>(input->GetNext(), output->storage() + i);
+ }
+
+ static bool DeserializeElements(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!Traits::Resize(*output, input->size()))
+ return false;
+ ArrayIterator<Traits, UserType> iterator(*output);
+ for (size_t i = 0; i < input->size(); ++i) {
+ if (!Deserialize<Element>(input->at(i), &iterator.GetNext()))
+ return false;
+ }
+ return true;
+ }
+};
+
+// Serializes and deserializes arrays of bools.
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator>
+struct ArraySerializer<MojomType,
+ MaybeConstUserType,
+ UserTypeIterator,
+ typename std::enable_if<BelongsTo<
+ typename MojomType::Element,
+ MojomTypeCategory::BOOLEAN>::value>::type> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = ArrayTraits<UserType>;
+ using Data = typename MojomTypeTraits<MojomType>::Data;
+
+ static_assert(std::is_same<bool, typename Traits::Element>::value,
+ "Incorrect array serializer");
+
+ static size_t GetSerializedSize(UserTypeIterator* input,
+ SerializationContext* context) {
+ return sizeof(Data) + Align((input->GetSize() + 7) / 8);
+ }
+
+ static void SerializeElements(UserTypeIterator* input,
+ Buffer* buf,
+ Data* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ DCHECK(!validate_params->element_is_nullable)
+ << "Primitive type should be non-nullable";
+ DCHECK(!validate_params->element_validate_params)
+ << "Primitive type should not have array validate params";
+
+ size_t size = input->GetSize();
+ for (size_t i = 0; i < size; ++i)
+ output->at(i) = input->GetNext();
+ }
+ static bool DeserializeElements(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!Traits::Resize(*output, input->size()))
+ return false;
+ ArrayIterator<Traits, UserType> iterator(*output);
+ for (size_t i = 0; i < input->size(); ++i)
+ iterator.GetNext() = input->at(i);
+ return true;
+ }
+};
+
+// Serializes and deserializes arrays of handles or interfaces.
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator>
+struct ArraySerializer<
+ MojomType,
+ MaybeConstUserType,
+ UserTypeIterator,
+ typename std::enable_if<
+ BelongsTo<typename MojomType::Element,
+ MojomTypeCategory::ASSOCIATED_INTERFACE |
+ MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST |
+ MojomTypeCategory::HANDLE |
+ MojomTypeCategory::INTERFACE |
+ MojomTypeCategory::INTERFACE_REQUEST>::value>::type> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Data = typename MojomTypeTraits<MojomType>::Data;
+ using Element = typename MojomType::Element;
+ using Traits = ArrayTraits<UserType>;
+
+ static size_t GetSerializedSize(UserTypeIterator* input,
+ SerializationContext* context) {
+ size_t element_count = input->GetSize();
+ if (BelongsTo<Element,
+ MojomTypeCategory::ASSOCIATED_INTERFACE |
+ MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST>::value) {
+ for (size_t i = 0; i < element_count; ++i) {
+ typename UserTypeIterator::GetNextResult next = input->GetNext();
+ size_t size = PrepareToSerialize<Element>(next, context);
+ DCHECK_EQ(size, 0u);
+ }
+ }
+ return sizeof(Data) + Align(element_count * sizeof(typename Data::Element));
+ }
+
+ static void SerializeElements(UserTypeIterator* input,
+ Buffer* buf,
+ Data* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ DCHECK(!validate_params->element_validate_params)
+ << "Handle or interface type should not have array validate params";
+
+ size_t size = input->GetSize();
+ for (size_t i = 0; i < size; ++i) {
+ typename UserTypeIterator::GetNextResult next = input->GetNext();
+ Serialize<Element>(next, &output->at(i), context);
+
+ static const ValidationError kError =
+ BelongsTo<Element,
+ MojomTypeCategory::ASSOCIATED_INTERFACE |
+ MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST>::value
+ ? VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID
+ : VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE;
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !validate_params->element_is_nullable &&
+ !IsHandleOrInterfaceValid(output->at(i)),
+ kError,
+ MakeMessageWithArrayIndex("invalid handle or interface ID in array "
+ "expecting valid handles or interface IDs",
+ size, i));
+ }
+ }
+ static bool DeserializeElements(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!Traits::Resize(*output, input->size()))
+ return false;
+ ArrayIterator<Traits, UserType> iterator(*output);
+ for (size_t i = 0; i < input->size(); ++i) {
+ bool result =
+ Deserialize<Element>(&input->at(i), &iterator.GetNext(), context);
+ DCHECK(result);
+ }
+ return true;
+ }
+};
+
+// This template must only apply to pointer mojo entity (strings, structs,
+// arrays and maps).
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator>
+struct ArraySerializer<MojomType,
+ MaybeConstUserType,
+ UserTypeIterator,
+ typename std::enable_if<BelongsTo<
+ typename MojomType::Element,
+ MojomTypeCategory::ARRAY | MojomTypeCategory::MAP |
+ MojomTypeCategory::STRING |
+ MojomTypeCategory::STRUCT>::value>::type> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Data = typename MojomTypeTraits<MojomType>::Data;
+ using Element = typename MojomType::Element;
+ using DataElementPtr = typename MojomTypeTraits<Element>::Data*;
+ using Traits = ArrayTraits<UserType>;
+
+ static size_t GetSerializedSize(UserTypeIterator* input,
+ SerializationContext* context) {
+ size_t element_count = input->GetSize();
+ size_t size = sizeof(Data) + element_count * sizeof(typename Data::Element);
+ for (size_t i = 0; i < element_count; ++i) {
+ typename UserTypeIterator::GetNextResult next = input->GetNext();
+ size += PrepareToSerialize<Element>(next, context);
+ }
+ return size;
+ }
+
+ static void SerializeElements(UserTypeIterator* input,
+ Buffer* buf,
+ Data* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ size_t size = input->GetSize();
+ for (size_t i = 0; i < size; ++i) {
+ DataElementPtr data_ptr;
+ typename UserTypeIterator::GetNextResult next = input->GetNext();
+ SerializeCaller<Element>::Run(next, buf, &data_ptr,
+ validate_params->element_validate_params,
+ context);
+ output->at(i).Set(data_ptr);
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !validate_params->element_is_nullable && !data_ptr,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex("null in array expecting valid pointers",
+ size, i));
+ }
+ }
+ static bool DeserializeElements(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!Traits::Resize(*output, input->size()))
+ return false;
+ ArrayIterator<Traits, UserType> iterator(*output);
+ for (size_t i = 0; i < input->size(); ++i) {
+ if (!Deserialize<Element>(input->at(i).Get(), &iterator.GetNext(),
+ context))
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ template <typename T,
+ bool is_array_or_map = BelongsTo<T,
+ MojomTypeCategory::ARRAY |
+ MojomTypeCategory::MAP>::value>
+ struct SerializeCaller {
+ template <typename InputElementType>
+ static void Run(InputElementType&& input,
+ Buffer* buf,
+ DataElementPtr* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ Serialize<T>(std::forward<InputElementType>(input), buf, output, context);
+ }
+ };
+
+ template <typename T>
+ struct SerializeCaller<T, true> {
+ template <typename InputElementType>
+ static void Run(InputElementType&& input,
+ Buffer* buf,
+ DataElementPtr* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ Serialize<T>(std::forward<InputElementType>(input), buf, output,
+ validate_params, context);
+ }
+ };
+};
+
+// Handles serialization and deserialization of arrays of unions.
+template <typename MojomType,
+ typename MaybeConstUserType,
+ typename UserTypeIterator>
+struct ArraySerializer<
+ MojomType,
+ MaybeConstUserType,
+ UserTypeIterator,
+ typename std::enable_if<BelongsTo<typename MojomType::Element,
+ MojomTypeCategory::UNION>::value>::type> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Data = typename MojomTypeTraits<MojomType>::Data;
+ using Element = typename MojomType::Element;
+ using Traits = ArrayTraits<UserType>;
+
+ static size_t GetSerializedSize(UserTypeIterator* input,
+ SerializationContext* context) {
+ size_t element_count = input->GetSize();
+ size_t size = sizeof(Data);
+ for (size_t i = 0; i < element_count; ++i) {
+ // Call with |inlined| set to false, so that it will account for both the
+ // data in the union and the space in the array used to hold the union.
+ typename UserTypeIterator::GetNextResult next = input->GetNext();
+ size += PrepareToSerialize<Element>(next, false, context);
+ }
+ return size;
+ }
+
+ static void SerializeElements(UserTypeIterator* input,
+ Buffer* buf,
+ Data* output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ size_t size = input->GetSize();
+ for (size_t i = 0; i < size; ++i) {
+ typename Data::Element* result = output->storage() + i;
+ typename UserTypeIterator::GetNextResult next = input->GetNext();
+ Serialize<Element>(next, buf, &result, true, context);
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !validate_params->element_is_nullable && output->at(i).is_null(),
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex("null in array expecting valid unions",
+ size, i));
+ }
+ }
+
+ static bool DeserializeElements(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!Traits::Resize(*output, input->size()))
+ return false;
+ ArrayIterator<Traits, UserType> iterator(*output);
+ for (size_t i = 0; i < input->size(); ++i) {
+ if (!Deserialize<Element>(&input->at(i), &iterator.GetNext(), context))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename Element, typename MaybeConstUserType>
+struct Serializer<ArrayDataView<Element>, MaybeConstUserType> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = ArrayTraits<UserType>;
+ using Impl = ArraySerializer<ArrayDataView<Element>,
+ MaybeConstUserType,
+ ArrayIterator<Traits, MaybeConstUserType>>;
+ using Data = typename MojomTypeTraits<ArrayDataView<Element>>::Data;
+
+ static size_t PrepareToSerialize(MaybeConstUserType& input,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input))
+ return 0;
+ ArrayIterator<Traits, MaybeConstUserType> iterator(input);
+ return Impl::GetSerializedSize(&iterator, context);
+ }
+
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buf,
+ Data** output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ if (!CallIsNullIfExists<Traits>(input)) {
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ validate_params->expected_num_elements != 0 &&
+ Traits::GetSize(input) != validate_params->expected_num_elements,
+ internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ internal::MakeMessageWithExpectedArraySize(
+ "fixed-size array has wrong number of elements",
+ Traits::GetSize(input), validate_params->expected_num_elements));
+ Data* result = Data::New(Traits::GetSize(input), buf);
+ if (result) {
+ ArrayIterator<Traits, MaybeConstUserType> iterator(input);
+ Impl::SerializeElements(&iterator, buf, result, validate_params,
+ context);
+ }
+ *output = result;
+ } else {
+ *output = nullptr;
+ }
+ }
+
+ static bool Deserialize(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!input)
+ return CallSetToNullIfExists<Traits>(output);
+ return Impl::DeserializeElements(input, output, context);
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/associated_binding.cc b/mojo/public/cpp/bindings/lib/associated_binding.cc
new file mode 100644
index 0000000000..6788e68e07
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_binding.cc
@@ -0,0 +1,62 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/associated_binding.h"
+
+namespace mojo {
+
+AssociatedBindingBase::AssociatedBindingBase() {}
+
+AssociatedBindingBase::~AssociatedBindingBase() {}
+
+void AssociatedBindingBase::AddFilter(std::unique_ptr<MessageReceiver> filter) {
+ DCHECK(endpoint_client_);
+ endpoint_client_->AddFilter(std::move(filter));
+}
+
+void AssociatedBindingBase::Close() {
+ endpoint_client_.reset();
+}
+
+void AssociatedBindingBase::CloseWithReason(uint32_t custom_reason,
+ const std::string& description) {
+ if (endpoint_client_)
+ endpoint_client_->CloseWithReason(custom_reason, description);
+ Close();
+}
+
+void AssociatedBindingBase::set_connection_error_handler(
+ const base::Closure& error_handler) {
+ DCHECK(is_bound());
+ endpoint_client_->set_connection_error_handler(error_handler);
+}
+
+void AssociatedBindingBase::set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(is_bound());
+ endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+}
+
+void AssociatedBindingBase::FlushForTesting() {
+ endpoint_client_->FlushForTesting();
+}
+
+void AssociatedBindingBase::BindImpl(
+ ScopedInterfaceEndpointHandle handle,
+ MessageReceiverWithResponderStatus* receiver,
+ std::unique_ptr<MessageReceiver> payload_validator,
+ bool expect_sync_requests,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ uint32_t interface_version) {
+ if (!handle.is_valid()) {
+ endpoint_client_.reset();
+ return;
+ }
+
+ endpoint_client_.reset(new InterfaceEndpointClient(
+ std::move(handle), receiver, std::move(payload_validator),
+ expect_sync_requests, std::move(runner), interface_version));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/associated_group.cc b/mojo/public/cpp/bindings/lib/associated_group.cc
new file mode 100644
index 0000000000..3e95eeb027
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_group.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 "mojo/public/cpp/bindings/associated_group.h"
+
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+
+namespace mojo {
+
+AssociatedGroup::AssociatedGroup() = default;
+
+AssociatedGroup::AssociatedGroup(
+ scoped_refptr<AssociatedGroupController> controller)
+ : controller_(std::move(controller)) {}
+
+AssociatedGroup::AssociatedGroup(const ScopedInterfaceEndpointHandle& handle)
+ : controller_getter_(handle.CreateGroupControllerGetter()) {}
+
+AssociatedGroup::AssociatedGroup(const AssociatedGroup& other) = default;
+
+AssociatedGroup::~AssociatedGroup() = default;
+
+AssociatedGroup& AssociatedGroup::operator=(const AssociatedGroup& other) =
+ default;
+
+AssociatedGroupController* AssociatedGroup::GetController() {
+ if (controller_)
+ return controller_.get();
+
+ return controller_getter_.Run();
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/associated_group_controller.cc b/mojo/public/cpp/bindings/lib/associated_group_controller.cc
new file mode 100644
index 0000000000..f4a9aa2852
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_group_controller.cc
@@ -0,0 +1,24 @@
+// 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 "mojo/public/cpp/bindings/associated_group_controller.h"
+
+#include "mojo/public/cpp/bindings/associated_group.h"
+
+namespace mojo {
+
+AssociatedGroupController::~AssociatedGroupController() {}
+
+ScopedInterfaceEndpointHandle
+AssociatedGroupController::CreateScopedInterfaceEndpointHandle(InterfaceId id) {
+ return ScopedInterfaceEndpointHandle(id, this);
+}
+
+bool AssociatedGroupController::NotifyAssociation(
+ ScopedInterfaceEndpointHandle* handle_to_send,
+ InterfaceId id) {
+ return handle_to_send->NotifyAssociation(id, this);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
new file mode 100644
index 0000000000..78281eda9a
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
@@ -0,0 +1,18 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+
+namespace mojo {
+
+void GetIsolatedInterface(ScopedInterfaceEndpointHandle handle) {
+ MessagePipe pipe;
+ scoped_refptr<internal::MultiplexRouter> router =
+ new internal::MultiplexRouter(std::move(pipe.handle0),
+ internal::MultiplexRouter::MULTI_INTERFACE,
+ false, base::ThreadTaskRunnerHandle::Get());
+ router->AssociateInterface(std::move(handle));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
new file mode 100644
index 0000000000..a4b51882d2
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
@@ -0,0 +1,157 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_
+
+#include <stdint.h>
+
+#include <algorithm> // For |std::swap()|.
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename Interface>
+class AssociatedInterfacePtrState {
+ public:
+ AssociatedInterfacePtrState() : version_(0u) {}
+
+ ~AssociatedInterfacePtrState() {
+ endpoint_client_.reset();
+ proxy_.reset();
+ }
+
+ Interface* instance() {
+ // This will be null if the object is not bound.
+ return proxy_.get();
+ }
+
+ uint32_t version() const { return version_; }
+
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback) {
+ // It is safe to capture |this| because the callback won't be run after this
+ // object goes away.
+ endpoint_client_->QueryVersion(
+ base::Bind(&AssociatedInterfacePtrState::OnQueryVersion,
+ base::Unretained(this), callback));
+ }
+
+ void RequireVersion(uint32_t version) {
+ if (version <= version_)
+ return;
+
+ version_ = version;
+ endpoint_client_->RequireVersion(version);
+ }
+
+ void FlushForTesting() { endpoint_client_->FlushForTesting(); }
+
+ void CloseWithReason(uint32_t custom_reason, const std::string& description) {
+ endpoint_client_->CloseWithReason(custom_reason, description);
+ }
+
+ void Swap(AssociatedInterfacePtrState* other) {
+ using std::swap;
+ swap(other->endpoint_client_, endpoint_client_);
+ swap(other->proxy_, proxy_);
+ swap(other->version_, version_);
+ }
+
+ void Bind(AssociatedInterfacePtrInfo<Interface> info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ DCHECK(!endpoint_client_);
+ DCHECK(!proxy_);
+ DCHECK_EQ(0u, version_);
+ DCHECK(info.is_valid());
+
+ version_ = info.version();
+ // The version is only queried from the client so the value passed here
+ // will not be used.
+ endpoint_client_.reset(new InterfaceEndpointClient(
+ info.PassHandle(), nullptr,
+ base::WrapUnique(new typename Interface::ResponseValidator_()), false,
+ std::move(runner), 0u));
+ proxy_.reset(new Proxy(endpoint_client_.get()));
+ }
+
+ // After this method is called, the object is in an invalid state and
+ // shouldn't be reused.
+ AssociatedInterfacePtrInfo<Interface> PassInterface() {
+ ScopedInterfaceEndpointHandle handle = endpoint_client_->PassHandle();
+ endpoint_client_.reset();
+ proxy_.reset();
+ return AssociatedInterfacePtrInfo<Interface>(std::move(handle), version_);
+ }
+
+ bool is_bound() const { return !!endpoint_client_; }
+
+ bool encountered_error() const {
+ return endpoint_client_ ? endpoint_client_->encountered_error() : false;
+ }
+
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(endpoint_client_);
+ endpoint_client_->set_connection_error_handler(error_handler);
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(endpoint_client_);
+ endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+ }
+
+ // Returns true if bound and awaiting a response to a message.
+ bool has_pending_callbacks() const {
+ return endpoint_client_ && endpoint_client_->has_pending_responders();
+ }
+
+ AssociatedGroup* associated_group() {
+ return endpoint_client_ ? endpoint_client_->associated_group() : nullptr;
+ }
+
+ void ForwardMessage(Message message) { endpoint_client_->Accept(&message); }
+
+ void ForwardMessageWithResponder(Message message,
+ std::unique_ptr<MessageReceiver> responder) {
+ endpoint_client_->AcceptWithResponder(&message, std::move(responder));
+ }
+
+ private:
+ using Proxy = typename Interface::Proxy_;
+
+ void OnQueryVersion(const base::Callback<void(uint32_t)>& callback,
+ uint32_t version) {
+ version_ = version;
+ callback.Run(version);
+ }
+
+ std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+ std::unique_ptr<Proxy> proxy_;
+
+ uint32_t version_;
+
+ DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrState);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_
diff --git a/mojo/public/cpp/bindings/lib/binding_state.cc b/mojo/public/cpp/bindings/lib/binding_state.cc
new file mode 100644
index 0000000000..b34cb47e28
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/binding_state.cc
@@ -0,0 +1,90 @@
+// 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 "mojo/public/cpp/bindings/lib/binding_state.h"
+
+namespace mojo {
+namespace internal {
+
+BindingStateBase::BindingStateBase() = default;
+
+BindingStateBase::~BindingStateBase() = default;
+
+void BindingStateBase::AddFilter(std::unique_ptr<MessageReceiver> filter) {
+ DCHECK(endpoint_client_);
+ endpoint_client_->AddFilter(std::move(filter));
+}
+
+bool BindingStateBase::HasAssociatedInterfaces() const {
+ return router_ ? router_->HasAssociatedEndpoints() : false;
+}
+
+void BindingStateBase::PauseIncomingMethodCallProcessing() {
+ DCHECK(router_);
+ router_->PauseIncomingMethodCallProcessing();
+}
+void BindingStateBase::ResumeIncomingMethodCallProcessing() {
+ DCHECK(router_);
+ router_->ResumeIncomingMethodCallProcessing();
+}
+
+bool BindingStateBase::WaitForIncomingMethodCall(MojoDeadline deadline) {
+ DCHECK(router_);
+ return router_->WaitForIncomingMessage(deadline);
+}
+
+void BindingStateBase::Close() {
+ if (!router_)
+ return;
+
+ endpoint_client_.reset();
+ router_->CloseMessagePipe();
+ router_ = nullptr;
+}
+
+void BindingStateBase::CloseWithReason(uint32_t custom_reason,
+ const std::string& description) {
+ if (endpoint_client_)
+ endpoint_client_->CloseWithReason(custom_reason, description);
+
+ Close();
+}
+
+void BindingStateBase::FlushForTesting() {
+ endpoint_client_->FlushForTesting();
+}
+
+void BindingStateBase::EnableTestingMode() {
+ DCHECK(is_bound());
+ router_->EnableTestingMode();
+}
+
+void BindingStateBase::BindInternal(
+ ScopedMessagePipeHandle handle,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const char* interface_name,
+ std::unique_ptr<MessageReceiver> request_validator,
+ bool passes_associated_kinds,
+ bool has_sync_methods,
+ MessageReceiverWithResponderStatus* stub,
+ uint32_t interface_version) {
+ DCHECK(!router_);
+
+ MultiplexRouter::Config config =
+ passes_associated_kinds
+ ? MultiplexRouter::MULTI_INTERFACE
+ : (has_sync_methods
+ ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS
+ : MultiplexRouter::SINGLE_INTERFACE);
+ router_ = new MultiplexRouter(std::move(handle), config, false, runner);
+ router_->SetMasterInterfaceName(interface_name);
+
+ endpoint_client_.reset(new InterfaceEndpointClient(
+ router_->CreateLocalEndpointHandle(kMasterInterfaceId), stub,
+ std::move(request_validator), has_sync_methods, std::move(runner),
+ interface_version));
+}
+
+} // namesapce internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/binding_state.h b/mojo/public/cpp/bindings/lib/binding_state.h
new file mode 100644
index 0000000000..0b0dbee002
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/binding_state.h
@@ -0,0 +1,128 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+#include "mojo/public/cpp/bindings/message_header_validator.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace internal {
+
+class MOJO_CPP_BINDINGS_EXPORT BindingStateBase {
+ public:
+ BindingStateBase();
+ ~BindingStateBase();
+
+ void AddFilter(std::unique_ptr<MessageReceiver> filter);
+
+ bool HasAssociatedInterfaces() const;
+
+ void PauseIncomingMethodCallProcessing();
+ void ResumeIncomingMethodCallProcessing();
+
+ bool WaitForIncomingMethodCall(
+ MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE);
+
+ void Close();
+ void CloseWithReason(uint32_t custom_reason, const std::string& description);
+
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(is_bound());
+ endpoint_client_->set_connection_error_handler(error_handler);
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(is_bound());
+ endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+ }
+
+ bool is_bound() const { return !!router_; }
+
+ MessagePipeHandle handle() const {
+ DCHECK(is_bound());
+ return router_->handle();
+ }
+
+ void FlushForTesting();
+
+ void EnableTestingMode();
+
+ protected:
+ void BindInternal(ScopedMessagePipeHandle handle,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const char* interface_name,
+ std::unique_ptr<MessageReceiver> request_validator,
+ bool passes_associated_kinds,
+ bool has_sync_methods,
+ MessageReceiverWithResponderStatus* stub,
+ uint32_t interface_version);
+
+ scoped_refptr<internal::MultiplexRouter> router_;
+ std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+};
+
+template <typename Interface, typename ImplRefTraits>
+class BindingState : public BindingStateBase {
+ public:
+ using ImplPointerType = typename ImplRefTraits::PointerType;
+
+ explicit BindingState(ImplPointerType impl) {
+ stub_.set_sink(std::move(impl));
+ }
+
+ ~BindingState() { Close(); }
+
+ void Bind(ScopedMessagePipeHandle handle,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ BindingStateBase::BindInternal(
+ std::move(handle), runner, Interface::Name_,
+ base::MakeUnique<typename Interface::RequestValidator_>(),
+ Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, &stub_,
+ Interface::Version_);
+ }
+
+ InterfaceRequest<Interface> Unbind() {
+ endpoint_client_.reset();
+ InterfaceRequest<Interface> request =
+ MakeRequest<Interface>(router_->PassMessagePipe());
+ router_ = nullptr;
+ return request;
+ }
+
+ Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); }
+
+ private:
+ typename Interface::template Stub_<ImplRefTraits> stub_;
+
+ DISALLOW_COPY_AND_ASSIGN(BindingState);
+};
+
+} // namesapce internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_
diff --git a/mojo/public/cpp/bindings/lib/bindings_internal.h b/mojo/public/cpp/bindings/lib/bindings_internal.h
new file mode 100644
index 0000000000..631daec392
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/bindings_internal.h
@@ -0,0 +1,336 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_
+
+#include <stdint.h>
+
+#include <functional>
+
+#include "base/template_util.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+template <typename T>
+class ArrayDataView;
+
+template <typename T>
+class AssociatedInterfacePtrInfoDataView;
+
+template <typename T>
+class AssociatedInterfaceRequestDataView;
+
+template <typename T>
+class InterfacePtrDataView;
+
+template <typename T>
+class InterfaceRequestDataView;
+
+template <typename K, typename V>
+class MapDataView;
+
+class NativeStructDataView;
+
+class StringDataView;
+
+namespace internal {
+
+// Please note that this is a different value than |mojo::kInvalidHandleValue|,
+// which is the "decoded" invalid handle.
+const uint32_t kEncodedInvalidHandleValue = static_cast<uint32_t>(-1);
+
+// A serialized union always takes 16 bytes:
+// 4-byte size + 4-byte tag + 8-byte payload.
+const uint32_t kUnionDataSize = 16;
+
+template <typename T>
+class Array_Data;
+
+template <typename K, typename V>
+class Map_Data;
+
+class NativeStruct_Data;
+
+using String_Data = Array_Data<char>;
+
+inline size_t Align(size_t size) {
+ return (size + 7) & ~0x7;
+}
+
+inline bool IsAligned(const void* ptr) {
+ return !(reinterpret_cast<uintptr_t>(ptr) & 0x7);
+}
+
+// Pointers are encoded as relative offsets. The offsets are relative to the
+// address of where the offset value is stored, such that the pointer may be
+// recovered with the expression:
+//
+// ptr = reinterpret_cast<char*>(offset) + *offset
+//
+// A null pointer is encoded as an offset value of 0.
+//
+inline void EncodePointer(const void* ptr, uint64_t* offset) {
+ if (!ptr) {
+ *offset = 0;
+ return;
+ }
+
+ const char* p_obj = reinterpret_cast<const char*>(ptr);
+ const char* p_slot = reinterpret_cast<const char*>(offset);
+ DCHECK(p_obj > p_slot);
+
+ *offset = static_cast<uint64_t>(p_obj - p_slot);
+}
+
+// Note: This function doesn't validate the encoded pointer value.
+inline const void* DecodePointer(const uint64_t* offset) {
+ if (!*offset)
+ return nullptr;
+ return reinterpret_cast<const char*>(offset) + *offset;
+}
+
+#pragma pack(push, 1)
+
+struct StructHeader {
+ uint32_t num_bytes;
+ uint32_t version;
+};
+static_assert(sizeof(StructHeader) == 8, "Bad sizeof(StructHeader)");
+
+struct ArrayHeader {
+ uint32_t num_bytes;
+ uint32_t num_elements;
+};
+static_assert(sizeof(ArrayHeader) == 8, "Bad_sizeof(ArrayHeader)");
+
+template <typename T>
+struct Pointer {
+ using BaseType = T;
+
+ void Set(T* ptr) { EncodePointer(ptr, &offset); }
+ const T* Get() const { return static_cast<const T*>(DecodePointer(&offset)); }
+ T* Get() {
+ return static_cast<T*>(const_cast<void*>(DecodePointer(&offset)));
+ }
+
+ bool is_null() const { return offset == 0; }
+
+ uint64_t offset;
+};
+static_assert(sizeof(Pointer<char>) == 8, "Bad_sizeof(Pointer)");
+
+using GenericPointer = Pointer<void>;
+
+struct Handle_Data {
+ Handle_Data() = default;
+ explicit Handle_Data(uint32_t value) : value(value) {}
+
+ bool is_valid() const { return value != kEncodedInvalidHandleValue; }
+
+ uint32_t value;
+};
+static_assert(sizeof(Handle_Data) == 4, "Bad_sizeof(Handle_Data)");
+
+struct Interface_Data {
+ Handle_Data handle;
+ uint32_t version;
+};
+static_assert(sizeof(Interface_Data) == 8, "Bad_sizeof(Interface_Data)");
+
+struct AssociatedEndpointHandle_Data {
+ AssociatedEndpointHandle_Data() = default;
+ explicit AssociatedEndpointHandle_Data(uint32_t value) : value(value) {}
+
+ bool is_valid() const { return value != kEncodedInvalidHandleValue; }
+
+ uint32_t value;
+};
+static_assert(sizeof(AssociatedEndpointHandle_Data) == 4,
+ "Bad_sizeof(AssociatedEndpointHandle_Data)");
+
+struct AssociatedInterface_Data {
+ AssociatedEndpointHandle_Data handle;
+ uint32_t version;
+};
+static_assert(sizeof(AssociatedInterface_Data) == 8,
+ "Bad_sizeof(AssociatedInterface_Data)");
+
+#pragma pack(pop)
+
+template <typename T>
+T FetchAndReset(T* ptr) {
+ T temp = *ptr;
+ *ptr = T();
+ return temp;
+}
+
+template <typename T>
+struct IsUnionDataType {
+ private:
+ template <typename U>
+ static YesType Test(const typename U::MojomUnionDataType*);
+
+ template <typename U>
+ static NoType Test(...);
+
+ EnsureTypeIsComplete<T> check_t_;
+
+ public:
+ static const bool value =
+ sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value;
+};
+
+enum class MojomTypeCategory : uint32_t {
+ ARRAY = 1 << 0,
+ ASSOCIATED_INTERFACE = 1 << 1,
+ ASSOCIATED_INTERFACE_REQUEST = 1 << 2,
+ BOOLEAN = 1 << 3,
+ ENUM = 1 << 4,
+ HANDLE = 1 << 5,
+ INTERFACE = 1 << 6,
+ INTERFACE_REQUEST = 1 << 7,
+ MAP = 1 << 8,
+ // POD except boolean and enum.
+ POD = 1 << 9,
+ STRING = 1 << 10,
+ STRUCT = 1 << 11,
+ UNION = 1 << 12
+};
+
+inline constexpr MojomTypeCategory operator&(MojomTypeCategory x,
+ MojomTypeCategory y) {
+ return static_cast<MojomTypeCategory>(static_cast<uint32_t>(x) &
+ static_cast<uint32_t>(y));
+}
+
+inline constexpr MojomTypeCategory operator|(MojomTypeCategory x,
+ MojomTypeCategory y) {
+ return static_cast<MojomTypeCategory>(static_cast<uint32_t>(x) |
+ static_cast<uint32_t>(y));
+}
+
+template <typename T, bool is_enum = std::is_enum<T>::value>
+struct MojomTypeTraits {
+ using Data = T;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category = MojomTypeCategory::POD;
+};
+
+template <typename T>
+struct MojomTypeTraits<ArrayDataView<T>, false> {
+ using Data = Array_Data<typename MojomTypeTraits<T>::DataAsArrayElement>;
+ using DataAsArrayElement = Pointer<Data>;
+
+ static const MojomTypeCategory category = MojomTypeCategory::ARRAY;
+};
+
+template <typename T>
+struct MojomTypeTraits<AssociatedInterfacePtrInfoDataView<T>, false> {
+ using Data = AssociatedInterface_Data;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category =
+ MojomTypeCategory::ASSOCIATED_INTERFACE;
+};
+
+template <typename T>
+struct MojomTypeTraits<AssociatedInterfaceRequestDataView<T>, false> {
+ using Data = AssociatedEndpointHandle_Data;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category =
+ MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST;
+};
+
+template <>
+struct MojomTypeTraits<bool, false> {
+ using Data = bool;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category = MojomTypeCategory::BOOLEAN;
+};
+
+template <typename T>
+struct MojomTypeTraits<T, true> {
+ using Data = int32_t;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category = MojomTypeCategory::ENUM;
+};
+
+template <typename T>
+struct MojomTypeTraits<ScopedHandleBase<T>, false> {
+ using Data = Handle_Data;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category = MojomTypeCategory::HANDLE;
+};
+
+template <typename T>
+struct MojomTypeTraits<InterfacePtrDataView<T>, false> {
+ using Data = Interface_Data;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category = MojomTypeCategory::INTERFACE;
+};
+
+template <typename T>
+struct MojomTypeTraits<InterfaceRequestDataView<T>, false> {
+ using Data = Handle_Data;
+ using DataAsArrayElement = Data;
+
+ static const MojomTypeCategory category =
+ MojomTypeCategory::INTERFACE_REQUEST;
+};
+
+template <typename K, typename V>
+struct MojomTypeTraits<MapDataView<K, V>, false> {
+ using Data = Map_Data<typename MojomTypeTraits<K>::DataAsArrayElement,
+ typename MojomTypeTraits<V>::DataAsArrayElement>;
+ using DataAsArrayElement = Pointer<Data>;
+
+ static const MojomTypeCategory category = MojomTypeCategory::MAP;
+};
+
+template <>
+struct MojomTypeTraits<NativeStructDataView, false> {
+ using Data = internal::NativeStruct_Data;
+ using DataAsArrayElement = Pointer<Data>;
+
+ static const MojomTypeCategory category = MojomTypeCategory::STRUCT;
+};
+
+template <>
+struct MojomTypeTraits<StringDataView, false> {
+ using Data = String_Data;
+ using DataAsArrayElement = Pointer<Data>;
+
+ static const MojomTypeCategory category = MojomTypeCategory::STRING;
+};
+
+template <typename T, MojomTypeCategory categories>
+struct BelongsTo {
+ static const bool value =
+ static_cast<uint32_t>(MojomTypeTraits<T>::category & categories) != 0;
+};
+
+template <typename T>
+struct EnumHashImpl {
+ static_assert(std::is_enum<T>::value, "Incorrect hash function.");
+
+ size_t operator()(T input) const {
+ using UnderlyingType = typename base::underlying_type<T>::type;
+ return std::hash<UnderlyingType>()(static_cast<UnderlyingType>(input));
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/lib/buffer.h b/mojo/public/cpp/bindings/lib/buffer.h
new file mode 100644
index 0000000000..213a44590f
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/buffer.h
@@ -0,0 +1,70 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+
+namespace mojo {
+namespace internal {
+
+// Buffer provides an interface to allocate memory blocks which are 8-byte
+// aligned and zero-initialized. It doesn't own the underlying memory. Users
+// must ensure that the memory stays valid while using the allocated blocks from
+// Buffer.
+class Buffer {
+ public:
+ Buffer() {}
+
+ // The memory must have been zero-initialized. |data| must be 8-byte
+ // aligned.
+ void Initialize(void* data, size_t size) {
+ DCHECK(IsAligned(data));
+
+ data_ = data;
+ size_ = size;
+ cursor_ = reinterpret_cast<uintptr_t>(data);
+ data_end_ = cursor_ + size;
+ }
+
+ size_t size() const { return size_; }
+
+ void* data() const { return data_; }
+
+ // Allocates |num_bytes| from the buffer and returns a pointer to the start of
+ // the allocated block.
+ // The resulting address is 8-byte aligned, and the content of the memory is
+ // zero-filled.
+ void* Allocate(size_t num_bytes) {
+ num_bytes = Align(num_bytes);
+ uintptr_t result = cursor_;
+ cursor_ += num_bytes;
+ if (cursor_ > data_end_ || cursor_ < result) {
+ NOTREACHED();
+ cursor_ -= num_bytes;
+ return nullptr;
+ }
+
+ return reinterpret_cast<void*>(result);
+ }
+
+ private:
+ void* data_ = nullptr;
+ size_t size_ = 0;
+
+ uintptr_t cursor_ = 0;
+ uintptr_t data_end_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(Buffer);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_
diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc
new file mode 100644
index 0000000000..d93e45ed93
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/connector.cc
@@ -0,0 +1,493 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/connector.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_local.h"
+#include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
+#include "mojo/public/cpp/bindings/sync_handle_watcher.h"
+#include "mojo/public/cpp/system/wait.h"
+
+namespace mojo {
+
+namespace {
+
+// The NestingObserver for each thread. Note that this is always a
+// Connector::MessageLoopNestingObserver; we use the base type here because that
+// subclass is private to Connector.
+base::LazyInstance<
+ base::ThreadLocalPointer<base::MessageLoop::NestingObserver>>::Leaky
+ g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Used to efficiently maintain a doubly-linked list of all Connectors
+// currently dispatching on any given thread.
+class Connector::ActiveDispatchTracker {
+ public:
+ explicit ActiveDispatchTracker(const base::WeakPtr<Connector>& connector);
+ ~ActiveDispatchTracker();
+
+ void NotifyBeginNesting();
+
+ private:
+ const base::WeakPtr<Connector> connector_;
+ MessageLoopNestingObserver* const nesting_observer_;
+ ActiveDispatchTracker* outer_tracker_ = nullptr;
+ ActiveDispatchTracker* inner_tracker_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(ActiveDispatchTracker);
+};
+
+// Watches the MessageLoop on the current thread. Notifies the current chain of
+// ActiveDispatchTrackers when a nested message loop is started.
+class Connector::MessageLoopNestingObserver
+ : public base::MessageLoop::NestingObserver,
+ public base::MessageLoop::DestructionObserver {
+ public:
+ MessageLoopNestingObserver() {
+ base::MessageLoop::current()->AddNestingObserver(this);
+ base::MessageLoop::current()->AddDestructionObserver(this);
+ }
+
+ ~MessageLoopNestingObserver() override {}
+
+ // base::MessageLoop::NestingObserver:
+ void OnBeginNestedMessageLoop() override {
+ if (top_tracker_)
+ top_tracker_->NotifyBeginNesting();
+ }
+
+ // base::MessageLoop::DestructionObserver:
+ void WillDestroyCurrentMessageLoop() override {
+ base::MessageLoop::current()->RemoveNestingObserver(this);
+ base::MessageLoop::current()->RemoveDestructionObserver(this);
+ DCHECK_EQ(this, g_tls_nesting_observer.Get().Get());
+ g_tls_nesting_observer.Get().Set(nullptr);
+ delete this;
+ }
+
+ static MessageLoopNestingObserver* GetForThread() {
+ if (!base::MessageLoop::current() ||
+ !base::MessageLoop::current()->nesting_allowed())
+ return nullptr;
+ auto* observer = static_cast<MessageLoopNestingObserver*>(
+ g_tls_nesting_observer.Get().Get());
+ if (!observer) {
+ observer = new MessageLoopNestingObserver;
+ g_tls_nesting_observer.Get().Set(observer);
+ }
+ return observer;
+ }
+
+ private:
+ friend class ActiveDispatchTracker;
+
+ ActiveDispatchTracker* top_tracker_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoopNestingObserver);
+};
+
+Connector::ActiveDispatchTracker::ActiveDispatchTracker(
+ const base::WeakPtr<Connector>& connector)
+ : connector_(connector), nesting_observer_(connector_->nesting_observer_) {
+ DCHECK(nesting_observer_);
+ if (nesting_observer_->top_tracker_) {
+ outer_tracker_ = nesting_observer_->top_tracker_;
+ outer_tracker_->inner_tracker_ = this;
+ }
+ nesting_observer_->top_tracker_ = this;
+}
+
+Connector::ActiveDispatchTracker::~ActiveDispatchTracker() {
+ if (nesting_observer_->top_tracker_ == this)
+ nesting_observer_->top_tracker_ = outer_tracker_;
+ else if (inner_tracker_)
+ inner_tracker_->outer_tracker_ = outer_tracker_;
+ if (outer_tracker_)
+ outer_tracker_->inner_tracker_ = inner_tracker_;
+}
+
+void Connector::ActiveDispatchTracker::NotifyBeginNesting() {
+ if (connector_ && connector_->handle_watcher_)
+ connector_->handle_watcher_->ArmOrNotify();
+ if (outer_tracker_)
+ outer_tracker_->NotifyBeginNesting();
+}
+
+Connector::Connector(ScopedMessagePipeHandle message_pipe,
+ ConnectorConfig config,
+ scoped_refptr<base::SingleThreadTaskRunner> runner)
+ : message_pipe_(std::move(message_pipe)),
+ task_runner_(std::move(runner)),
+ nesting_observer_(MessageLoopNestingObserver::GetForThread()),
+ weak_factory_(this) {
+ if (config == MULTI_THREADED_SEND)
+ lock_.emplace();
+
+ weak_self_ = weak_factory_.GetWeakPtr();
+ // Even though we don't have an incoming receiver, we still want to monitor
+ // the message pipe to know if is closed or encounters an error.
+ WaitToReadMore();
+}
+
+Connector::~Connector() {
+ {
+ // Allow for quick destruction on any thread if the pipe is already closed.
+ base::AutoLock lock(connected_lock_);
+ if (!connected_)
+ return;
+ }
+
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CancelWait();
+}
+
+void Connector::CloseMessagePipe() {
+ // Throw away the returned message pipe.
+ PassMessagePipe();
+}
+
+ScopedMessagePipeHandle Connector::PassMessagePipe() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ CancelWait();
+ internal::MayAutoLock locker(&lock_);
+ ScopedMessagePipeHandle message_pipe = std::move(message_pipe_);
+ weak_factory_.InvalidateWeakPtrs();
+ sync_handle_watcher_callback_count_ = 0;
+
+ base::AutoLock lock(connected_lock_);
+ connected_ = false;
+ return message_pipe;
+}
+
+void Connector::RaiseError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ HandleError(true, true);
+}
+
+bool Connector::WaitForIncomingMessage(MojoDeadline deadline) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error_)
+ return false;
+
+ ResumeIncomingMethodCallProcessing();
+
+ // TODO(rockot): Use a timed Wait here. Nobody uses anything but 0 or
+ // INDEFINITE deadlines at present, so we only support those.
+ DCHECK(deadline == 0 || deadline == MOJO_DEADLINE_INDEFINITE);
+
+ MojoResult rv = MOJO_RESULT_UNKNOWN;
+ if (deadline == 0 && !message_pipe_->QuerySignalsState().readable())
+ return false;
+
+ if (deadline == MOJO_DEADLINE_INDEFINITE) {
+ rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ if (rv != MOJO_RESULT_OK) {
+ // Users that call WaitForIncomingMessage() should expect their code to be
+ // re-entered, so we call the error handler synchronously.
+ HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false);
+ return false;
+ }
+ }
+
+ ignore_result(ReadSingleMessage(&rv));
+ return (rv == MOJO_RESULT_OK);
+}
+
+void Connector::PauseIncomingMethodCallProcessing() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (paused_)
+ return;
+
+ paused_ = true;
+ CancelWait();
+}
+
+void Connector::ResumeIncomingMethodCallProcessing() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!paused_)
+ return;
+
+ paused_ = false;
+ WaitToReadMore();
+}
+
+bool Connector::Accept(Message* message) {
+ DCHECK(lock_ || thread_checker_.CalledOnValidThread());
+
+ // It shouldn't hurt even if |error_| may be changed by a different thread at
+ // the same time. The outcome is that we may write into |message_pipe_| after
+ // encountering an error, which should be fine.
+ if (error_)
+ return false;
+
+ internal::MayAutoLock locker(&lock_);
+
+ if (!message_pipe_.is_valid() || drop_writes_)
+ return true;
+
+ MojoResult rv =
+ WriteMessageNew(message_pipe_.get(), message->TakeMojoMessage(),
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+
+ switch (rv) {
+ case MOJO_RESULT_OK:
+ break;
+ case MOJO_RESULT_FAILED_PRECONDITION:
+ // There's no point in continuing to write to this pipe since the other
+ // end is gone. Avoid writing any future messages. Hide write failures
+ // from the caller since we'd like them to continue consuming any backlog
+ // of incoming messages before regarding the message pipe as closed.
+ drop_writes_ = true;
+ break;
+ case MOJO_RESULT_BUSY:
+ // We'd get a "busy" result if one of the message's handles is:
+ // - |message_pipe_|'s own handle;
+ // - simultaneously being used on another thread; or
+ // - in a "busy" state that prohibits it from being transferred (e.g.,
+ // a data pipe handle in the middle of a two-phase read/write,
+ // regardless of which thread that two-phase read/write is happening
+ // on).
+ // TODO(vtl): I wonder if this should be a |DCHECK()|. (But, until
+ // crbug.com/389666, etc. are resolved, this will make tests fail quickly
+ // rather than hanging.)
+ CHECK(false) << "Race condition or other bug detected";
+ return false;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
+ }
+ return true;
+}
+
+void Connector::AllowWokenUpBySyncWatchOnSameThread() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ allow_woken_up_by_others_ = true;
+
+ EnsureSyncWatcherExists();
+ sync_watcher_->AllowWokenUpBySyncWatchOnSameThread();
+}
+
+bool Connector::SyncWatch(const bool* should_stop) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error_)
+ return false;
+
+ ResumeIncomingMethodCallProcessing();
+
+ EnsureSyncWatcherExists();
+ return sync_watcher_->SyncWatch(should_stop);
+}
+
+void Connector::SetWatcherHeapProfilerTag(const char* tag) {
+ heap_profiler_tag_ = tag;
+ if (handle_watcher_) {
+ handle_watcher_->set_heap_profiler_tag(tag);
+ }
+}
+
+void Connector::OnWatcherHandleReady(MojoResult result) {
+ OnHandleReadyInternal(result);
+}
+
+void Connector::OnSyncHandleWatcherHandleReady(MojoResult result) {
+ base::WeakPtr<Connector> weak_self(weak_self_);
+
+ sync_handle_watcher_callback_count_++;
+ OnHandleReadyInternal(result);
+ // At this point, this object might have been deleted.
+ if (weak_self) {
+ DCHECK_LT(0u, sync_handle_watcher_callback_count_);
+ sync_handle_watcher_callback_count_--;
+ }
+}
+
+void Connector::OnHandleReadyInternal(MojoResult result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (result != MOJO_RESULT_OK) {
+ HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false);
+ return;
+ }
+
+ ReadAllAvailableMessages();
+ // At this point, this object might have been deleted. Return.
+}
+
+void Connector::WaitToReadMore() {
+ CHECK(!paused_);
+ DCHECK(!handle_watcher_);
+
+ handle_watcher_.reset(new SimpleWatcher(
+ FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL, task_runner_));
+ if (heap_profiler_tag_)
+ handle_watcher_->set_heap_profiler_tag(heap_profiler_tag_);
+ MojoResult rv = handle_watcher_->Watch(
+ message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&Connector::OnWatcherHandleReady, base::Unretained(this)));
+
+ if (rv != MOJO_RESULT_OK) {
+ // If the watch failed because the handle is invalid or its conditions can
+ // no longer be met, we signal the error asynchronously to avoid reentry.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Connector::OnWatcherHandleReady, weak_self_, rv));
+ } else {
+ handle_watcher_->ArmOrNotify();
+ }
+
+ if (allow_woken_up_by_others_) {
+ EnsureSyncWatcherExists();
+ sync_watcher_->AllowWokenUpBySyncWatchOnSameThread();
+ }
+}
+
+bool Connector::ReadSingleMessage(MojoResult* read_result) {
+ CHECK(!paused_);
+
+ bool receiver_result = false;
+
+ // Detect if |this| was destroyed or the message pipe was closed/transferred
+ // during message dispatch.
+ base::WeakPtr<Connector> weak_self = weak_self_;
+
+ Message message;
+ const MojoResult rv = ReadMessage(message_pipe_.get(), &message);
+ *read_result = rv;
+
+ if (rv == MOJO_RESULT_OK) {
+ base::Optional<ActiveDispatchTracker> dispatch_tracker;
+ if (!is_dispatching_ && nesting_observer_) {
+ is_dispatching_ = true;
+ dispatch_tracker.emplace(weak_self);
+ }
+
+ receiver_result =
+ incoming_receiver_ && incoming_receiver_->Accept(&message);
+
+ if (!weak_self)
+ return false;
+
+ if (dispatch_tracker) {
+ is_dispatching_ = false;
+ dispatch_tracker.reset();
+ }
+ } else if (rv == MOJO_RESULT_SHOULD_WAIT) {
+ return true;
+ } else {
+ HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false);
+ return false;
+ }
+
+ if (enforce_errors_from_incoming_receiver_ && !receiver_result) {
+ HandleError(true, false);
+ return false;
+ }
+ return true;
+}
+
+void Connector::ReadAllAvailableMessages() {
+ while (!error_) {
+ base::WeakPtr<Connector> weak_self = weak_self_;
+ MojoResult rv;
+
+ // May delete |this.|
+ if (!ReadSingleMessage(&rv))
+ return;
+
+ if (!weak_self || paused_)
+ return;
+
+ DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_SHOULD_WAIT);
+
+ if (rv == MOJO_RESULT_SHOULD_WAIT) {
+ // Attempt to re-arm the Watcher.
+ MojoResult ready_result;
+ MojoResult arm_result = handle_watcher_->Arm(&ready_result);
+ if (arm_result == MOJO_RESULT_OK)
+ return;
+
+ // The watcher is already ready to notify again.
+ DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, arm_result);
+
+ if (ready_result == MOJO_RESULT_FAILED_PRECONDITION) {
+ HandleError(false, false);
+ return;
+ }
+
+ // There's more to read now, so we'll just keep looping.
+ DCHECK_EQ(MOJO_RESULT_OK, ready_result);
+ }
+ }
+}
+
+void Connector::CancelWait() {
+ handle_watcher_.reset();
+ sync_watcher_.reset();
+}
+
+void Connector::HandleError(bool force_pipe_reset, bool force_async_handler) {
+ if (error_ || !message_pipe_.is_valid())
+ return;
+
+ if (paused_) {
+ // Enforce calling the error handler asynchronously if the user has paused
+ // receiving messages. We need to wait until the user starts receiving
+ // messages again.
+ force_async_handler = true;
+ }
+
+ if (!force_pipe_reset && force_async_handler)
+ force_pipe_reset = true;
+
+ if (force_pipe_reset) {
+ CancelWait();
+ internal::MayAutoLock locker(&lock_);
+ message_pipe_.reset();
+ MessagePipe dummy_pipe;
+ message_pipe_ = std::move(dummy_pipe.handle0);
+ } else {
+ CancelWait();
+ }
+
+ if (force_async_handler) {
+ if (!paused_)
+ WaitToReadMore();
+ } else {
+ error_ = true;
+ if (!connection_error_handler_.is_null())
+ connection_error_handler_.Run();
+ }
+}
+
+void Connector::EnsureSyncWatcherExists() {
+ if (sync_watcher_)
+ return;
+ sync_watcher_.reset(new SyncHandleWatcher(
+ message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&Connector::OnSyncHandleWatcherHandleReady,
+ base::Unretained(this))));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc
new file mode 100644
index 0000000000..1b7bb78e5f
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc
@@ -0,0 +1,150 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/lib/control_message_handler.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h"
+
+namespace mojo {
+namespace internal {
+namespace {
+
+bool ValidateControlRequestWithResponse(Message* message) {
+ ValidationContext validation_context(message->payload(),
+ message->payload_num_bytes(), 0, 0,
+ message, "ControlRequestValidator");
+ if (!ValidateMessageIsRequestExpectingResponse(message, &validation_context))
+ return false;
+
+ switch (message->header()->name) {
+ case interface_control::kRunMessageId:
+ return ValidateMessagePayload<
+ interface_control::internal::RunMessageParams_Data>(
+ message, &validation_context);
+ }
+ return false;
+}
+
+bool ValidateControlRequestWithoutResponse(Message* message) {
+ ValidationContext validation_context(message->payload(),
+ message->payload_num_bytes(), 0, 0,
+ message, "ControlRequestValidator");
+ if (!ValidateMessageIsRequestWithoutResponse(message, &validation_context))
+ return false;
+
+ switch (message->header()->name) {
+ case interface_control::kRunOrClosePipeMessageId:
+ return ValidateMessageIsRequestWithoutResponse(message,
+ &validation_context) &&
+ ValidateMessagePayload<
+ interface_control::internal::RunOrClosePipeMessageParams_Data>(
+ message, &validation_context);
+ }
+ return false;
+}
+
+} // namespace
+
+// static
+bool ControlMessageHandler::IsControlMessage(const Message* message) {
+ return message->header()->name == interface_control::kRunMessageId ||
+ message->header()->name == interface_control::kRunOrClosePipeMessageId;
+}
+
+ControlMessageHandler::ControlMessageHandler(uint32_t interface_version)
+ : interface_version_(interface_version) {
+}
+
+ControlMessageHandler::~ControlMessageHandler() {
+}
+
+bool ControlMessageHandler::Accept(Message* message) {
+ if (!ValidateControlRequestWithoutResponse(message))
+ return false;
+
+ if (message->header()->name == interface_control::kRunOrClosePipeMessageId)
+ return RunOrClosePipe(message);
+
+ NOTREACHED();
+ return false;
+}
+
+bool ControlMessageHandler::AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) {
+ if (!ValidateControlRequestWithResponse(message))
+ return false;
+
+ if (message->header()->name == interface_control::kRunMessageId)
+ return Run(message, std::move(responder));
+
+ NOTREACHED();
+ return false;
+}
+
+bool ControlMessageHandler::Run(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) {
+ interface_control::internal::RunMessageParams_Data* params =
+ reinterpret_cast<interface_control::internal::RunMessageParams_Data*>(
+ message->mutable_payload());
+ interface_control::RunMessageParamsPtr params_ptr;
+ Deserialize<interface_control::RunMessageParamsDataView>(params, &params_ptr,
+ &context_);
+ auto& input = *params_ptr->input;
+ interface_control::RunOutputPtr output = interface_control::RunOutput::New();
+ if (input.is_query_version()) {
+ output->set_query_version_result(
+ interface_control::QueryVersionResult::New());
+ output->get_query_version_result()->version = interface_version_;
+ } else if (input.is_flush_for_testing()) {
+ output.reset();
+ } else {
+ output.reset();
+ }
+
+ auto response_params_ptr = interface_control::RunResponseMessageParams::New();
+ response_params_ptr->output = std::move(output);
+ size_t size =
+ PrepareToSerialize<interface_control::RunResponseMessageParamsDataView>(
+ response_params_ptr, &context_);
+ MessageBuilder builder(interface_control::kRunMessageId,
+ Message::kFlagIsResponse, size, 0);
+ builder.message()->set_request_id(message->request_id());
+
+ interface_control::internal::RunResponseMessageParams_Data* response_params =
+ nullptr;
+ Serialize<interface_control::RunResponseMessageParamsDataView>(
+ response_params_ptr, builder.buffer(), &response_params, &context_);
+ ignore_result(responder->Accept(builder.message()));
+
+ return true;
+}
+
+bool ControlMessageHandler::RunOrClosePipe(Message* message) {
+ interface_control::internal::RunOrClosePipeMessageParams_Data* params =
+ reinterpret_cast<
+ interface_control::internal::RunOrClosePipeMessageParams_Data*>(
+ message->mutable_payload());
+ interface_control::RunOrClosePipeMessageParamsPtr params_ptr;
+ Deserialize<interface_control::RunOrClosePipeMessageParamsDataView>(
+ params, &params_ptr, &context_);
+ auto& input = *params_ptr->input;
+ if (input.is_require_version())
+ return interface_version_ >= input.get_require_version()->version;
+
+ return false;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h
new file mode 100644
index 0000000000..5d1f716ea8
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/control_message_handler.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace internal {
+
+// Handlers for request messages defined in interface_control_messages.mojom.
+class MOJO_CPP_BINDINGS_EXPORT ControlMessageHandler
+ : NON_EXPORTED_BASE(public MessageReceiverWithResponderStatus) {
+ public:
+ static bool IsControlMessage(const Message* message);
+
+ explicit ControlMessageHandler(uint32_t interface_version);
+ ~ControlMessageHandler() override;
+
+ // Call the following methods only if IsControlMessage() returned true.
+ bool Accept(Message* message) override;
+ bool AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) override;
+
+ private:
+ bool Run(Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder);
+ bool RunOrClosePipe(Message* message);
+
+ uint32_t interface_version_;
+ SerializationContext context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ControlMessageHandler);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_
diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc
new file mode 100644
index 0000000000..d082b49fb3
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc
@@ -0,0 +1,188 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/lib/control_message_proxy.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h"
+
+namespace mojo {
+namespace internal {
+
+namespace {
+
+bool ValidateControlResponse(Message* message) {
+ ValidationContext validation_context(message->payload(),
+ message->payload_num_bytes(), 0, 0,
+ message, "ControlResponseValidator");
+ if (!ValidateMessageIsResponse(message, &validation_context))
+ return false;
+
+ switch (message->header()->name) {
+ case interface_control::kRunMessageId:
+ return ValidateMessagePayload<
+ interface_control::internal::RunResponseMessageParams_Data>(
+ message, &validation_context);
+ }
+ return false;
+}
+
+using RunCallback =
+ base::Callback<void(interface_control::RunResponseMessageParamsPtr)>;
+
+class RunResponseForwardToCallback : public MessageReceiver {
+ public:
+ explicit RunResponseForwardToCallback(const RunCallback& callback)
+ : callback_(callback) {}
+ bool Accept(Message* message) override;
+
+ private:
+ RunCallback callback_;
+ DISALLOW_COPY_AND_ASSIGN(RunResponseForwardToCallback);
+};
+
+bool RunResponseForwardToCallback::Accept(Message* message) {
+ if (!ValidateControlResponse(message))
+ return false;
+
+ interface_control::internal::RunResponseMessageParams_Data* params =
+ reinterpret_cast<
+ interface_control::internal::RunResponseMessageParams_Data*>(
+ message->mutable_payload());
+ interface_control::RunResponseMessageParamsPtr params_ptr;
+ SerializationContext context;
+ Deserialize<interface_control::RunResponseMessageParamsDataView>(
+ params, &params_ptr, &context);
+
+ callback_.Run(std::move(params_ptr));
+ return true;
+}
+
+void SendRunMessage(MessageReceiverWithResponder* receiver,
+ interface_control::RunInputPtr input_ptr,
+ const RunCallback& callback) {
+ SerializationContext context;
+
+ auto params_ptr = interface_control::RunMessageParams::New();
+ params_ptr->input = std::move(input_ptr);
+ size_t size = PrepareToSerialize<interface_control::RunMessageParamsDataView>(
+ params_ptr, &context);
+ MessageBuilder builder(interface_control::kRunMessageId,
+ Message::kFlagExpectsResponse, size, 0);
+
+ interface_control::internal::RunMessageParams_Data* params = nullptr;
+ Serialize<interface_control::RunMessageParamsDataView>(
+ params_ptr, builder.buffer(), &params, &context);
+ std::unique_ptr<MessageReceiver> responder =
+ base::MakeUnique<RunResponseForwardToCallback>(callback);
+ ignore_result(
+ receiver->AcceptWithResponder(builder.message(), std::move(responder)));
+}
+
+Message ConstructRunOrClosePipeMessage(
+ interface_control::RunOrClosePipeInputPtr input_ptr) {
+ SerializationContext context;
+
+ auto params_ptr = interface_control::RunOrClosePipeMessageParams::New();
+ params_ptr->input = std::move(input_ptr);
+
+ size_t size = PrepareToSerialize<
+ interface_control::RunOrClosePipeMessageParamsDataView>(params_ptr,
+ &context);
+ MessageBuilder builder(interface_control::kRunOrClosePipeMessageId, 0, size,
+ 0);
+
+ interface_control::internal::RunOrClosePipeMessageParams_Data* params =
+ nullptr;
+ Serialize<interface_control::RunOrClosePipeMessageParamsDataView>(
+ params_ptr, builder.buffer(), &params, &context);
+ return std::move(*builder.message());
+}
+
+void SendRunOrClosePipeMessage(
+ MessageReceiverWithResponder* receiver,
+ interface_control::RunOrClosePipeInputPtr input_ptr) {
+ Message message(ConstructRunOrClosePipeMessage(std::move(input_ptr)));
+
+ ignore_result(receiver->Accept(&message));
+}
+
+void RunVersionCallback(
+ const base::Callback<void(uint32_t)>& callback,
+ interface_control::RunResponseMessageParamsPtr run_response) {
+ uint32_t version = 0u;
+ if (run_response->output && run_response->output->is_query_version_result())
+ version = run_response->output->get_query_version_result()->version;
+ callback.Run(version);
+}
+
+void RunClosure(const base::Closure& callback,
+ interface_control::RunResponseMessageParamsPtr run_response) {
+ callback.Run();
+}
+
+} // namespace
+
+ControlMessageProxy::ControlMessageProxy(MessageReceiverWithResponder* receiver)
+ : receiver_(receiver) {
+}
+
+ControlMessageProxy::~ControlMessageProxy() = default;
+
+void ControlMessageProxy::QueryVersion(
+ const base::Callback<void(uint32_t)>& callback) {
+ auto input_ptr = interface_control::RunInput::New();
+ input_ptr->set_query_version(interface_control::QueryVersion::New());
+ SendRunMessage(receiver_, std::move(input_ptr),
+ base::Bind(&RunVersionCallback, callback));
+}
+
+void ControlMessageProxy::RequireVersion(uint32_t version) {
+ auto require_version = interface_control::RequireVersion::New();
+ require_version->version = version;
+ auto input_ptr = interface_control::RunOrClosePipeInput::New();
+ input_ptr->set_require_version(std::move(require_version));
+ SendRunOrClosePipeMessage(receiver_, std::move(input_ptr));
+}
+
+void ControlMessageProxy::FlushForTesting() {
+ if (encountered_error_)
+ return;
+
+ auto input_ptr = interface_control::RunInput::New();
+ input_ptr->set_flush_for_testing(interface_control::FlushForTesting::New());
+ base::RunLoop run_loop;
+ run_loop_quit_closure_ = run_loop.QuitClosure();
+ SendRunMessage(
+ receiver_, std::move(input_ptr),
+ base::Bind(&RunClosure,
+ base::Bind(&ControlMessageProxy::RunFlushForTestingClosure,
+ base::Unretained(this))));
+ run_loop.Run();
+}
+
+void ControlMessageProxy::RunFlushForTestingClosure() {
+ DCHECK(!run_loop_quit_closure_.is_null());
+ base::ResetAndReturn(&run_loop_quit_closure_).Run();
+}
+
+void ControlMessageProxy::OnConnectionError() {
+ encountered_error_ = true;
+ if (!run_loop_quit_closure_.is_null())
+ RunFlushForTestingClosure();
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.h b/mojo/public/cpp/bindings/lib/control_message_proxy.h
new file mode 100644
index 0000000000..2f9314ebf0
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/control_message_proxy.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+
+namespace mojo {
+
+class MessageReceiverWithResponder;
+
+namespace internal {
+
+// Proxy for request messages defined in interface_control_messages.mojom.
+class MOJO_CPP_BINDINGS_EXPORT ControlMessageProxy {
+ public:
+ // Doesn't take ownership of |receiver|. It must outlive this object.
+ explicit ControlMessageProxy(MessageReceiverWithResponder* receiver);
+ ~ControlMessageProxy();
+
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback);
+ void RequireVersion(uint32_t version);
+
+ void FlushForTesting();
+ void OnConnectionError();
+
+ private:
+ void RunFlushForTestingClosure();
+
+ // Not owned.
+ MessageReceiverWithResponder* receiver_;
+ bool encountered_error_ = false;
+
+ base::Closure run_loop_quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(ControlMessageProxy);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_
diff --git a/mojo/public/cpp/bindings/lib/equals_traits.h b/mojo/public/cpp/bindings/lib/equals_traits.h
new file mode 100644
index 0000000000..53c7dce693
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/equals_traits.h
@@ -0,0 +1,94 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_
+
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename T>
+struct HasEqualsMethod {
+ template <typename U>
+ static char Test(decltype(&U::Equals));
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <typename T, bool has_equals_method = HasEqualsMethod<T>::value>
+struct EqualsTraits;
+
+template <typename T>
+bool Equals(const T& a, const T& b);
+
+template <typename T>
+struct EqualsTraits<T, true> {
+ static bool Equals(const T& a, const T& b) { return a.Equals(b); }
+};
+
+template <typename T>
+struct EqualsTraits<T, false> {
+ static bool Equals(const T& a, const T& b) { return a == b; }
+};
+
+template <typename T>
+struct EqualsTraits<base::Optional<T>, false> {
+ static bool Equals(const base::Optional<T>& a, const base::Optional<T>& b) {
+ if (!a && !b)
+ return true;
+ if (!a || !b)
+ return false;
+
+ return internal::Equals(*a, *b);
+ }
+};
+
+template <typename T>
+struct EqualsTraits<std::vector<T>, false> {
+ static bool Equals(const std::vector<T>& a, const std::vector<T>& b) {
+ if (a.size() != b.size())
+ return false;
+ for (size_t i = 0; i < a.size(); ++i) {
+ if (!internal::Equals(a[i], b[i]))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename K, typename V>
+struct EqualsTraits<std::unordered_map<K, V>, false> {
+ static bool Equals(const std::unordered_map<K, V>& a,
+ const std::unordered_map<K, V>& b) {
+ if (a.size() != b.size())
+ return false;
+ for (const auto& element : a) {
+ auto iter = b.find(element.first);
+ if (iter == b.end() || !internal::Equals(element.second, iter->second))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename T>
+bool Equals(const T& a, const T& b) {
+ return EqualsTraits<T>::Equals(a, b);
+}
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/lib/filter_chain.cc b/mojo/public/cpp/bindings/lib/filter_chain.cc
new file mode 100644
index 0000000000..5d919fe172
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/filter_chain.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 "mojo/public/cpp/bindings/filter_chain.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+namespace mojo {
+
+FilterChain::FilterChain(MessageReceiver* sink) : sink_(sink) {
+}
+
+FilterChain::FilterChain(FilterChain&& other) : sink_(other.sink_) {
+ other.sink_ = nullptr;
+ filters_.swap(other.filters_);
+}
+
+FilterChain& FilterChain::operator=(FilterChain&& other) {
+ std::swap(sink_, other.sink_);
+ filters_.swap(other.filters_);
+ return *this;
+}
+
+FilterChain::~FilterChain() {
+}
+
+void FilterChain::SetSink(MessageReceiver* sink) {
+ DCHECK(!sink_);
+ sink_ = sink;
+}
+
+bool FilterChain::Accept(Message* message) {
+ DCHECK(sink_);
+ for (auto& filter : filters_)
+ if (!filter->Accept(message))
+ return false;
+ return sink_->Accept(message);
+}
+
+void FilterChain::Append(std::unique_ptr<MessageReceiver> filter) {
+ filters_.emplace_back(std::move(filter));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/mojo/public/cpp/bindings/lib/fixed_buffer.cc
new file mode 100644
index 0000000000..725a193cd7
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/fixed_buffer.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+
+#include <stdlib.h>
+
+namespace mojo {
+namespace internal {
+
+FixedBufferForTesting::FixedBufferForTesting(size_t size) {
+ size = internal::Align(size);
+ // Use calloc here to ensure all message memory is zero'd out.
+ void* ptr = calloc(size, 1);
+ Initialize(ptr, size);
+}
+
+FixedBufferForTesting::~FixedBufferForTesting() {
+ free(data());
+}
+
+void* FixedBufferForTesting::Leak() {
+ void* ptr = data();
+ Initialize(nullptr, 0);
+ return ptr;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.h b/mojo/public/cpp/bindings/lib/fixed_buffer.h
new file mode 100644
index 0000000000..070b0c8cef
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/fixed_buffer.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_
+
+#include <stddef.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+
+namespace mojo {
+namespace internal {
+
+// FixedBufferForTesting owns its buffer. The Leak method may be used to steal
+// the underlying memory.
+class MOJO_CPP_BINDINGS_EXPORT FixedBufferForTesting
+ : NON_EXPORTED_BASE(public Buffer) {
+ public:
+ explicit FixedBufferForTesting(size_t size);
+ ~FixedBufferForTesting();
+
+ // Returns the internal memory owned by the Buffer to the caller. The Buffer
+ // relinquishes its pointer, effectively resetting the state of the Buffer
+ // and leaving the caller responsible for freeing the returned memory address
+ // when no longer needed.
+ void* Leak();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FixedBufferForTesting);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_
diff --git a/mojo/public/cpp/bindings/lib/handle_interface_serialization.h b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h
new file mode 100644
index 0000000000..14ed21f0ac
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h
@@ -0,0 +1,181 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_
+
+#include <type_traits>
+
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/interface_data_view.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/system/handle.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename Base, typename T>
+struct Serializer<AssociatedInterfacePtrInfoDataView<Base>,
+ AssociatedInterfacePtrInfo<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static size_t PrepareToSerialize(const AssociatedInterfacePtrInfo<T>& input,
+ SerializationContext* context) {
+ if (input.handle().is_valid())
+ context->associated_endpoint_count++;
+ return 0;
+ }
+
+ static void Serialize(AssociatedInterfacePtrInfo<T>& input,
+ AssociatedInterface_Data* output,
+ SerializationContext* context) {
+ DCHECK(!input.handle().is_valid() || input.handle().pending_association());
+ if (input.handle().is_valid()) {
+ // Set to the index of the element pushed to the back of the vector.
+ output->handle.value =
+ static_cast<uint32_t>(context->associated_endpoint_handles.size());
+ context->associated_endpoint_handles.push_back(input.PassHandle());
+ } else {
+ output->handle.value = kEncodedInvalidHandleValue;
+ }
+ output->version = input.version();
+ }
+
+ static bool Deserialize(AssociatedInterface_Data* input,
+ AssociatedInterfacePtrInfo<T>* output,
+ SerializationContext* context) {
+ if (input->handle.is_valid()) {
+ DCHECK_LT(input->handle.value,
+ context->associated_endpoint_handles.size());
+ output->set_handle(
+ std::move(context->associated_endpoint_handles[input->handle.value]));
+ } else {
+ output->set_handle(ScopedInterfaceEndpointHandle());
+ }
+ output->set_version(input->version);
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<AssociatedInterfaceRequestDataView<Base>,
+ AssociatedInterfaceRequest<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static size_t PrepareToSerialize(const AssociatedInterfaceRequest<T>& input,
+ SerializationContext* context) {
+ if (input.handle().is_valid())
+ context->associated_endpoint_count++;
+ return 0;
+ }
+
+ static void Serialize(AssociatedInterfaceRequest<T>& input,
+ AssociatedEndpointHandle_Data* output,
+ SerializationContext* context) {
+ DCHECK(!input.handle().is_valid() || input.handle().pending_association());
+ if (input.handle().is_valid()) {
+ // Set to the index of the element pushed to the back of the vector.
+ output->value =
+ static_cast<uint32_t>(context->associated_endpoint_handles.size());
+ context->associated_endpoint_handles.push_back(input.PassHandle());
+ } else {
+ output->value = kEncodedInvalidHandleValue;
+ }
+ }
+
+ static bool Deserialize(AssociatedEndpointHandle_Data* input,
+ AssociatedInterfaceRequest<T>* output,
+ SerializationContext* context) {
+ if (input->is_valid()) {
+ DCHECK_LT(input->value, context->associated_endpoint_handles.size());
+ output->Bind(
+ std::move(context->associated_endpoint_handles[input->value]));
+ } else {
+ output->Bind(ScopedInterfaceEndpointHandle());
+ }
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<InterfacePtrDataView<Base>, InterfacePtr<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static size_t PrepareToSerialize(const InterfacePtr<T>& input,
+ SerializationContext* context) {
+ return 0;
+ }
+
+ static void Serialize(InterfacePtr<T>& input,
+ Interface_Data* output,
+ SerializationContext* context) {
+ InterfacePtrInfo<T> info = input.PassInterface();
+ output->handle = context->handles.AddHandle(info.PassHandle().release());
+ output->version = info.version();
+ }
+
+ static bool Deserialize(Interface_Data* input,
+ InterfacePtr<T>* output,
+ SerializationContext* context) {
+ output->Bind(InterfacePtrInfo<T>(
+ context->handles.TakeHandleAs<mojo::MessagePipeHandle>(input->handle),
+ input->version));
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<InterfaceRequestDataView<Base>, InterfaceRequest<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static size_t PrepareToSerialize(const InterfaceRequest<T>& input,
+ SerializationContext* context) {
+ return 0;
+ }
+
+ static void Serialize(InterfaceRequest<T>& input,
+ Handle_Data* output,
+ SerializationContext* context) {
+ *output = context->handles.AddHandle(input.PassMessagePipe().release());
+ }
+
+ static bool Deserialize(Handle_Data* input,
+ InterfaceRequest<T>* output,
+ SerializationContext* context) {
+ output->Bind(context->handles.TakeHandleAs<MessagePipeHandle>(*input));
+ return true;
+ }
+};
+
+template <typename T>
+struct Serializer<ScopedHandleBase<T>, ScopedHandleBase<T>> {
+ static size_t PrepareToSerialize(const ScopedHandleBase<T>& input,
+ SerializationContext* context) {
+ return 0;
+ }
+
+ static void Serialize(ScopedHandleBase<T>& input,
+ Handle_Data* output,
+ SerializationContext* context) {
+ *output = context->handles.AddHandle(input.release());
+ }
+
+ static bool Deserialize(Handle_Data* input,
+ ScopedHandleBase<T>* output,
+ SerializationContext* context) {
+ *output = context->handles.TakeHandleAs<T>(*input);
+ return true;
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/hash_util.h b/mojo/public/cpp/bindings/lib/hash_util.h
new file mode 100644
index 0000000000..93280d69da
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/hash_util.h
@@ -0,0 +1,84 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_
+
+#include <cstring>
+#include <functional>
+#include <type_traits>
+#include <vector>
+
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename T>
+size_t HashCombine(size_t seed, const T& value) {
+ // Based on proposal in:
+ // http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf
+ return seed ^ (std::hash<T>()(value) + (seed << 6) + (seed >> 2));
+}
+
+template <typename T>
+struct HasHashMethod {
+ template <typename U>
+ static char Test(decltype(&U::Hash));
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <typename T, bool has_hash_method = HasHashMethod<T>::value>
+struct HashTraits;
+
+template <typename T>
+size_t Hash(size_t seed, const T& value);
+
+template <typename T>
+struct HashTraits<T, true> {
+ static size_t Hash(size_t seed, const T& value) { return value.Hash(seed); }
+};
+
+template <typename T>
+struct HashTraits<T, false> {
+ static size_t Hash(size_t seed, const T& value) {
+ return HashCombine(seed, value);
+ }
+};
+
+template <typename T>
+struct HashTraits<std::vector<T>, false> {
+ static size_t Hash(size_t seed, const std::vector<T>& value) {
+ for (const auto& element : value) {
+ seed = HashCombine(seed, element);
+ }
+ return seed;
+ }
+};
+
+template <typename T>
+struct HashTraits<base::Optional<std::vector<T>>, false> {
+ static size_t Hash(size_t seed, const base::Optional<std::vector<T>>& value) {
+ if (!value)
+ return HashCombine(seed, 0);
+
+ return Hash(seed, *value);
+ }
+};
+
+template <typename T>
+size_t Hash(size_t seed, const T& value) {
+ return HashTraits<T>::Hash(seed, value);
+}
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
new file mode 100644
index 0000000000..4682e72fad
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -0,0 +1,412 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_controller.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+
+namespace mojo {
+
+// ----------------------------------------------------------------------------
+
+namespace {
+
+void DCheckIfInvalid(const base::WeakPtr<InterfaceEndpointClient>& client,
+ const std::string& message) {
+ bool is_valid = client && !client->encountered_error();
+ DCHECK(!is_valid) << message;
+}
+
+// When receiving an incoming message which expects a repsonse,
+// InterfaceEndpointClient creates a ResponderThunk object and passes it to the
+// incoming message receiver. When the receiver finishes processing the message,
+// it can provide a response using this object.
+class ResponderThunk : public MessageReceiverWithStatus {
+ public:
+ explicit ResponderThunk(
+ const base::WeakPtr<InterfaceEndpointClient>& endpoint_client,
+ scoped_refptr<base::SingleThreadTaskRunner> runner)
+ : endpoint_client_(endpoint_client),
+ accept_was_invoked_(false),
+ task_runner_(std::move(runner)) {}
+ ~ResponderThunk() override {
+ if (!accept_was_invoked_) {
+ // The Service handled a message that was expecting a response
+ // but did not send a response.
+ // We raise an error to signal the calling application that an error
+ // condition occurred. Without this the calling application would have no
+ // way of knowing it should stop waiting for a response.
+ if (task_runner_->RunsTasksOnCurrentThread()) {
+ // Please note that even if this code is run from a different task
+ // runner on the same thread as |task_runner_|, it is okay to directly
+ // call InterfaceEndpointClient::RaiseError(), because it will raise
+ // error from the correct task runner asynchronously.
+ if (endpoint_client_) {
+ endpoint_client_->RaiseError();
+ }
+ } else {
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&InterfaceEndpointClient::RaiseError, endpoint_client_));
+ }
+ }
+ }
+
+ // MessageReceiver implementation:
+ bool Accept(Message* message) override {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ accept_was_invoked_ = true;
+ DCHECK(message->has_flag(Message::kFlagIsResponse));
+
+ bool result = false;
+
+ if (endpoint_client_)
+ result = endpoint_client_->Accept(message);
+
+ return result;
+ }
+
+ // MessageReceiverWithStatus implementation:
+ bool IsValid() override {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ return endpoint_client_ && !endpoint_client_->encountered_error();
+ }
+
+ void DCheckInvalid(const std::string& message) override {
+ if (task_runner_->RunsTasksOnCurrentThread()) {
+ DCheckIfInvalid(endpoint_client_, message);
+ } else {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&DCheckIfInvalid, endpoint_client_, message));
+ }
+ }
+
+ private:
+ base::WeakPtr<InterfaceEndpointClient> endpoint_client_;
+ bool accept_was_invoked_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResponderThunk);
+};
+
+} // namespace
+
+// ----------------------------------------------------------------------------
+
+InterfaceEndpointClient::SyncResponseInfo::SyncResponseInfo(
+ bool* in_response_received)
+ : response_received(in_response_received) {}
+
+InterfaceEndpointClient::SyncResponseInfo::~SyncResponseInfo() {}
+
+// ----------------------------------------------------------------------------
+
+InterfaceEndpointClient::HandleIncomingMessageThunk::HandleIncomingMessageThunk(
+ InterfaceEndpointClient* owner)
+ : owner_(owner) {}
+
+InterfaceEndpointClient::HandleIncomingMessageThunk::
+ ~HandleIncomingMessageThunk() {}
+
+bool InterfaceEndpointClient::HandleIncomingMessageThunk::Accept(
+ Message* message) {
+ return owner_->HandleValidatedMessage(message);
+}
+
+// ----------------------------------------------------------------------------
+
+InterfaceEndpointClient::InterfaceEndpointClient(
+ ScopedInterfaceEndpointHandle handle,
+ MessageReceiverWithResponderStatus* receiver,
+ std::unique_ptr<MessageReceiver> payload_validator,
+ bool expect_sync_requests,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ uint32_t interface_version)
+ : expect_sync_requests_(expect_sync_requests),
+ handle_(std::move(handle)),
+ incoming_receiver_(receiver),
+ thunk_(this),
+ filters_(&thunk_),
+ task_runner_(std::move(runner)),
+ control_message_proxy_(this),
+ control_message_handler_(interface_version),
+ weak_ptr_factory_(this) {
+ DCHECK(handle_.is_valid());
+
+ // TODO(yzshen): the way to use validator (or message filter in general)
+ // directly is a little awkward.
+ if (payload_validator)
+ filters_.Append(std::move(payload_validator));
+
+ if (handle_.pending_association()) {
+ handle_.SetAssociationEventHandler(base::Bind(
+ &InterfaceEndpointClient::OnAssociationEvent, base::Unretained(this)));
+ } else {
+ InitControllerIfNecessary();
+ }
+}
+
+InterfaceEndpointClient::~InterfaceEndpointClient() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (controller_)
+ handle_.group_controller()->DetachEndpointClient(handle_);
+}
+
+AssociatedGroup* InterfaceEndpointClient::associated_group() {
+ if (!associated_group_)
+ associated_group_ = base::MakeUnique<AssociatedGroup>(handle_);
+ return associated_group_.get();
+}
+
+ScopedInterfaceEndpointHandle InterfaceEndpointClient::PassHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!has_pending_responders());
+
+ if (!handle_.is_valid())
+ return ScopedInterfaceEndpointHandle();
+
+ handle_.SetAssociationEventHandler(
+ ScopedInterfaceEndpointHandle::AssociationEventCallback());
+
+ if (controller_) {
+ controller_ = nullptr;
+ handle_.group_controller()->DetachEndpointClient(handle_);
+ }
+
+ return std::move(handle_);
+}
+
+void InterfaceEndpointClient::AddFilter(
+ std::unique_ptr<MessageReceiver> filter) {
+ filters_.Append(std::move(filter));
+}
+
+void InterfaceEndpointClient::RaiseError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!handle_.pending_association())
+ handle_.group_controller()->RaiseError();
+}
+
+void InterfaceEndpointClient::CloseWithReason(uint32_t custom_reason,
+ const std::string& description) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto handle = PassHandle();
+ handle.ResetWithReason(custom_reason, description);
+}
+
+bool InterfaceEndpointClient::Accept(Message* message) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!message->has_flag(Message::kFlagExpectsResponse));
+ DCHECK(!handle_.pending_association());
+
+ // This has to been done even if connection error has occurred. For example,
+ // the message contains a pending associated request. The user may try to use
+ // the corresponding associated interface pointer after sending this message.
+ // That associated interface pointer has to join an associated group in order
+ // to work properly.
+ if (!message->associated_endpoint_handles()->empty())
+ message->SerializeAssociatedEndpointHandles(handle_.group_controller());
+
+ if (encountered_error_)
+ return false;
+
+ InitControllerIfNecessary();
+
+ return controller_->SendMessage(message);
+}
+
+bool InterfaceEndpointClient::AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiver> responder) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(message->has_flag(Message::kFlagExpectsResponse));
+ DCHECK(!handle_.pending_association());
+
+ // Please see comments in Accept().
+ if (!message->associated_endpoint_handles()->empty())
+ message->SerializeAssociatedEndpointHandles(handle_.group_controller());
+
+ if (encountered_error_)
+ return false;
+
+ InitControllerIfNecessary();
+
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ uint64_t request_id = next_request_id_++;
+ if (request_id == 0)
+ request_id = next_request_id_++;
+
+ message->set_request_id(request_id);
+
+ bool is_sync = message->has_flag(Message::kFlagIsSync);
+ if (!controller_->SendMessage(message))
+ return false;
+
+ if (!is_sync) {
+ async_responders_[request_id] = std::move(responder);
+ return true;
+ }
+
+ SyncCallRestrictions::AssertSyncCallAllowed();
+
+ bool response_received = false;
+ sync_responses_.insert(std::make_pair(
+ request_id, base::MakeUnique<SyncResponseInfo>(&response_received)));
+
+ base::WeakPtr<InterfaceEndpointClient> weak_self =
+ weak_ptr_factory_.GetWeakPtr();
+ controller_->SyncWatch(&response_received);
+ // Make sure that this instance hasn't been destroyed.
+ if (weak_self) {
+ DCHECK(base::ContainsKey(sync_responses_, request_id));
+ auto iter = sync_responses_.find(request_id);
+ DCHECK_EQ(&response_received, iter->second->response_received);
+ if (response_received)
+ ignore_result(responder->Accept(&iter->second->response));
+ sync_responses_.erase(iter);
+ }
+
+ return true;
+}
+
+bool InterfaceEndpointClient::HandleIncomingMessage(Message* message) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return filters_.Accept(message);
+}
+
+void InterfaceEndpointClient::NotifyError(
+ const base::Optional<DisconnectReason>& reason) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (encountered_error_)
+ return;
+ encountered_error_ = true;
+
+ // Response callbacks may hold on to resource, and there's no need to keep
+ // them alive any longer. Note that it's allowed that a pending response
+ // callback may own this endpoint, so we simply move the responders onto the
+ // stack here and let them be destroyed when the stack unwinds.
+ AsyncResponderMap responders = std::move(async_responders_);
+
+ control_message_proxy_.OnConnectionError();
+
+ if (!error_handler_.is_null()) {
+ base::Closure error_handler = std::move(error_handler_);
+ error_handler.Run();
+ } else if (!error_with_reason_handler_.is_null()) {
+ ConnectionErrorWithReasonCallback error_with_reason_handler =
+ std::move(error_with_reason_handler_);
+ if (reason) {
+ error_with_reason_handler.Run(reason->custom_reason, reason->description);
+ } else {
+ error_with_reason_handler.Run(0, std::string());
+ }
+ }
+}
+
+void InterfaceEndpointClient::QueryVersion(
+ const base::Callback<void(uint32_t)>& callback) {
+ control_message_proxy_.QueryVersion(callback);
+}
+
+void InterfaceEndpointClient::RequireVersion(uint32_t version) {
+ control_message_proxy_.RequireVersion(version);
+}
+
+void InterfaceEndpointClient::FlushForTesting() {
+ control_message_proxy_.FlushForTesting();
+}
+
+void InterfaceEndpointClient::InitControllerIfNecessary() {
+ if (controller_ || handle_.pending_association())
+ return;
+
+ controller_ = handle_.group_controller()->AttachEndpointClient(handle_, this,
+ task_runner_);
+ if (expect_sync_requests_)
+ controller_->AllowWokenUpBySyncWatchOnSameThread();
+}
+
+void InterfaceEndpointClient::OnAssociationEvent(
+ ScopedInterfaceEndpointHandle::AssociationEvent event) {
+ if (event == ScopedInterfaceEndpointHandle::ASSOCIATED) {
+ InitControllerIfNecessary();
+ } else if (event ==
+ ScopedInterfaceEndpointHandle::PEER_CLOSED_BEFORE_ASSOCIATION) {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&InterfaceEndpointClient::NotifyError,
+ weak_ptr_factory_.GetWeakPtr(),
+ handle_.disconnect_reason()));
+ }
+}
+
+bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) {
+ DCHECK_EQ(handle_.id(), message->interface_id());
+
+ if (encountered_error_) {
+ // This message is received after error has been encountered. For associated
+ // interfaces, this means the remote side sends a
+ // PeerAssociatedEndpointClosed event but continues to send more messages
+ // for the same interface. Close the pipe because this shouldn't happen.
+ DVLOG(1) << "A message is received for an interface after it has been "
+ << "disconnected. Closing the pipe.";
+ return false;
+ }
+
+ if (message->has_flag(Message::kFlagExpectsResponse)) {
+ std::unique_ptr<MessageReceiverWithStatus> responder =
+ base::MakeUnique<ResponderThunk>(weak_ptr_factory_.GetWeakPtr(),
+ task_runner_);
+ if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) {
+ return control_message_handler_.AcceptWithResponder(message,
+ std::move(responder));
+ } else {
+ return incoming_receiver_->AcceptWithResponder(message,
+ std::move(responder));
+ }
+ } else if (message->has_flag(Message::kFlagIsResponse)) {
+ uint64_t request_id = message->request_id();
+
+ if (message->has_flag(Message::kFlagIsSync)) {
+ auto it = sync_responses_.find(request_id);
+ if (it == sync_responses_.end())
+ return false;
+ it->second->response = std::move(*message);
+ *it->second->response_received = true;
+ return true;
+ }
+
+ auto it = async_responders_.find(request_id);
+ if (it == async_responders_.end())
+ return false;
+ std::unique_ptr<MessageReceiver> responder = std::move(it->second);
+ async_responders_.erase(it);
+ return responder->Accept(message);
+ } else {
+ if (mojo::internal::ControlMessageHandler::IsControlMessage(message))
+ return control_message_handler_.Accept(message);
+
+ return incoming_receiver_->Accept(message);
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
new file mode 100644
index 0000000000..fa54979795
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_
+
+#include <stdint.h>
+
+#include <algorithm> // For |std::swap()|.
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+#include "mojo/public/cpp/bindings/message_header_validator.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename Interface>
+class InterfacePtrState {
+ public:
+ InterfacePtrState() : version_(0u) {}
+
+ ~InterfacePtrState() {
+ endpoint_client_.reset();
+ proxy_.reset();
+ if (router_)
+ router_->CloseMessagePipe();
+ }
+
+ Interface* instance() {
+ ConfigureProxyIfNecessary();
+
+ // This will be null if the object is not bound.
+ return proxy_.get();
+ }
+
+ uint32_t version() const { return version_; }
+
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback) {
+ ConfigureProxyIfNecessary();
+
+ // It is safe to capture |this| because the callback won't be run after this
+ // object goes away.
+ endpoint_client_->QueryVersion(base::Bind(
+ &InterfacePtrState::OnQueryVersion, base::Unretained(this), callback));
+ }
+
+ void RequireVersion(uint32_t version) {
+ ConfigureProxyIfNecessary();
+
+ if (version <= version_)
+ return;
+
+ version_ = version;
+ endpoint_client_->RequireVersion(version);
+ }
+
+ void FlushForTesting() {
+ ConfigureProxyIfNecessary();
+ endpoint_client_->FlushForTesting();
+ }
+
+ void CloseWithReason(uint32_t custom_reason, const std::string& description) {
+ ConfigureProxyIfNecessary();
+ endpoint_client_->CloseWithReason(custom_reason, description);
+ }
+
+ void Swap(InterfacePtrState* other) {
+ using std::swap;
+ swap(other->router_, router_);
+ swap(other->endpoint_client_, endpoint_client_);
+ swap(other->proxy_, proxy_);
+ handle_.swap(other->handle_);
+ runner_.swap(other->runner_);
+ swap(other->version_, version_);
+ }
+
+ void Bind(InterfacePtrInfo<Interface> info,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ DCHECK(!router_);
+ DCHECK(!endpoint_client_);
+ DCHECK(!proxy_);
+ DCHECK(!handle_.is_valid());
+ DCHECK_EQ(0u, version_);
+ DCHECK(info.is_valid());
+
+ handle_ = info.PassHandle();
+ version_ = info.version();
+ runner_ = std::move(runner);
+ }
+
+ bool HasAssociatedInterfaces() const {
+ return router_ ? router_->HasAssociatedEndpoints() : false;
+ }
+
+ // After this method is called, the object is in an invalid state and
+ // shouldn't be reused.
+ InterfacePtrInfo<Interface> PassInterface() {
+ endpoint_client_.reset();
+ proxy_.reset();
+ return InterfacePtrInfo<Interface>(
+ router_ ? router_->PassMessagePipe() : std::move(handle_), version_);
+ }
+
+ bool is_bound() const { return handle_.is_valid() || endpoint_client_; }
+
+ bool encountered_error() const {
+ return endpoint_client_ ? endpoint_client_->encountered_error() : false;
+ }
+
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ ConfigureProxyIfNecessary();
+
+ DCHECK(endpoint_client_);
+ endpoint_client_->set_connection_error_handler(error_handler);
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ ConfigureProxyIfNecessary();
+
+ DCHECK(endpoint_client_);
+ endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+ }
+
+ // Returns true if bound and awaiting a response to a message.
+ bool has_pending_callbacks() const {
+ return endpoint_client_ && endpoint_client_->has_pending_responders();
+ }
+
+ AssociatedGroup* associated_group() {
+ ConfigureProxyIfNecessary();
+ return endpoint_client_->associated_group();
+ }
+
+ void EnableTestingMode() {
+ ConfigureProxyIfNecessary();
+ router_->EnableTestingMode();
+ }
+
+ void ForwardMessage(Message message) {
+ ConfigureProxyIfNecessary();
+ endpoint_client_->Accept(&message);
+ }
+
+ void ForwardMessageWithResponder(Message message,
+ std::unique_ptr<MessageReceiver> responder) {
+ ConfigureProxyIfNecessary();
+ endpoint_client_->AcceptWithResponder(&message, std::move(responder));
+ }
+
+ private:
+ using Proxy = typename Interface::Proxy_;
+
+ void ConfigureProxyIfNecessary() {
+ // The proxy has been configured.
+ if (proxy_) {
+ DCHECK(router_);
+ DCHECK(endpoint_client_);
+ return;
+ }
+ // The object hasn't been bound.
+ if (!handle_.is_valid())
+ return;
+
+ MultiplexRouter::Config config =
+ Interface::PassesAssociatedKinds_
+ ? MultiplexRouter::MULTI_INTERFACE
+ : (Interface::HasSyncMethods_
+ ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS
+ : MultiplexRouter::SINGLE_INTERFACE);
+ router_ = new MultiplexRouter(std::move(handle_), config, true, runner_);
+ router_->SetMasterInterfaceName(Interface::Name_);
+ endpoint_client_.reset(new InterfaceEndpointClient(
+ router_->CreateLocalEndpointHandle(kMasterInterfaceId), nullptr,
+ base::WrapUnique(new typename Interface::ResponseValidator_()), false,
+ std::move(runner_),
+ // The version is only queried from the client so the value passed here
+ // will not be used.
+ 0u));
+ proxy_.reset(new Proxy(endpoint_client_.get()));
+ }
+
+ void OnQueryVersion(const base::Callback<void(uint32_t)>& callback,
+ uint32_t version) {
+ version_ = version;
+ callback.Run(version);
+ }
+
+ scoped_refptr<MultiplexRouter> router_;
+
+ std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+ std::unique_ptr<Proxy> proxy_;
+
+ // |router_| (as well as other members above) is not initialized until
+ // read/write with the message pipe handle is needed. |handle_| is valid
+ // between the Bind() call and the initialization of |router_|.
+ ScopedMessagePipeHandle handle_;
+ scoped_refptr<base::SingleThreadTaskRunner> runner_;
+
+ uint32_t version_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfacePtrState);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_
diff --git a/mojo/public/cpp/bindings/lib/map_data_internal.h b/mojo/public/cpp/bindings/lib/map_data_internal.h
new file mode 100644
index 0000000000..f8e3d2918f
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/map_data_internal.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/validate_params.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+
+namespace mojo {
+namespace internal {
+
+// Map serializes into a struct which has two arrays as struct fields, the keys
+// and the values.
+template <typename Key, typename Value>
+class Map_Data {
+ public:
+ static Map_Data* New(Buffer* buf) {
+ return new (buf->Allocate(sizeof(Map_Data))) Map_Data();
+ }
+
+ // |validate_params| must have non-null |key_validate_params| and
+ // |element_validate_params| members.
+ static bool Validate(const void* data,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ if (!data)
+ return true;
+
+ if (!ValidateStructHeaderAndClaimMemory(data, validation_context))
+ return false;
+
+ const Map_Data* object = static_cast<const Map_Data*>(data);
+ if (object->header_.num_bytes != sizeof(Map_Data) ||
+ object->header_.version != 0) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER);
+ return false;
+ }
+
+ if (!ValidatePointerNonNullable(
+ object->keys, "null key array in map struct", validation_context) ||
+ !ValidateContainer(object->keys, validation_context,
+ validate_params->key_validate_params)) {
+ return false;
+ }
+
+ if (!ValidatePointerNonNullable(object->values,
+ "null value array in map struct",
+ validation_context) ||
+ !ValidateContainer(object->values, validation_context,
+ validate_params->element_validate_params)) {
+ return false;
+ }
+
+ if (object->keys.Get()->size() != object->values.Get()->size()) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP);
+ return false;
+ }
+
+ return true;
+ }
+
+ StructHeader header_;
+
+ Pointer<Array_Data<Key>> keys;
+ Pointer<Array_Data<Value>> values;
+
+ private:
+ Map_Data() {
+ header_.num_bytes = sizeof(*this);
+ header_.version = 0;
+ }
+ ~Map_Data() = delete;
+};
+static_assert(sizeof(Map_Data<char, char>) == 24, "Bad sizeof(Map_Data)");
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/lib/map_serialization.h b/mojo/public/cpp/bindings/lib/map_serialization.h
new file mode 100644
index 0000000000..718a76307d
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/map_serialization.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_
+
+#include <type_traits>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/array_data_view.h"
+#include "mojo/public/cpp/bindings/lib/array_serialization.h"
+#include "mojo/public/cpp/bindings/lib/map_data_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/bindings/map_data_view.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename MaybeConstUserType>
+class MapReaderBase {
+ public:
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = MapTraits<UserType>;
+ using MaybeConstIterator =
+ decltype(Traits::GetBegin(std::declval<MaybeConstUserType&>()));
+
+ explicit MapReaderBase(MaybeConstUserType& input)
+ : input_(input), iter_(Traits::GetBegin(input_)) {}
+ ~MapReaderBase() {}
+
+ size_t GetSize() const { return Traits::GetSize(input_); }
+
+ // Return null because key or value elements are not stored continuously in
+ // memory.
+ void* GetDataIfExists() { return nullptr; }
+
+ protected:
+ MaybeConstUserType& input_;
+ MaybeConstIterator iter_;
+};
+
+// Used as the UserTypeReader template parameter of ArraySerializer.
+template <typename MaybeConstUserType>
+class MapKeyReader : public MapReaderBase<MaybeConstUserType> {
+ public:
+ using Base = MapReaderBase<MaybeConstUserType>;
+ using Traits = typename Base::Traits;
+ using MaybeConstIterator = typename Base::MaybeConstIterator;
+
+ explicit MapKeyReader(MaybeConstUserType& input) : Base(input) {}
+ ~MapKeyReader() {}
+
+ using GetNextResult =
+ decltype(Traits::GetKey(std::declval<MaybeConstIterator&>()));
+ GetNextResult GetNext() {
+ GetNextResult key = Traits::GetKey(this->iter_);
+ Traits::AdvanceIterator(this->iter_);
+ return key;
+ }
+};
+
+// Used as the UserTypeReader template parameter of ArraySerializer.
+template <typename MaybeConstUserType>
+class MapValueReader : public MapReaderBase<MaybeConstUserType> {
+ public:
+ using Base = MapReaderBase<MaybeConstUserType>;
+ using Traits = typename Base::Traits;
+ using MaybeConstIterator = typename Base::MaybeConstIterator;
+
+ explicit MapValueReader(MaybeConstUserType& input) : Base(input) {}
+ ~MapValueReader() {}
+
+ using GetNextResult =
+ decltype(Traits::GetValue(std::declval<MaybeConstIterator&>()));
+ GetNextResult GetNext() {
+ GetNextResult value = Traits::GetValue(this->iter_);
+ Traits::AdvanceIterator(this->iter_);
+ return value;
+ }
+};
+
+template <typename Key, typename Value, typename MaybeConstUserType>
+struct Serializer<MapDataView<Key, Value>, MaybeConstUserType> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = MapTraits<UserType>;
+ using UserKey = typename Traits::Key;
+ using UserValue = typename Traits::Value;
+ using Data = typename MojomTypeTraits<MapDataView<Key, Value>>::Data;
+ using KeyArraySerializer = ArraySerializer<ArrayDataView<Key>,
+ std::vector<UserKey>,
+ MapKeyReader<MaybeConstUserType>>;
+ using ValueArraySerializer =
+ ArraySerializer<ArrayDataView<Value>,
+ std::vector<UserValue>,
+ MapValueReader<MaybeConstUserType>>;
+
+ static size_t PrepareToSerialize(MaybeConstUserType& input,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input))
+ return 0;
+
+ size_t struct_overhead = sizeof(Data);
+ MapKeyReader<MaybeConstUserType> key_reader(input);
+ size_t keys_size =
+ KeyArraySerializer::GetSerializedSize(&key_reader, context);
+ MapValueReader<MaybeConstUserType> value_reader(input);
+ size_t values_size =
+ ValueArraySerializer::GetSerializedSize(&value_reader, context);
+
+ return struct_overhead + keys_size + values_size;
+ }
+
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buf,
+ Data** output,
+ const ContainerValidateParams* validate_params,
+ SerializationContext* context) {
+ DCHECK(validate_params->key_validate_params);
+ DCHECK(validate_params->element_validate_params);
+ if (CallIsNullIfExists<Traits>(input)) {
+ *output = nullptr;
+ return;
+ }
+
+ auto result = Data::New(buf);
+ if (result) {
+ auto keys_ptr = MojomTypeTraits<ArrayDataView<Key>>::Data::New(
+ Traits::GetSize(input), buf);
+ if (keys_ptr) {
+ MapKeyReader<MaybeConstUserType> key_reader(input);
+ KeyArraySerializer::SerializeElements(
+ &key_reader, buf, keys_ptr, validate_params->key_validate_params,
+ context);
+ result->keys.Set(keys_ptr);
+ }
+
+ auto values_ptr = MojomTypeTraits<ArrayDataView<Value>>::Data::New(
+ Traits::GetSize(input), buf);
+ if (values_ptr) {
+ MapValueReader<MaybeConstUserType> value_reader(input);
+ ValueArraySerializer::SerializeElements(
+ &value_reader, buf, values_ptr,
+ validate_params->element_validate_params, context);
+ result->values.Set(values_ptr);
+ }
+ }
+ *output = result;
+ }
+
+ static bool Deserialize(Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!input)
+ return CallSetToNullIfExists<Traits>(output);
+
+ std::vector<UserKey> keys;
+ std::vector<UserValue> values;
+
+ if (!KeyArraySerializer::DeserializeElements(input->keys.Get(), &keys,
+ context) ||
+ !ValueArraySerializer::DeserializeElements(input->values.Get(), &values,
+ context)) {
+ return false;
+ }
+
+ DCHECK_EQ(keys.size(), values.size());
+ size_t size = keys.size();
+ Traits::SetToEmpty(output);
+
+ for (size_t i = 0; i < size; ++i) {
+ if (!Traits::Insert(*output, std::move(keys[i]), std::move(values[i])))
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/may_auto_lock.h b/mojo/public/cpp/bindings/lib/may_auto_lock.h
new file mode 100644
index 0000000000..06091fee90
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/may_auto_lock.h
@@ -0,0 +1,62 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/synchronization/lock.h"
+
+namespace mojo {
+namespace internal {
+
+// Similar to base::AutoLock, except that it does nothing if |lock| passed into
+// the constructor is null.
+class MayAutoLock {
+ public:
+ explicit MayAutoLock(base::Optional<base::Lock>* lock)
+ : lock_(lock->has_value() ? &lock->value() : nullptr) {
+ if (lock_)
+ lock_->Acquire();
+ }
+
+ ~MayAutoLock() {
+ if (lock_) {
+ lock_->AssertAcquired();
+ lock_->Release();
+ }
+ }
+
+ private:
+ base::Lock* lock_;
+ DISALLOW_COPY_AND_ASSIGN(MayAutoLock);
+};
+
+// Similar to base::AutoUnlock, except that it does nothing if |lock| passed
+// into the constructor is null.
+class MayAutoUnlock {
+ public:
+ explicit MayAutoUnlock(base::Optional<base::Lock>* lock)
+ : lock_(lock->has_value() ? &lock->value() : nullptr) {
+ if (lock_) {
+ lock_->AssertAcquired();
+ lock_->Release();
+ }
+ }
+
+ ~MayAutoUnlock() {
+ if (lock_)
+ lock_->Acquire();
+ }
+
+ private:
+ base::Lock* lock_;
+ DISALLOW_COPY_AND_ASSIGN(MayAutoUnlock);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_
diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc
new file mode 100644
index 0000000000..e5f3808117
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message.cc
@@ -0,0 +1,332 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/message.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_local.h"
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+
+namespace mojo {
+
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<internal::MessageDispatchContext>>::
+ DestructorAtExit g_tls_message_dispatch_context = LAZY_INSTANCE_INITIALIZER;
+
+base::LazyInstance<base::ThreadLocalPointer<SyncMessageResponseContext>>::
+ DestructorAtExit g_tls_sync_response_context = LAZY_INSTANCE_INITIALIZER;
+
+void DoNotifyBadMessage(Message message, const std::string& error) {
+ message.NotifyBadMessage(error);
+}
+
+} // namespace
+
+Message::Message() {
+}
+
+Message::Message(Message&& other)
+ : buffer_(std::move(other.buffer_)),
+ handles_(std::move(other.handles_)),
+ associated_endpoint_handles_(
+ std::move(other.associated_endpoint_handles_)) {}
+
+Message::~Message() {
+ CloseHandles();
+}
+
+Message& Message::operator=(Message&& other) {
+ Reset();
+ std::swap(other.buffer_, buffer_);
+ std::swap(other.handles_, handles_);
+ std::swap(other.associated_endpoint_handles_, associated_endpoint_handles_);
+ return *this;
+}
+
+void Message::Reset() {
+ CloseHandles();
+ handles_.clear();
+ associated_endpoint_handles_.clear();
+ buffer_.reset();
+}
+
+void Message::Initialize(size_t capacity, bool zero_initialized) {
+ DCHECK(!buffer_);
+ buffer_.reset(new internal::MessageBuffer(capacity, zero_initialized));
+}
+
+void Message::InitializeFromMojoMessage(ScopedMessageHandle message,
+ uint32_t num_bytes,
+ std::vector<Handle>* handles) {
+ DCHECK(!buffer_);
+ buffer_.reset(new internal::MessageBuffer(std::move(message), num_bytes));
+ handles_.swap(*handles);
+}
+
+const uint8_t* Message::payload() const {
+ if (version() < 2)
+ return data() + header()->num_bytes;
+
+ return static_cast<const uint8_t*>(header_v2()->payload.Get());
+}
+
+uint32_t Message::payload_num_bytes() const {
+ DCHECK_GE(data_num_bytes(), header()->num_bytes);
+ size_t num_bytes;
+ if (version() < 2) {
+ num_bytes = data_num_bytes() - header()->num_bytes;
+ } else {
+ auto payload = reinterpret_cast<uintptr_t>(header_v2()->payload.Get());
+ if (!payload) {
+ num_bytes = 0;
+ } else {
+ auto payload_end =
+ reinterpret_cast<uintptr_t>(header_v2()->payload_interface_ids.Get());
+ if (!payload_end)
+ payload_end = reinterpret_cast<uintptr_t>(data() + data_num_bytes());
+ DCHECK_GE(payload_end, payload);
+ num_bytes = payload_end - payload;
+ }
+ }
+ DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max());
+ return static_cast<uint32_t>(num_bytes);
+}
+
+uint32_t Message::payload_num_interface_ids() const {
+ auto* array_pointer =
+ version() < 2 ? nullptr : header_v2()->payload_interface_ids.Get();
+ return array_pointer ? static_cast<uint32_t>(array_pointer->size()) : 0;
+}
+
+const uint32_t* Message::payload_interface_ids() const {
+ auto* array_pointer =
+ version() < 2 ? nullptr : header_v2()->payload_interface_ids.Get();
+ return array_pointer ? array_pointer->storage() : nullptr;
+}
+
+ScopedMessageHandle Message::TakeMojoMessage() {
+ // If there are associated endpoints transferred,
+ // SerializeAssociatedEndpointHandles() must be called before this method.
+ DCHECK(associated_endpoint_handles_.empty());
+
+ if (handles_.empty()) // Fast path for the common case: No handles.
+ return buffer_->TakeMessage();
+
+ // Allocate a new message with space for the handles, then copy the buffer
+ // contents into it.
+ //
+ // TODO(rockot): We could avoid this copy by extending GetSerializedSize()
+ // behavior to collect handles. It's unoptimized for now because it's much
+ // more common to have messages with no handles.
+ ScopedMessageHandle new_message;
+ MojoResult rv = AllocMessage(
+ data_num_bytes(),
+ handles_.empty() ? nullptr
+ : reinterpret_cast<const MojoHandle*>(handles_.data()),
+ handles_.size(),
+ MOJO_ALLOC_MESSAGE_FLAG_NONE,
+ &new_message);
+ CHECK_EQ(rv, MOJO_RESULT_OK);
+ handles_.clear();
+
+ void* new_buffer = nullptr;
+ rv = GetMessageBuffer(new_message.get(), &new_buffer);
+ CHECK_EQ(rv, MOJO_RESULT_OK);
+
+ memcpy(new_buffer, data(), data_num_bytes());
+ buffer_.reset();
+
+ return new_message;
+}
+
+void Message::NotifyBadMessage(const std::string& error) {
+ DCHECK(buffer_);
+ buffer_->NotifyBadMessage(error);
+}
+
+void Message::CloseHandles() {
+ for (std::vector<Handle>::iterator it = handles_.begin();
+ it != handles_.end(); ++it) {
+ if (it->is_valid())
+ CloseRaw(*it);
+ }
+}
+
+void Message::SerializeAssociatedEndpointHandles(
+ AssociatedGroupController* group_controller) {
+ if (associated_endpoint_handles_.empty())
+ return;
+
+ DCHECK_GE(version(), 2u);
+ DCHECK(header_v2()->payload_interface_ids.is_null());
+
+ size_t size = associated_endpoint_handles_.size();
+ auto* data = internal::Array_Data<uint32_t>::New(size, buffer());
+ header_v2()->payload_interface_ids.Set(data);
+
+ for (size_t i = 0; i < size; ++i) {
+ ScopedInterfaceEndpointHandle& handle = associated_endpoint_handles_[i];
+
+ DCHECK(handle.pending_association());
+ data->storage()[i] =
+ group_controller->AssociateInterface(std::move(handle));
+ }
+ associated_endpoint_handles_.clear();
+}
+
+bool Message::DeserializeAssociatedEndpointHandles(
+ AssociatedGroupController* group_controller) {
+ associated_endpoint_handles_.clear();
+
+ uint32_t num_ids = payload_num_interface_ids();
+ if (num_ids == 0)
+ return true;
+
+ associated_endpoint_handles_.reserve(num_ids);
+ uint32_t* ids = header_v2()->payload_interface_ids.Get()->storage();
+ bool result = true;
+ for (uint32_t i = 0; i < num_ids; ++i) {
+ auto handle = group_controller->CreateLocalEndpointHandle(ids[i]);
+ if (IsValidInterfaceId(ids[i]) && !handle.is_valid()) {
+ // |ids[i]| itself is valid but handle creation failed. In that case, mark
+ // deserialization as failed but continue to deserialize the rest of
+ // handles.
+ result = false;
+ }
+
+ associated_endpoint_handles_.push_back(std::move(handle));
+ ids[i] = kInvalidInterfaceId;
+ }
+ return result;
+}
+
+PassThroughFilter::PassThroughFilter() {}
+
+PassThroughFilter::~PassThroughFilter() {}
+
+bool PassThroughFilter::Accept(Message* message) { return true; }
+
+SyncMessageResponseContext::SyncMessageResponseContext()
+ : outer_context_(current()) {
+ g_tls_sync_response_context.Get().Set(this);
+}
+
+SyncMessageResponseContext::~SyncMessageResponseContext() {
+ DCHECK_EQ(current(), this);
+ g_tls_sync_response_context.Get().Set(outer_context_);
+}
+
+// static
+SyncMessageResponseContext* SyncMessageResponseContext::current() {
+ return g_tls_sync_response_context.Get().Get();
+}
+
+void SyncMessageResponseContext::ReportBadMessage(const std::string& error) {
+ GetBadMessageCallback().Run(error);
+}
+
+const ReportBadMessageCallback&
+SyncMessageResponseContext::GetBadMessageCallback() {
+ if (bad_message_callback_.is_null()) {
+ bad_message_callback_ =
+ base::Bind(&DoNotifyBadMessage, base::Passed(&response_));
+ }
+ return bad_message_callback_;
+}
+
+MojoResult ReadMessage(MessagePipeHandle handle, Message* message) {
+ MojoResult rv;
+
+ std::vector<Handle> handles;
+ ScopedMessageHandle mojo_message;
+ uint32_t num_bytes = 0, num_handles = 0;
+ rv = ReadMessageNew(handle,
+ &mojo_message,
+ &num_bytes,
+ nullptr,
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ if (rv == MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ DCHECK_GT(num_handles, 0u);
+ handles.resize(num_handles);
+ rv = ReadMessageNew(handle,
+ &mojo_message,
+ &num_bytes,
+ reinterpret_cast<MojoHandle*>(handles.data()),
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ }
+
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ message->InitializeFromMojoMessage(
+ std::move(mojo_message), num_bytes, &handles);
+ return MOJO_RESULT_OK;
+}
+
+void ReportBadMessage(const std::string& error) {
+ internal::MessageDispatchContext* context =
+ internal::MessageDispatchContext::current();
+ DCHECK(context);
+ context->GetBadMessageCallback().Run(error);
+}
+
+ReportBadMessageCallback GetBadMessageCallback() {
+ internal::MessageDispatchContext* context =
+ internal::MessageDispatchContext::current();
+ DCHECK(context);
+ return context->GetBadMessageCallback();
+}
+
+namespace internal {
+
+MessageHeaderV2::MessageHeaderV2() = default;
+
+MessageDispatchContext::MessageDispatchContext(Message* message)
+ : outer_context_(current()), message_(message) {
+ g_tls_message_dispatch_context.Get().Set(this);
+}
+
+MessageDispatchContext::~MessageDispatchContext() {
+ DCHECK_EQ(current(), this);
+ g_tls_message_dispatch_context.Get().Set(outer_context_);
+}
+
+// static
+MessageDispatchContext* MessageDispatchContext::current() {
+ return g_tls_message_dispatch_context.Get().Get();
+}
+
+const ReportBadMessageCallback&
+MessageDispatchContext::GetBadMessageCallback() {
+ if (bad_message_callback_.is_null()) {
+ bad_message_callback_ =
+ base::Bind(&DoNotifyBadMessage, base::Passed(message_));
+ }
+ return bad_message_callback_;
+}
+
+// static
+void SyncMessageResponseSetup::SetCurrentSyncResponseMessage(Message* message) {
+ SyncMessageResponseContext* context = SyncMessageResponseContext::current();
+ if (context)
+ context->response_ = std::move(*message);
+}
+
+} // namespace internal
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_buffer.cc b/mojo/public/cpp/bindings/lib/message_buffer.cc
new file mode 100644
index 0000000000..cc12ef6e31
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_buffer.cc
@@ -0,0 +1,52 @@
+// 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 "mojo/public/cpp/bindings/lib/message_buffer.h"
+
+#include <limits>
+
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+
+namespace mojo {
+namespace internal {
+
+MessageBuffer::MessageBuffer(size_t capacity, bool zero_initialized) {
+ DCHECK_LE(capacity, std::numeric_limits<uint32_t>::max());
+
+ MojoResult rv = AllocMessage(capacity, nullptr, 0,
+ MOJO_ALLOC_MESSAGE_FLAG_NONE, &message_);
+ CHECK_EQ(rv, MOJO_RESULT_OK);
+
+ void* buffer = nullptr;
+ if (capacity != 0) {
+ rv = GetMessageBuffer(message_.get(), &buffer);
+ CHECK_EQ(rv, MOJO_RESULT_OK);
+
+ if (zero_initialized)
+ memset(buffer, 0, capacity);
+ }
+ Initialize(buffer, capacity);
+}
+
+MessageBuffer::MessageBuffer(ScopedMessageHandle message, uint32_t num_bytes) {
+ message_ = std::move(message);
+
+ void* buffer = nullptr;
+ if (num_bytes != 0) {
+ MojoResult rv = GetMessageBuffer(message_.get(), &buffer);
+ CHECK_EQ(rv, MOJO_RESULT_OK);
+ }
+ Initialize(buffer, num_bytes);
+}
+
+MessageBuffer::~MessageBuffer() {}
+
+void MessageBuffer::NotifyBadMessage(const std::string& error) {
+ DCHECK(message_.is_valid());
+ MojoResult result = mojo::NotifyBadMessage(message_.get(), error);
+ DCHECK_EQ(result, MOJO_RESULT_OK);
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_buffer.h b/mojo/public/cpp/bindings/lib/message_buffer.h
new file mode 100644
index 0000000000..96d5140f77
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_buffer.h
@@ -0,0 +1,43 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+#include "mojo/public/cpp/system/message.h"
+
+namespace mojo {
+namespace internal {
+
+// A fixed-size Buffer using a Mojo message object for storage.
+class MessageBuffer : public Buffer {
+ public:
+ // Initializes this buffer to carry a fixed byte capacity and no handles.
+ MessageBuffer(size_t capacity, bool zero_initialized);
+
+ // Initializes this buffer from an existing Mojo MessageHandle.
+ MessageBuffer(ScopedMessageHandle message, uint32_t num_bytes);
+
+ ~MessageBuffer();
+
+ ScopedMessageHandle TakeMessage() { return std::move(message_); }
+
+ void NotifyBadMessage(const std::string& error);
+
+ private:
+ ScopedMessageHandle message_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageBuffer);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_
diff --git a/mojo/public/cpp/bindings/lib/message_builder.cc b/mojo/public/cpp/bindings/lib/message_builder.cc
new file mode 100644
index 0000000000..6806a73213
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_builder.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 "mojo/public/cpp/bindings/lib/message_builder.h"
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+#include "mojo/public/cpp/bindings/lib/message_internal.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename Header>
+void Allocate(Buffer* buf, Header** header) {
+ *header = static_cast<Header*>(buf->Allocate(sizeof(Header)));
+ (*header)->num_bytes = sizeof(Header);
+}
+
+MessageBuilder::MessageBuilder(uint32_t name,
+ uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count) {
+ if (payload_interface_id_count > 0) {
+ // Version 2
+ InitializeMessage(
+ sizeof(MessageHeaderV2) + Align(payload_size) +
+ ArrayDataTraits<uint32_t>::GetStorageSize(
+ static_cast<uint32_t>(payload_interface_id_count)));
+
+ MessageHeaderV2* header;
+ Allocate(message_.buffer(), &header);
+ header->version = 2;
+ header->name = name;
+ header->flags = flags;
+ // The payload immediately follows the header.
+ header->payload.Set(header + 1);
+ } else if (flags &
+ (Message::kFlagExpectsResponse | Message::kFlagIsResponse)) {
+ // Version 1
+ InitializeMessage(sizeof(MessageHeaderV1) + payload_size);
+
+ MessageHeaderV1* header;
+ Allocate(message_.buffer(), &header);
+ header->version = 1;
+ header->name = name;
+ header->flags = flags;
+ } else {
+ InitializeMessage(sizeof(MessageHeader) + payload_size);
+
+ MessageHeader* header;
+ Allocate(message_.buffer(), &header);
+ header->version = 0;
+ header->name = name;
+ header->flags = flags;
+ }
+}
+
+MessageBuilder::~MessageBuilder() {
+}
+
+void MessageBuilder::InitializeMessage(size_t size) {
+ message_.Initialize(static_cast<uint32_t>(Align(size)),
+ true /* zero_initialized */);
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_builder.h b/mojo/public/cpp/bindings/lib/message_builder.h
new file mode 100644
index 0000000000..8a4d5c4690
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_builder.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+
+class Message;
+
+namespace internal {
+
+class Buffer;
+
+class MOJO_CPP_BINDINGS_EXPORT MessageBuilder {
+ public:
+ MessageBuilder(uint32_t name,
+ uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count);
+ ~MessageBuilder();
+
+ Buffer* buffer() { return message_.buffer(); }
+ Message* message() { return &message_; }
+
+ private:
+ void InitializeMessage(size_t size);
+
+ Message message_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageBuilder);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_
diff --git a/mojo/public/cpp/bindings/lib/message_header_validator.cc b/mojo/public/cpp/bindings/lib/message_header_validator.cc
new file mode 100644
index 0000000000..9f8c6278c0
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_header_validator.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 "mojo/public/cpp/bindings/message_header_validator.h"
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/validate_params.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+
+namespace mojo {
+namespace {
+
+// TODO(yzshen): Define a mojom struct for message header and use the generated
+// validation and data view code.
+bool IsValidMessageHeader(const internal::MessageHeader* header,
+ internal::ValidationContext* validation_context) {
+ // NOTE: Our goal is to preserve support for future extension of the message
+ // header. If we encounter fields we do not understand, we must ignore them.
+
+ // Extra validation of the struct header:
+ do {
+ if (header->version == 0) {
+ if (header->num_bytes == sizeof(internal::MessageHeader))
+ break;
+ } else if (header->version == 1) {
+ if (header->num_bytes == sizeof(internal::MessageHeaderV1))
+ break;
+ } else if (header->version == 2) {
+ if (header->num_bytes == sizeof(internal::MessageHeaderV2))
+ break;
+ } else if (header->version > 2) {
+ if (header->num_bytes >= sizeof(internal::MessageHeaderV2))
+ break;
+ }
+ internal::ReportValidationError(
+ validation_context,
+ internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER);
+ return false;
+ } while (false);
+
+ // Validate flags (allow unknown bits):
+
+ // These flags require a RequestID.
+ constexpr uint32_t kRequestIdFlags =
+ Message::kFlagExpectsResponse | Message::kFlagIsResponse;
+ if (header->version == 0 && (header->flags & kRequestIdFlags)) {
+ internal::ReportValidationError(
+ validation_context,
+ internal::VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID);
+ return false;
+ }
+
+ // These flags are mutually exclusive.
+ if ((header->flags & kRequestIdFlags) == kRequestIdFlags) {
+ internal::ReportValidationError(
+ validation_context,
+ internal::VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS);
+ return false;
+ }
+
+ if (header->version < 2)
+ return true;
+
+ auto* header_v2 = static_cast<const internal::MessageHeaderV2*>(header);
+ // For the payload pointer:
+ // - Check that the pointer can be safely decoded.
+ // - Claim one byte that the pointer points to. It makes sure not only the
+ // address is within the message, but also the address precedes the array
+ // storing interface IDs (which is important for safely calculating the
+ // payload size).
+ // - Validation of the payload contents will be done separately based on the
+ // payload type.
+ if (!header_v2->payload.is_null() &&
+ (!internal::ValidatePointer(header_v2->payload, validation_context) ||
+ !validation_context->ClaimMemory(header_v2->payload.Get(), 1))) {
+ return false;
+ }
+
+ const internal::ContainerValidateParams validate_params(0, false, nullptr);
+ if (!internal::ValidateContainer(header_v2->payload_interface_ids,
+ validation_context, &validate_params)) {
+ return false;
+ }
+
+ if (!header_v2->payload_interface_ids.is_null()) {
+ size_t num_ids = header_v2->payload_interface_ids.Get()->size();
+ const uint32_t* ids = header_v2->payload_interface_ids.Get()->storage();
+ for (size_t i = 0; i < num_ids; ++i) {
+ if (!IsValidInterfaceId(ids[i]) || IsMasterInterfaceId(ids[i])) {
+ internal::ReportValidationError(
+ validation_context,
+ internal::VALIDATION_ERROR_ILLEGAL_INTERFACE_ID);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+MessageHeaderValidator::MessageHeaderValidator()
+ : MessageHeaderValidator("MessageHeaderValidator") {}
+
+MessageHeaderValidator::MessageHeaderValidator(const std::string& description)
+ : description_(description) {
+}
+
+void MessageHeaderValidator::SetDescription(const std::string& description) {
+ description_ = description;
+}
+
+bool MessageHeaderValidator::Accept(Message* message) {
+ // Pass 0 as number of handles and associated endpoint handles because we
+ // don't expect any in the header, even if |message| contains handles.
+ internal::ValidationContext validation_context(
+ message->data(), message->data_num_bytes(), 0, 0, message, description_);
+
+ if (!internal::ValidateStructHeaderAndClaimMemory(message->data(),
+ &validation_context))
+ return false;
+
+ if (!IsValidMessageHeader(message->header(), &validation_context))
+ return false;
+
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_internal.h b/mojo/public/cpp/bindings/lib/message_internal.h
new file mode 100644
index 0000000000..6693198f81
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_internal.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+
+namespace mojo {
+
+class Message;
+
+namespace internal {
+
+template <typename T>
+class Array_Data;
+
+#pragma pack(push, 1)
+
+struct MessageHeader : internal::StructHeader {
+ // Interface ID for identifying multiple interfaces running on the same
+ // message pipe.
+ uint32_t interface_id;
+ // Message name, which is scoped to the interface that the message belongs to.
+ uint32_t name;
+ // 0 or either of the enum values defined above.
+ uint32_t flags;
+ // Unused padding to make the struct size a multiple of 8 bytes.
+ uint32_t padding;
+};
+static_assert(sizeof(MessageHeader) == 24, "Bad sizeof(MessageHeader)");
+
+struct MessageHeaderV1 : MessageHeader {
+ // Only used if either kFlagExpectsResponse or kFlagIsResponse is set in
+ // order to match responses with corresponding requests.
+ uint64_t request_id;
+};
+static_assert(sizeof(MessageHeaderV1) == 32, "Bad sizeof(MessageHeaderV1)");
+
+struct MessageHeaderV2 : MessageHeaderV1 {
+ MessageHeaderV2();
+ GenericPointer payload;
+ Pointer<Array_Data<uint32_t>> payload_interface_ids;
+};
+static_assert(sizeof(MessageHeaderV2) == 48, "Bad sizeof(MessageHeaderV2)");
+
+#pragma pack(pop)
+
+class MOJO_CPP_BINDINGS_EXPORT MessageDispatchContext {
+ public:
+ explicit MessageDispatchContext(Message* message);
+ ~MessageDispatchContext();
+
+ static MessageDispatchContext* current();
+
+ const base::Callback<void(const std::string&)>& GetBadMessageCallback();
+
+ private:
+ MessageDispatchContext* outer_context_;
+ Message* message_;
+ base::Callback<void(const std::string&)> bad_message_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageDispatchContext);
+};
+
+class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseSetup {
+ public:
+ static void SetCurrentSyncResponseMessage(Message* message);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc
new file mode 100644
index 0000000000..ff7c678289
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
@@ -0,0 +1,960 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_controller.h"
+#include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+namespace mojo {
+namespace internal {
+
+// InterfaceEndpoint stores the information of an interface endpoint registered
+// with the router.
+// No one other than the router's |endpoints_| and |tasks_| should hold refs to
+// this object.
+class MultiplexRouter::InterfaceEndpoint
+ : public base::RefCountedThreadSafe<InterfaceEndpoint>,
+ public InterfaceEndpointController {
+ public:
+ InterfaceEndpoint(MultiplexRouter* router, InterfaceId id)
+ : router_(router),
+ id_(id),
+ closed_(false),
+ peer_closed_(false),
+ handle_created_(false),
+ client_(nullptr) {}
+
+ // ---------------------------------------------------------------------------
+ // The following public methods are safe to call from any threads without
+ // locking.
+
+ InterfaceId id() const { return id_; }
+
+ // ---------------------------------------------------------------------------
+ // The following public methods are called under the router's lock.
+
+ bool closed() const { return closed_; }
+ void set_closed() {
+ router_->AssertLockAcquired();
+ closed_ = true;
+ }
+
+ bool peer_closed() const { return peer_closed_; }
+ void set_peer_closed() {
+ router_->AssertLockAcquired();
+ peer_closed_ = true;
+ }
+
+ bool handle_created() const { return handle_created_; }
+ void set_handle_created() {
+ router_->AssertLockAcquired();
+ handle_created_ = true;
+ }
+
+ const base::Optional<DisconnectReason>& disconnect_reason() const {
+ return disconnect_reason_;
+ }
+ void set_disconnect_reason(
+ const base::Optional<DisconnectReason>& disconnect_reason) {
+ router_->AssertLockAcquired();
+ disconnect_reason_ = disconnect_reason;
+ }
+
+ base::SingleThreadTaskRunner* task_runner() const {
+ return task_runner_.get();
+ }
+
+ InterfaceEndpointClient* client() const { return client_; }
+
+ void AttachClient(InterfaceEndpointClient* client,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ router_->AssertLockAcquired();
+ DCHECK(!client_);
+ DCHECK(!closed_);
+ DCHECK(runner->BelongsToCurrentThread());
+
+ task_runner_ = std::move(runner);
+ client_ = client;
+ }
+
+ // This method must be called on the same thread as the corresponding
+ // AttachClient() call.
+ void DetachClient() {
+ router_->AssertLockAcquired();
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(!closed_);
+
+ task_runner_ = nullptr;
+ client_ = nullptr;
+ sync_watcher_.reset();
+ }
+
+ void SignalSyncMessageEvent() {
+ router_->AssertLockAcquired();
+ if (sync_message_event_signaled_)
+ return;
+ sync_message_event_signaled_ = true;
+ if (sync_message_event_)
+ sync_message_event_->Signal();
+ }
+
+ void ResetSyncMessageSignal() {
+ router_->AssertLockAcquired();
+ if (!sync_message_event_signaled_)
+ return;
+ sync_message_event_signaled_ = false;
+ if (sync_message_event_)
+ sync_message_event_->Reset();
+ }
+
+ // ---------------------------------------------------------------------------
+ // The following public methods (i.e., InterfaceEndpointController
+ // implementation) are called by the client on the same thread as the
+ // AttachClient() call. They are called outside of the router's lock.
+
+ bool SendMessage(Message* message) override {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ message->set_interface_id(id_);
+ return router_->connector_.Accept(message);
+ }
+
+ void AllowWokenUpBySyncWatchOnSameThread() override {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ EnsureSyncWatcherExists();
+ sync_watcher_->AllowWokenUpBySyncWatchOnSameThread();
+ }
+
+ bool SyncWatch(const bool* should_stop) override {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ EnsureSyncWatcherExists();
+ return sync_watcher_->SyncWatch(should_stop);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<InterfaceEndpoint>;
+
+ ~InterfaceEndpoint() override {
+ router_->AssertLockAcquired();
+
+ DCHECK(!client_);
+ DCHECK(closed_);
+ DCHECK(peer_closed_);
+ DCHECK(!sync_watcher_);
+ }
+
+ void OnSyncEventSignaled() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ scoped_refptr<MultiplexRouter> router_protector(router_);
+
+ MayAutoLock locker(&router_->lock_);
+ scoped_refptr<InterfaceEndpoint> self_protector(this);
+
+ bool more_to_process = router_->ProcessFirstSyncMessageForEndpoint(id_);
+
+ if (!more_to_process)
+ ResetSyncMessageSignal();
+
+ // Currently there are no queued sync messages and the peer has closed so
+ // there won't be incoming sync messages in the future.
+ if (!more_to_process && peer_closed_) {
+ // If a SyncWatch() call (or multiple ones) of this interface endpoint is
+ // on the call stack, resetting the sync watcher will allow it to exit
+ // when the call stack unwinds to that frame.
+ sync_watcher_.reset();
+ }
+ }
+
+ void EnsureSyncWatcherExists() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ if (sync_watcher_)
+ return;
+
+ {
+ MayAutoLock locker(&router_->lock_);
+ if (!sync_message_event_) {
+ sync_message_event_.emplace(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ if (sync_message_event_signaled_)
+ sync_message_event_->Signal();
+ }
+ }
+ sync_watcher_.reset(
+ new SyncEventWatcher(&sync_message_event_.value(),
+ base::Bind(&InterfaceEndpoint::OnSyncEventSignaled,
+ base::Unretained(this))));
+ }
+
+ // ---------------------------------------------------------------------------
+ // The following members are safe to access from any threads.
+
+ MultiplexRouter* const router_;
+ const InterfaceId id_;
+
+ // ---------------------------------------------------------------------------
+ // The following members are accessed under the router's lock.
+
+ // Whether the endpoint has been closed.
+ bool closed_;
+ // Whether the peer endpoint has been closed.
+ bool peer_closed_;
+
+ // Whether there is already a ScopedInterfaceEndpointHandle created for this
+ // endpoint.
+ bool handle_created_;
+
+ base::Optional<DisconnectReason> disconnect_reason_;
+
+ // The task runner on which |client_|'s methods can be called.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ // Not owned. It is null if no client is attached to this endpoint.
+ InterfaceEndpointClient* client_;
+
+ // An event used to signal that sync messages are available. The event is
+ // initialized under the router's lock and remains unchanged afterwards. It
+ // may be accessed outside of the router's lock later.
+ base::Optional<base::WaitableEvent> sync_message_event_;
+ bool sync_message_event_signaled_ = false;
+
+ // ---------------------------------------------------------------------------
+ // The following members are only valid while a client is attached. They are
+ // used exclusively on the client's thread. They may be accessed outside of
+ // the router's lock.
+
+ std::unique_ptr<SyncEventWatcher> sync_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfaceEndpoint);
+};
+
+// MessageWrapper objects are always destroyed under the router's lock. On
+// destruction, if the message it wrappers contains
+// ScopedInterfaceEndpointHandles (which cannot be destructed under the
+// router's lock), the wrapper unlocks to clean them up.
+class MultiplexRouter::MessageWrapper {
+ public:
+ MessageWrapper() = default;
+
+ MessageWrapper(MultiplexRouter* router, Message message)
+ : router_(router), value_(std::move(message)) {}
+
+ MessageWrapper(MessageWrapper&& other)
+ : router_(other.router_), value_(std::move(other.value_)) {}
+
+ ~MessageWrapper() {
+ if (value_.associated_endpoint_handles()->empty())
+ return;
+
+ router_->AssertLockAcquired();
+ {
+ MayAutoUnlock unlocker(&router_->lock_);
+ value_.mutable_associated_endpoint_handles()->clear();
+ }
+ }
+
+ MessageWrapper& operator=(MessageWrapper&& other) {
+ router_ = other.router_;
+ value_ = std::move(other.value_);
+ return *this;
+ }
+
+ Message& value() { return value_; }
+
+ private:
+ MultiplexRouter* router_ = nullptr;
+ Message value_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageWrapper);
+};
+
+struct MultiplexRouter::Task {
+ public:
+ // Doesn't take ownership of |message| but takes its contents.
+ static std::unique_ptr<Task> CreateMessageTask(
+ MessageWrapper message_wrapper) {
+ Task* task = new Task(MESSAGE);
+ task->message_wrapper = std::move(message_wrapper);
+ return base::WrapUnique(task);
+ }
+ static std::unique_ptr<Task> CreateNotifyErrorTask(
+ InterfaceEndpoint* endpoint) {
+ Task* task = new Task(NOTIFY_ERROR);
+ task->endpoint_to_notify = endpoint;
+ return base::WrapUnique(task);
+ }
+
+ ~Task() {}
+
+ bool IsMessageTask() const { return type == MESSAGE; }
+ bool IsNotifyErrorTask() const { return type == NOTIFY_ERROR; }
+
+ MessageWrapper message_wrapper;
+ scoped_refptr<InterfaceEndpoint> endpoint_to_notify;
+
+ enum Type { MESSAGE, NOTIFY_ERROR };
+ Type type;
+
+ private:
+ explicit Task(Type in_type) : type(in_type) {}
+
+ DISALLOW_COPY_AND_ASSIGN(Task);
+};
+
+MultiplexRouter::MultiplexRouter(
+ ScopedMessagePipeHandle message_pipe,
+ Config config,
+ bool set_interface_id_namesapce_bit,
+ scoped_refptr<base::SingleThreadTaskRunner> runner)
+ : set_interface_id_namespace_bit_(set_interface_id_namesapce_bit),
+ task_runner_(runner),
+ header_validator_(nullptr),
+ filters_(this),
+ connector_(std::move(message_pipe),
+ config == MULTI_INTERFACE ? Connector::MULTI_THREADED_SEND
+ : Connector::SINGLE_THREADED_SEND,
+ std::move(runner)),
+ control_message_handler_(this),
+ control_message_proxy_(&connector_),
+ next_interface_id_value_(1),
+ posted_to_process_tasks_(false),
+ encountered_error_(false),
+ paused_(false),
+ testing_mode_(false) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ if (config == MULTI_INTERFACE)
+ lock_.emplace();
+
+ if (config == SINGLE_INTERFACE_WITH_SYNC_METHODS ||
+ config == MULTI_INTERFACE) {
+ // Always participate in sync handle watching in multi-interface mode,
+ // because even if it doesn't expect sync requests during sync handle
+ // watching, it may still need to dispatch messages to associated endpoints
+ // on a different thread.
+ connector_.AllowWokenUpBySyncWatchOnSameThread();
+ }
+ connector_.set_incoming_receiver(&filters_);
+ connector_.set_connection_error_handler(
+ base::Bind(&MultiplexRouter::OnPipeConnectionError,
+ base::Unretained(this)));
+
+ std::unique_ptr<MessageHeaderValidator> header_validator =
+ base::MakeUnique<MessageHeaderValidator>();
+ header_validator_ = header_validator.get();
+ filters_.Append(std::move(header_validator));
+}
+
+MultiplexRouter::~MultiplexRouter() {
+ MayAutoLock locker(&lock_);
+
+ sync_message_tasks_.clear();
+ tasks_.clear();
+
+ for (auto iter = endpoints_.begin(); iter != endpoints_.end();) {
+ InterfaceEndpoint* endpoint = iter->second.get();
+ // Increment the iterator before calling UpdateEndpointStateMayRemove()
+ // because it may remove the corresponding value from the map.
+ ++iter;
+
+ if (!endpoint->closed()) {
+ // This happens when a NotifyPeerEndpointClosed message been received, but
+ // the interface ID hasn't been used to create local endpoint handle.
+ DCHECK(!endpoint->client());
+ DCHECK(endpoint->peer_closed());
+ UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED);
+ } else {
+ UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED);
+ }
+ }
+
+ DCHECK(endpoints_.empty());
+}
+
+void MultiplexRouter::SetMasterInterfaceName(const char* name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ header_validator_->SetDescription(
+ std::string(name) + " [master] MessageHeaderValidator");
+ control_message_handler_.SetDescription(
+ std::string(name) + " [master] PipeControlMessageHandler");
+ connector_.SetWatcherHeapProfilerTag(name);
+}
+
+InterfaceId MultiplexRouter::AssociateInterface(
+ ScopedInterfaceEndpointHandle handle_to_send) {
+ if (!handle_to_send.pending_association())
+ return kInvalidInterfaceId;
+
+ uint32_t id = 0;
+ {
+ MayAutoLock locker(&lock_);
+ do {
+ if (next_interface_id_value_ >= kInterfaceIdNamespaceMask)
+ next_interface_id_value_ = 1;
+ id = next_interface_id_value_++;
+ if (set_interface_id_namespace_bit_)
+ id |= kInterfaceIdNamespaceMask;
+ } while (base::ContainsKey(endpoints_, id));
+
+ InterfaceEndpoint* endpoint = new InterfaceEndpoint(this, id);
+ endpoints_[id] = endpoint;
+ if (encountered_error_)
+ UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED);
+ endpoint->set_handle_created();
+ }
+
+ if (!NotifyAssociation(&handle_to_send, id)) {
+ // The peer handle of |handle_to_send|, which is supposed to join this
+ // associated group, has been closed.
+ {
+ MayAutoLock locker(&lock_);
+ InterfaceEndpoint* endpoint = FindEndpoint(id);
+ if (endpoint)
+ UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED);
+ }
+
+ control_message_proxy_.NotifyPeerEndpointClosed(
+ id, handle_to_send.disconnect_reason());
+ }
+ return id;
+}
+
+ScopedInterfaceEndpointHandle MultiplexRouter::CreateLocalEndpointHandle(
+ InterfaceId id) {
+ if (!IsValidInterfaceId(id))
+ return ScopedInterfaceEndpointHandle();
+
+ MayAutoLock locker(&lock_);
+ bool inserted = false;
+ InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, &inserted);
+ if (inserted) {
+ DCHECK(!endpoint->handle_created());
+
+ if (encountered_error_)
+ UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED);
+ } else {
+ // If the endpoint already exist, it is because we have received a
+ // notification that the peer endpoint has closed.
+ CHECK(!endpoint->closed());
+ CHECK(endpoint->peer_closed());
+
+ if (endpoint->handle_created())
+ return ScopedInterfaceEndpointHandle();
+ }
+
+ endpoint->set_handle_created();
+ return CreateScopedInterfaceEndpointHandle(id);
+}
+
+void MultiplexRouter::CloseEndpointHandle(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) {
+ if (!IsValidInterfaceId(id))
+ return;
+
+ MayAutoLock locker(&lock_);
+ DCHECK(base::ContainsKey(endpoints_, id));
+ InterfaceEndpoint* endpoint = endpoints_[id].get();
+ DCHECK(!endpoint->client());
+ DCHECK(!endpoint->closed());
+ UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED);
+
+ if (!IsMasterInterfaceId(id) || reason) {
+ MayAutoUnlock unlocker(&lock_);
+ control_message_proxy_.NotifyPeerEndpointClosed(id, reason);
+ }
+
+ ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr);
+}
+
+InterfaceEndpointController* MultiplexRouter::AttachEndpointClient(
+ const ScopedInterfaceEndpointHandle& handle,
+ InterfaceEndpointClient* client,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ const InterfaceId id = handle.id();
+
+ DCHECK(IsValidInterfaceId(id));
+ DCHECK(client);
+
+ MayAutoLock locker(&lock_);
+ DCHECK(base::ContainsKey(endpoints_, id));
+
+ InterfaceEndpoint* endpoint = endpoints_[id].get();
+ endpoint->AttachClient(client, std::move(runner));
+
+ if (endpoint->peer_closed())
+ tasks_.push_back(Task::CreateNotifyErrorTask(endpoint));
+ ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr);
+
+ return endpoint;
+}
+
+void MultiplexRouter::DetachEndpointClient(
+ const ScopedInterfaceEndpointHandle& handle) {
+ const InterfaceId id = handle.id();
+
+ DCHECK(IsValidInterfaceId(id));
+
+ MayAutoLock locker(&lock_);
+ DCHECK(base::ContainsKey(endpoints_, id));
+
+ InterfaceEndpoint* endpoint = endpoints_[id].get();
+ endpoint->DetachClient();
+}
+
+void MultiplexRouter::RaiseError() {
+ if (task_runner_->BelongsToCurrentThread()) {
+ connector_.RaiseError();
+ } else {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&MultiplexRouter::RaiseError, this));
+ }
+}
+
+void MultiplexRouter::CloseMessagePipe() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ connector_.CloseMessagePipe();
+ // CloseMessagePipe() above won't trigger connection error handler.
+ // Explicitly call OnPipeConnectionError() so that associated endpoints will
+ // get notified.
+ OnPipeConnectionError();
+}
+
+void MultiplexRouter::PauseIncomingMethodCallProcessing() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ connector_.PauseIncomingMethodCallProcessing();
+
+ MayAutoLock locker(&lock_);
+ paused_ = true;
+
+ for (auto iter = endpoints_.begin(); iter != endpoints_.end(); ++iter)
+ iter->second->ResetSyncMessageSignal();
+}
+
+void MultiplexRouter::ResumeIncomingMethodCallProcessing() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ connector_.ResumeIncomingMethodCallProcessing();
+
+ MayAutoLock locker(&lock_);
+ paused_ = false;
+
+ for (auto iter = endpoints_.begin(); iter != endpoints_.end(); ++iter) {
+ auto sync_iter = sync_message_tasks_.find(iter->first);
+ if (iter->second->peer_closed() ||
+ (sync_iter != sync_message_tasks_.end() &&
+ !sync_iter->second.empty())) {
+ iter->second->SignalSyncMessageEvent();
+ }
+ }
+
+ ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr);
+}
+
+bool MultiplexRouter::HasAssociatedEndpoints() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ MayAutoLock locker(&lock_);
+
+ if (endpoints_.size() > 1)
+ return true;
+ if (endpoints_.size() == 0)
+ return false;
+
+ return !base::ContainsKey(endpoints_, kMasterInterfaceId);
+}
+
+void MultiplexRouter::EnableTestingMode() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ MayAutoLock locker(&lock_);
+
+ testing_mode_ = true;
+ connector_.set_enforce_errors_from_incoming_receiver(false);
+}
+
+bool MultiplexRouter::Accept(Message* message) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!message->DeserializeAssociatedEndpointHandles(this))
+ return false;
+
+ scoped_refptr<MultiplexRouter> protector(this);
+ MayAutoLock locker(&lock_);
+
+ DCHECK(!paused_);
+
+ ClientCallBehavior client_call_behavior =
+ connector_.during_sync_handle_watcher_callback()
+ ? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES
+ : ALLOW_DIRECT_CLIENT_CALLS;
+
+ bool processed =
+ tasks_.empty() && ProcessIncomingMessage(message, client_call_behavior,
+ connector_.task_runner());
+
+ if (!processed) {
+ // Either the task queue is not empty or we cannot process the message
+ // directly. In both cases, there is no need to call ProcessTasks().
+ tasks_.push_back(
+ Task::CreateMessageTask(MessageWrapper(this, std::move(*message))));
+ Task* task = tasks_.back().get();
+
+ if (task->message_wrapper.value().has_flag(Message::kFlagIsSync)) {
+ InterfaceId id = task->message_wrapper.value().interface_id();
+ sync_message_tasks_[id].push_back(task);
+ InterfaceEndpoint* endpoint = FindEndpoint(id);
+ if (endpoint)
+ endpoint->SignalSyncMessageEvent();
+ }
+ } else if (!tasks_.empty()) {
+ // Processing the message may result in new tasks (for error notification)
+ // being added to the queue. In this case, we have to attempt to process the
+ // tasks.
+ ProcessTasks(client_call_behavior, connector_.task_runner());
+ }
+
+ // Always return true. If we see errors during message processing, we will
+ // explicitly call Connector::RaiseError() to disconnect the message pipe.
+ return true;
+}
+
+bool MultiplexRouter::OnPeerAssociatedEndpointClosed(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) {
+ DCHECK(!IsMasterInterfaceId(id) || reason);
+
+ MayAutoLock locker(&lock_);
+ InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, nullptr);
+
+ if (reason)
+ endpoint->set_disconnect_reason(reason);
+
+ // It is possible that this endpoint has been set as peer closed. That is
+ // because when the message pipe is closed, all the endpoints are updated with
+ // PEER_ENDPOINT_CLOSED. We continue to process remaining tasks in the queue,
+ // as long as there are refs keeping the router alive. If there is a
+ // PeerAssociatedEndpointClosedEvent control message in the queue, we will get
+ // here and see that the endpoint has been marked as peer closed.
+ if (!endpoint->peer_closed()) {
+ if (endpoint->client())
+ tasks_.push_back(Task::CreateNotifyErrorTask(endpoint));
+ UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED);
+ }
+
+ // No need to trigger a ProcessTasks() because it is already on the stack.
+
+ return true;
+}
+
+void MultiplexRouter::OnPipeConnectionError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_refptr<MultiplexRouter> protector(this);
+ MayAutoLock locker(&lock_);
+
+ encountered_error_ = true;
+
+ for (auto iter = endpoints_.begin(); iter != endpoints_.end();) {
+ InterfaceEndpoint* endpoint = iter->second.get();
+ // Increment the iterator before calling UpdateEndpointStateMayRemove()
+ // because it may remove the corresponding value from the map.
+ ++iter;
+
+ if (endpoint->client())
+ tasks_.push_back(Task::CreateNotifyErrorTask(endpoint));
+
+ UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED);
+ }
+
+ ProcessTasks(connector_.during_sync_handle_watcher_callback()
+ ? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES
+ : ALLOW_DIRECT_CLIENT_CALLS,
+ connector_.task_runner());
+}
+
+void MultiplexRouter::ProcessTasks(
+ ClientCallBehavior client_call_behavior,
+ base::SingleThreadTaskRunner* current_task_runner) {
+ AssertLockAcquired();
+
+ if (posted_to_process_tasks_)
+ return;
+
+ while (!tasks_.empty() && !paused_) {
+ std::unique_ptr<Task> task(std::move(tasks_.front()));
+ tasks_.pop_front();
+
+ InterfaceId id = kInvalidInterfaceId;
+ bool sync_message =
+ task->IsMessageTask() && !task->message_wrapper.value().IsNull() &&
+ task->message_wrapper.value().has_flag(Message::kFlagIsSync);
+ if (sync_message) {
+ id = task->message_wrapper.value().interface_id();
+ auto& sync_message_queue = sync_message_tasks_[id];
+ DCHECK_EQ(task.get(), sync_message_queue.front());
+ sync_message_queue.pop_front();
+ }
+
+ bool processed =
+ task->IsNotifyErrorTask()
+ ? ProcessNotifyErrorTask(task.get(), client_call_behavior,
+ current_task_runner)
+ : ProcessIncomingMessage(&task->message_wrapper.value(),
+ client_call_behavior, current_task_runner);
+
+ if (!processed) {
+ if (sync_message) {
+ auto& sync_message_queue = sync_message_tasks_[id];
+ sync_message_queue.push_front(task.get());
+ }
+ tasks_.push_front(std::move(task));
+ break;
+ } else {
+ if (sync_message) {
+ auto iter = sync_message_tasks_.find(id);
+ if (iter != sync_message_tasks_.end() && iter->second.empty())
+ sync_message_tasks_.erase(iter);
+ }
+ }
+ }
+}
+
+bool MultiplexRouter::ProcessFirstSyncMessageForEndpoint(InterfaceId id) {
+ AssertLockAcquired();
+
+ auto iter = sync_message_tasks_.find(id);
+ if (iter == sync_message_tasks_.end())
+ return false;
+
+ if (paused_)
+ return true;
+
+ MultiplexRouter::Task* task = iter->second.front();
+ iter->second.pop_front();
+
+ DCHECK(task->IsMessageTask());
+ MessageWrapper message_wrapper = std::move(task->message_wrapper);
+
+ // Note: after this call, |task| and |iter| may be invalidated.
+ bool processed = ProcessIncomingMessage(
+ &message_wrapper.value(), ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES,
+ nullptr);
+ DCHECK(processed);
+
+ iter = sync_message_tasks_.find(id);
+ if (iter == sync_message_tasks_.end())
+ return false;
+
+ if (iter->second.empty()) {
+ sync_message_tasks_.erase(iter);
+ return false;
+ }
+
+ return true;
+}
+
+bool MultiplexRouter::ProcessNotifyErrorTask(
+ Task* task,
+ ClientCallBehavior client_call_behavior,
+ base::SingleThreadTaskRunner* current_task_runner) {
+ DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread());
+ DCHECK(!paused_);
+
+ AssertLockAcquired();
+ InterfaceEndpoint* endpoint = task->endpoint_to_notify.get();
+ if (!endpoint->client())
+ return true;
+
+ if (client_call_behavior != ALLOW_DIRECT_CLIENT_CALLS ||
+ endpoint->task_runner() != current_task_runner) {
+ MaybePostToProcessTasks(endpoint->task_runner());
+ return false;
+ }
+
+ DCHECK(endpoint->task_runner()->BelongsToCurrentThread());
+
+ InterfaceEndpointClient* client = endpoint->client();
+ base::Optional<DisconnectReason> disconnect_reason(
+ endpoint->disconnect_reason());
+
+ {
+ // We must unlock before calling into |client| because it may call this
+ // object within NotifyError(). Holding the lock will lead to deadlock.
+ //
+ // It is safe to call into |client| without the lock. Because |client| is
+ // always accessed on the same thread, including DetachEndpointClient().
+ MayAutoUnlock unlocker(&lock_);
+ client->NotifyError(disconnect_reason);
+ }
+ return true;
+}
+
+bool MultiplexRouter::ProcessIncomingMessage(
+ Message* message,
+ ClientCallBehavior client_call_behavior,
+ base::SingleThreadTaskRunner* current_task_runner) {
+ DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread());
+ DCHECK(!paused_);
+ DCHECK(message);
+ AssertLockAcquired();
+
+ if (message->IsNull()) {
+ // This is a sync message and has been processed during sync handle
+ // watching.
+ return true;
+ }
+
+ if (PipeControlMessageHandler::IsPipeControlMessage(message)) {
+ bool result = false;
+
+ {
+ MayAutoUnlock unlocker(&lock_);
+ result = control_message_handler_.Accept(message);
+ }
+
+ if (!result)
+ RaiseErrorInNonTestingMode();
+
+ return true;
+ }
+
+ InterfaceId id = message->interface_id();
+ DCHECK(IsValidInterfaceId(id));
+
+ InterfaceEndpoint* endpoint = FindEndpoint(id);
+ if (!endpoint || endpoint->closed())
+ return true;
+
+ if (!endpoint->client()) {
+ // We need to wait until a client is attached in order to dispatch further
+ // messages.
+ return false;
+ }
+
+ bool can_direct_call;
+ if (message->has_flag(Message::kFlagIsSync)) {
+ can_direct_call = client_call_behavior != NO_DIRECT_CLIENT_CALLS &&
+ endpoint->task_runner()->BelongsToCurrentThread();
+ } else {
+ can_direct_call = client_call_behavior == ALLOW_DIRECT_CLIENT_CALLS &&
+ endpoint->task_runner() == current_task_runner;
+ }
+
+ if (!can_direct_call) {
+ MaybePostToProcessTasks(endpoint->task_runner());
+ return false;
+ }
+
+ DCHECK(endpoint->task_runner()->BelongsToCurrentThread());
+
+ InterfaceEndpointClient* client = endpoint->client();
+ bool result = false;
+ {
+ // We must unlock before calling into |client| because it may call this
+ // object within HandleIncomingMessage(). Holding the lock will lead to
+ // deadlock.
+ //
+ // It is safe to call into |client| without the lock. Because |client| is
+ // always accessed on the same thread, including DetachEndpointClient().
+ MayAutoUnlock unlocker(&lock_);
+ result = client->HandleIncomingMessage(message);
+ }
+ if (!result)
+ RaiseErrorInNonTestingMode();
+
+ return true;
+}
+
+void MultiplexRouter::MaybePostToProcessTasks(
+ base::SingleThreadTaskRunner* task_runner) {
+ AssertLockAcquired();
+ if (posted_to_process_tasks_)
+ return;
+
+ posted_to_process_tasks_ = true;
+ posted_to_task_runner_ = task_runner;
+ task_runner->PostTask(
+ FROM_HERE, base::Bind(&MultiplexRouter::LockAndCallProcessTasks, this));
+}
+
+void MultiplexRouter::LockAndCallProcessTasks() {
+ // There is no need to hold a ref to this class in this case because this is
+ // always called using base::Bind(), which holds a ref.
+ MayAutoLock locker(&lock_);
+ posted_to_process_tasks_ = false;
+ scoped_refptr<base::SingleThreadTaskRunner> runner(
+ std::move(posted_to_task_runner_));
+ ProcessTasks(ALLOW_DIRECT_CLIENT_CALLS, runner.get());
+}
+
+void MultiplexRouter::UpdateEndpointStateMayRemove(
+ InterfaceEndpoint* endpoint,
+ EndpointStateUpdateType type) {
+ if (type == ENDPOINT_CLOSED) {
+ endpoint->set_closed();
+ } else {
+ endpoint->set_peer_closed();
+ // If the interface endpoint is performing a sync watch, this makes sure
+ // it is notified and eventually exits the sync watch.
+ endpoint->SignalSyncMessageEvent();
+ }
+ if (endpoint->closed() && endpoint->peer_closed())
+ endpoints_.erase(endpoint->id());
+}
+
+void MultiplexRouter::RaiseErrorInNonTestingMode() {
+ AssertLockAcquired();
+ if (!testing_mode_)
+ RaiseError();
+}
+
+MultiplexRouter::InterfaceEndpoint* MultiplexRouter::FindOrInsertEndpoint(
+ InterfaceId id,
+ bool* inserted) {
+ AssertLockAcquired();
+ // Either |inserted| is nullptr or it points to a boolean initialized as
+ // false.
+ DCHECK(!inserted || !*inserted);
+
+ InterfaceEndpoint* endpoint = FindEndpoint(id);
+ if (!endpoint) {
+ endpoint = new InterfaceEndpoint(this, id);
+ endpoints_[id] = endpoint;
+ if (inserted)
+ *inserted = true;
+ }
+
+ return endpoint;
+}
+
+MultiplexRouter::InterfaceEndpoint* MultiplexRouter::FindEndpoint(
+ InterfaceId id) {
+ AssertLockAcquired();
+ auto iter = endpoints_.find(id);
+ return iter != endpoints_.end() ? iter->second.get() : nullptr;
+}
+
+void MultiplexRouter::AssertLockAcquired() {
+#if DCHECK_IS_ON()
+ if (lock_)
+ lock_->AssertAcquired();
+#endif
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.h b/mojo/public/cpp/bindings/lib/multiplex_router.h
new file mode 100644
index 0000000000..cac138bcb7
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.h
@@ -0,0 +1,275 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_
+
+#include <stdint.h>
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/message_header_validator.h"
+#include "mojo/public/cpp/bindings/pipe_control_message_handler.h"
+#include "mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h"
+#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace mojo {
+
+namespace internal {
+
+// MultiplexRouter supports routing messages for multiple interfaces over a
+// single message pipe.
+//
+// It is created on the thread where the master interface of the message pipe
+// lives. Although it is ref-counted, it is guarateed to be destructed on the
+// same thread.
+// Some public methods are only allowed to be called on the creating thread;
+// while the others are safe to call from any threads. Please see the method
+// comments for more details.
+//
+// NOTE: CloseMessagePipe() or PassMessagePipe() MUST be called on |runner|'s
+// thread before this object is destroyed.
+class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
+ : NON_EXPORTED_BASE(public MessageReceiver),
+ public AssociatedGroupController,
+ NON_EXPORTED_BASE(public PipeControlMessageHandlerDelegate) {
+ public:
+ enum Config {
+ // There is only the master interface running on this router. Please note
+ // that because of interface versioning, the other side of the message pipe
+ // may use a newer master interface definition which passes associated
+ // interfaces. In that case, this router may still receive pipe control
+ // messages or messages targetting associated interfaces.
+ SINGLE_INTERFACE,
+ // Similar to the mode above, there is only the master interface running on
+ // this router. Besides, the master interface has sync methods.
+ SINGLE_INTERFACE_WITH_SYNC_METHODS,
+ // There may be associated interfaces running on this router.
+ MULTI_INTERFACE
+ };
+
+ // If |set_interface_id_namespace_bit| is true, the interface IDs generated by
+ // this router will have the highest bit set.
+ MultiplexRouter(ScopedMessagePipeHandle message_pipe,
+ Config config,
+ bool set_interface_id_namespace_bit,
+ scoped_refptr<base::SingleThreadTaskRunner> runner);
+
+ // Sets the master interface name for this router. Only used when reporting
+ // message header or control message validation errors.
+ // |name| must be a string literal.
+ void SetMasterInterfaceName(const char* name);
+
+ // ---------------------------------------------------------------------------
+ // The following public methods are safe to call from any threads.
+
+ // AssociatedGroupController implementation:
+ InterfaceId AssociateInterface(
+ ScopedInterfaceEndpointHandle handle_to_send) override;
+ ScopedInterfaceEndpointHandle CreateLocalEndpointHandle(
+ InterfaceId id) override;
+ void CloseEndpointHandle(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) override;
+ InterfaceEndpointController* AttachEndpointClient(
+ const ScopedInterfaceEndpointHandle& handle,
+ InterfaceEndpointClient* endpoint_client,
+ scoped_refptr<base::SingleThreadTaskRunner> runner) override;
+ void DetachEndpointClient(
+ const ScopedInterfaceEndpointHandle& handle) override;
+ void RaiseError() override;
+
+ // ---------------------------------------------------------------------------
+ // The following public methods are called on the creating thread.
+
+ // Please note that this method shouldn't be called unless it results from an
+ // explicit request of the user of bindings (e.g., the user sets an
+ // InterfacePtr to null or closes a Binding).
+ void CloseMessagePipe();
+
+ // Extracts the underlying message pipe.
+ ScopedMessagePipeHandle PassMessagePipe() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!HasAssociatedEndpoints());
+ return connector_.PassMessagePipe();
+ }
+
+ // Blocks the current thread until the first incoming message, or |deadline|.
+ bool WaitForIncomingMessage(MojoDeadline deadline) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return connector_.WaitForIncomingMessage(deadline);
+ }
+
+ // See Binding for details of pause/resume.
+ void PauseIncomingMethodCallProcessing();
+ void ResumeIncomingMethodCallProcessing();
+
+ // Whether there are any associated interfaces running currently.
+ bool HasAssociatedEndpoints() const;
+
+ // Sets this object to testing mode.
+ // In testing mode, the object doesn't disconnect the underlying message pipe
+ // when it receives unexpected or invalid messages.
+ void EnableTestingMode();
+
+ // Is the router bound to a message pipe handle?
+ bool is_valid() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return connector_.is_valid();
+ }
+
+ // TODO(yzshen): consider removing this getter.
+ MessagePipeHandle handle() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return connector_.handle();
+ }
+
+ bool SimulateReceivingMessageForTesting(Message* message) {
+ return filters_.Accept(message);
+ }
+
+ private:
+ class InterfaceEndpoint;
+ class MessageWrapper;
+ struct Task;
+
+ ~MultiplexRouter() override;
+
+ // MessageReceiver implementation:
+ bool Accept(Message* message) override;
+
+ // PipeControlMessageHandlerDelegate implementation:
+ bool OnPeerAssociatedEndpointClosed(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) override;
+
+ void OnPipeConnectionError();
+
+ // Specifies whether we are allowed to directly call into
+ // InterfaceEndpointClient (given that we are already on the same thread as
+ // the client).
+ enum ClientCallBehavior {
+ // Don't call any InterfaceEndpointClient methods directly.
+ NO_DIRECT_CLIENT_CALLS,
+ // Only call InterfaceEndpointClient::HandleIncomingMessage directly to
+ // handle sync messages.
+ ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES,
+ // Allow to call any InterfaceEndpointClient methods directly.
+ ALLOW_DIRECT_CLIENT_CALLS
+ };
+
+ // Processes enqueued tasks (incoming messages and error notifications).
+ // |current_task_runner| is only used when |client_call_behavior| is
+ // ALLOW_DIRECT_CLIENT_CALLS to determine whether we are on the right task
+ // runner to make client calls for async messages or connection error
+ // notifications.
+ //
+ // Note: Because calling into InterfaceEndpointClient may lead to destruction
+ // of this object, if direct calls are allowed, the caller needs to hold on to
+ // a ref outside of |lock_| before calling this method.
+ void ProcessTasks(ClientCallBehavior client_call_behavior,
+ base::SingleThreadTaskRunner* current_task_runner);
+
+ // Processes the first queued sync message for the endpoint corresponding to
+ // |id|; returns whether there are more sync messages for that endpoint in the
+ // queue.
+ //
+ // This method is only used by enpoints during sync watching. Therefore, not
+ // all sync messages are handled by it.
+ bool ProcessFirstSyncMessageForEndpoint(InterfaceId id);
+
+ // Returns true to indicate that |task|/|message| has been processed.
+ bool ProcessNotifyErrorTask(
+ Task* task,
+ ClientCallBehavior client_call_behavior,
+ base::SingleThreadTaskRunner* current_task_runner);
+ bool ProcessIncomingMessage(
+ Message* message,
+ ClientCallBehavior client_call_behavior,
+ base::SingleThreadTaskRunner* current_task_runner);
+
+ void MaybePostToProcessTasks(base::SingleThreadTaskRunner* task_runner);
+ void LockAndCallProcessTasks();
+
+ // Updates the state of |endpoint|. If both the endpoint and its peer have
+ // been closed, removes it from |endpoints_|.
+ // NOTE: The method may invalidate |endpoint|.
+ enum EndpointStateUpdateType { ENDPOINT_CLOSED, PEER_ENDPOINT_CLOSED };
+ void UpdateEndpointStateMayRemove(InterfaceEndpoint* endpoint,
+ EndpointStateUpdateType type);
+
+ void RaiseErrorInNonTestingMode();
+
+ InterfaceEndpoint* FindOrInsertEndpoint(InterfaceId id, bool* inserted);
+ InterfaceEndpoint* FindEndpoint(InterfaceId id);
+
+ void AssertLockAcquired();
+
+ // Whether to set the namespace bit when generating interface IDs. Please see
+ // comments of kInterfaceIdNamespaceMask.
+ const bool set_interface_id_namespace_bit_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Owned by |filters_| below.
+ MessageHeaderValidator* header_validator_;
+
+ FilterChain filters_;
+ Connector connector_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Protects the following members.
+ // Not set in Config::SINGLE_INTERFACE* mode.
+ mutable base::Optional<base::Lock> lock_;
+ PipeControlMessageHandler control_message_handler_;
+
+ // NOTE: It is unsafe to call into this object while holding |lock_|.
+ PipeControlMessageProxy control_message_proxy_;
+
+ std::map<InterfaceId, scoped_refptr<InterfaceEndpoint>> endpoints_;
+ uint32_t next_interface_id_value_;
+
+ std::deque<std::unique_ptr<Task>> tasks_;
+ // It refers to tasks in |tasks_| and doesn't own any of them.
+ std::map<InterfaceId, std::deque<Task*>> sync_message_tasks_;
+
+ bool posted_to_process_tasks_;
+ scoped_refptr<base::SingleThreadTaskRunner> posted_to_task_runner_;
+
+ bool encountered_error_;
+
+ bool paused_;
+
+ bool testing_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiplexRouter);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_
diff --git a/mojo/public/cpp/bindings/lib/native_enum_data.h b/mojo/public/cpp/bindings/lib/native_enum_data.h
new file mode 100644
index 0000000000..dcafce2815
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_enum_data.h
@@ -0,0 +1,26 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_
+
+namespace mojo {
+namespace internal {
+
+class ValidationContext;
+
+class NativeEnum_Data {
+ public:
+ static bool const kIsExtensible = true;
+
+ static bool IsKnownValue(int32_t value) { return false; }
+
+ static bool Validate(int32_t value,
+ ValidationContext* validation_context) { return true; }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_
diff --git a/mojo/public/cpp/bindings/lib/native_enum_serialization.h b/mojo/public/cpp/bindings/lib/native_enum_serialization.h
new file mode 100644
index 0000000000..4faf957c58
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_enum_serialization.h
@@ -0,0 +1,82 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "ipc/ipc_param_traits.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/bindings/native_enum.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename MaybeConstUserType>
+struct NativeEnumSerializerImpl {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = IPC::ParamTraits<UserType>;
+
+ // IPC_ENUM_TRAITS* macros serialize enum as int, make sure that fits into
+ // mojo native-only enum.
+ static_assert(sizeof(NativeEnum) >= sizeof(int),
+ "Cannot store the serialization result in NativeEnum.");
+
+ static void Serialize(UserType input, int32_t* output) {
+ base::Pickle pickle;
+ Traits::Write(&pickle, input);
+
+ CHECK_GE(sizeof(int32_t), pickle.payload_size());
+ *output = 0;
+ memcpy(reinterpret_cast<char*>(output), pickle.payload(),
+ pickle.payload_size());
+ }
+
+ struct PickleData {
+ uint32_t payload_size;
+ int32_t value;
+ };
+ static_assert(sizeof(PickleData) == 8, "PickleData size mismatch.");
+
+ static bool Deserialize(int32_t input, UserType* output) {
+ PickleData data = {sizeof(int32_t), input};
+ base::Pickle pickle_view(reinterpret_cast<const char*>(&data),
+ sizeof(PickleData));
+ base::PickleIterator iter(pickle_view);
+ return Traits::Read(&pickle_view, &iter, output);
+ }
+};
+
+struct UnmappedNativeEnumSerializerImpl {
+ static void Serialize(NativeEnum input, int32_t* output) {
+ *output = static_cast<int32_t>(input);
+ }
+ static bool Deserialize(int32_t input, NativeEnum* output) {
+ *output = static_cast<NativeEnum>(input);
+ return true;
+ }
+};
+
+template <>
+struct NativeEnumSerializerImpl<NativeEnum>
+ : public UnmappedNativeEnumSerializerImpl {};
+
+template <>
+struct NativeEnumSerializerImpl<const NativeEnum>
+ : public UnmappedNativeEnumSerializerImpl {};
+
+template <typename MaybeConstUserType>
+struct Serializer<NativeEnum, MaybeConstUserType>
+ : public NativeEnumSerializerImpl<MaybeConstUserType> {};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/native_struct.cc b/mojo/public/cpp/bindings/lib/native_struct.cc
new file mode 100644
index 0000000000..7b1a1a6c59
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_struct.cc
@@ -0,0 +1,34 @@
+// 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 "mojo/public/cpp/bindings/native_struct.h"
+
+#include "mojo/public/cpp/bindings/lib/hash_util.h"
+
+namespace mojo {
+
+// static
+NativeStructPtr NativeStruct::New() {
+ return NativeStructPtr(base::in_place);
+}
+
+NativeStruct::NativeStruct() {}
+
+NativeStruct::~NativeStruct() {}
+
+NativeStructPtr NativeStruct::Clone() const {
+ NativeStructPtr rv(New());
+ rv->data = data;
+ return rv;
+}
+
+bool NativeStruct::Equals(const NativeStruct& other) const {
+ return data == other.data;
+}
+
+size_t NativeStruct::Hash(size_t seed) const {
+ return internal::Hash(seed, data);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/native_struct_data.cc b/mojo/public/cpp/bindings/lib/native_struct_data.cc
new file mode 100644
index 0000000000..0e5d245692
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_struct_data.cc
@@ -0,0 +1,22 @@
+// 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 "mojo/public/cpp/bindings/lib/native_struct_data.h"
+
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+
+namespace mojo {
+namespace internal {
+
+// static
+bool NativeStruct_Data::Validate(const void* data,
+ ValidationContext* validation_context) {
+ const ContainerValidateParams data_validate_params(0, false, nullptr);
+ return Array_Data<uint8_t>::Validate(data, validation_context,
+ &data_validate_params);
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/native_struct_data.h b/mojo/public/cpp/bindings/lib/native_struct_data.h
new file mode 100644
index 0000000000..1c7cd81c77
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_struct_data.h
@@ -0,0 +1,38 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_
+
+#include <vector>
+
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/system/handle.h"
+
+namespace mojo {
+namespace internal {
+
+class ValidationContext;
+
+class MOJO_CPP_BINDINGS_EXPORT NativeStruct_Data {
+ public:
+ static bool Validate(const void* data, ValidationContext* validation_context);
+
+ // Unlike normal structs, the memory layout is exactly the same as an array
+ // of uint8_t.
+ Array_Data<uint8_t> data;
+
+ private:
+ NativeStruct_Data() = delete;
+ ~NativeStruct_Data() = delete;
+};
+
+static_assert(sizeof(Array_Data<uint8_t>) == sizeof(NativeStruct_Data),
+ "Mismatched NativeStruct_Data and Array_Data<uint8_t> size");
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_
diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.cc b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
new file mode 100644
index 0000000000..fa0dbf3803
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h"
+
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+
+namespace mojo {
+namespace internal {
+
+// static
+size_t UnmappedNativeStructSerializerImpl::PrepareToSerialize(
+ const NativeStructPtr& input,
+ SerializationContext* context) {
+ if (!input)
+ return 0;
+ return internal::PrepareToSerialize<ArrayDataView<uint8_t>>(input->data,
+ context);
+}
+
+// static
+void UnmappedNativeStructSerializerImpl::Serialize(
+ const NativeStructPtr& input,
+ Buffer* buffer,
+ NativeStruct_Data** output,
+ SerializationContext* context) {
+ if (!input) {
+ *output = nullptr;
+ return;
+ }
+
+ Array_Data<uint8_t>* data = nullptr;
+ const ContainerValidateParams params(0, false, nullptr);
+ internal::Serialize<ArrayDataView<uint8_t>>(input->data, buffer, &data,
+ &params, context);
+ *output = reinterpret_cast<NativeStruct_Data*>(data);
+}
+
+// static
+bool UnmappedNativeStructSerializerImpl::Deserialize(
+ NativeStruct_Data* input,
+ NativeStructPtr* output,
+ SerializationContext* context) {
+ Array_Data<uint8_t>* data = reinterpret_cast<Array_Data<uint8_t>*>(input);
+
+ NativeStructPtr result(NativeStruct::New());
+ if (!internal::Deserialize<ArrayDataView<uint8_t>>(data, &result->data,
+ context)) {
+ output = nullptr;
+ return false;
+ }
+ if (!result->data)
+ *output = nullptr;
+ else
+ result.Swap(output);
+ return true;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.h b/mojo/public/cpp/bindings/lib/native_struct_serialization.h
new file mode 100644
index 0000000000..457435b955
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "ipc/ipc_param_traits.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/native_struct.h"
+#include "mojo/public/cpp/bindings/native_struct_data_view.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename MaybeConstUserType>
+struct NativeStructSerializerImpl {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = IPC::ParamTraits<UserType>;
+
+ static size_t PrepareToSerialize(MaybeConstUserType& value,
+ SerializationContext* context) {
+ base::PickleSizer sizer;
+ Traits::GetSize(&sizer, value);
+ return Align(sizer.payload_size() + sizeof(ArrayHeader));
+ }
+
+ static void Serialize(MaybeConstUserType& value,
+ Buffer* buffer,
+ NativeStruct_Data** out,
+ SerializationContext* context) {
+ base::Pickle pickle;
+ Traits::Write(&pickle, value);
+
+#if DCHECK_IS_ON()
+ base::PickleSizer sizer;
+ Traits::GetSize(&sizer, value);
+ DCHECK_EQ(sizer.payload_size(), pickle.payload_size());
+#endif
+
+ size_t total_size = pickle.payload_size() + sizeof(ArrayHeader);
+ DCHECK_LT(total_size, std::numeric_limits<uint32_t>::max());
+
+ // Allocate a uint8 array, initialize its header, and copy the Pickle in.
+ ArrayHeader* header =
+ reinterpret_cast<ArrayHeader*>(buffer->Allocate(total_size));
+ header->num_bytes = static_cast<uint32_t>(total_size);
+ header->num_elements = static_cast<uint32_t>(pickle.payload_size());
+ memcpy(reinterpret_cast<char*>(header) + sizeof(ArrayHeader),
+ pickle.payload(), pickle.payload_size());
+
+ *out = reinterpret_cast<NativeStruct_Data*>(header);
+ }
+
+ static bool Deserialize(NativeStruct_Data* data,
+ UserType* out,
+ SerializationContext* context) {
+ if (!data)
+ return false;
+
+ // Construct a temporary base::Pickle view over the array data. Note that
+ // the Array_Data is laid out like this:
+ //
+ // [num_bytes (4 bytes)] [num_elements (4 bytes)] [elements...]
+ //
+ // and base::Pickle expects to view data like this:
+ //
+ // [payload_size (4 bytes)] [header bytes ...] [payload...]
+ //
+ // Because ArrayHeader's num_bytes includes the length of the header and
+ // Pickle's payload_size does not, we need to adjust the stored value
+ // momentarily so Pickle can view the data.
+ ArrayHeader* header = reinterpret_cast<ArrayHeader*>(data);
+ DCHECK_GE(header->num_bytes, sizeof(ArrayHeader));
+ header->num_bytes -= sizeof(ArrayHeader);
+
+ {
+ // Construct a view over the full Array_Data, including our hacked up
+ // header. Pickle will infer from this that the header is 8 bytes long,
+ // and the payload will contain all of the pickled bytes.
+ base::Pickle pickle_view(reinterpret_cast<const char*>(header),
+ header->num_bytes + sizeof(ArrayHeader));
+ base::PickleIterator iter(pickle_view);
+ if (!Traits::Read(&pickle_view, &iter, out))
+ return false;
+ }
+
+ // Return the header to its original state.
+ header->num_bytes += sizeof(ArrayHeader);
+
+ return true;
+ }
+};
+
+struct MOJO_CPP_BINDINGS_EXPORT UnmappedNativeStructSerializerImpl {
+ static size_t PrepareToSerialize(const NativeStructPtr& input,
+ SerializationContext* context);
+ static void Serialize(const NativeStructPtr& input,
+ Buffer* buffer,
+ NativeStruct_Data** output,
+ SerializationContext* context);
+ static bool Deserialize(NativeStruct_Data* input,
+ NativeStructPtr* output,
+ SerializationContext* context);
+};
+
+template <>
+struct NativeStructSerializerImpl<NativeStructPtr>
+ : public UnmappedNativeStructSerializerImpl {};
+
+template <>
+struct NativeStructSerializerImpl<const NativeStructPtr>
+ : public UnmappedNativeStructSerializerImpl {};
+
+template <typename MaybeConstUserType>
+struct Serializer<NativeStructDataView, MaybeConstUserType>
+ : public NativeStructSerializerImpl<MaybeConstUserType> {};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc
new file mode 100644
index 0000000000..d451c05a5f
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc
@@ -0,0 +1,90 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/pipe_control_message_handler.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+#include "mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h"
+#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h"
+
+namespace mojo {
+
+PipeControlMessageHandler::PipeControlMessageHandler(
+ PipeControlMessageHandlerDelegate* delegate)
+ : delegate_(delegate) {}
+
+PipeControlMessageHandler::~PipeControlMessageHandler() {}
+
+void PipeControlMessageHandler::SetDescription(const std::string& description) {
+ description_ = description;
+}
+
+// static
+bool PipeControlMessageHandler::IsPipeControlMessage(const Message* message) {
+ return !IsValidInterfaceId(message->interface_id());
+}
+
+bool PipeControlMessageHandler::Accept(Message* message) {
+ if (!Validate(message))
+ return false;
+
+ if (message->name() == pipe_control::kRunOrClosePipeMessageId)
+ return RunOrClosePipe(message);
+
+ NOTREACHED();
+ return false;
+}
+
+bool PipeControlMessageHandler::Validate(Message* message) {
+ internal::ValidationContext validation_context(message->payload(),
+ message->payload_num_bytes(),
+ 0, 0, message, description_);
+
+ if (message->name() == pipe_control::kRunOrClosePipeMessageId) {
+ if (!internal::ValidateMessageIsRequestWithoutResponse(
+ message, &validation_context)) {
+ return false;
+ }
+ return internal::ValidateMessagePayload<
+ pipe_control::internal::RunOrClosePipeMessageParams_Data>(
+ message, &validation_context);
+ }
+
+ return false;
+}
+
+bool PipeControlMessageHandler::RunOrClosePipe(Message* message) {
+ internal::SerializationContext context;
+ pipe_control::internal::RunOrClosePipeMessageParams_Data* params =
+ reinterpret_cast<
+ pipe_control::internal::RunOrClosePipeMessageParams_Data*>(
+ message->mutable_payload());
+ pipe_control::RunOrClosePipeMessageParamsPtr params_ptr;
+ internal::Deserialize<pipe_control::RunOrClosePipeMessageParamsDataView>(
+ params, &params_ptr, &context);
+
+ if (params_ptr->input->is_peer_associated_endpoint_closed_event()) {
+ const auto& event =
+ params_ptr->input->get_peer_associated_endpoint_closed_event();
+
+ base::Optional<DisconnectReason> reason;
+ if (event->disconnect_reason) {
+ reason.emplace(event->disconnect_reason->custom_reason,
+ event->disconnect_reason->description);
+ }
+ return delegate_->OnPeerAssociatedEndpointClosed(event->id, reason);
+ }
+
+ DVLOG(1) << "Unsupported command in a RunOrClosePipe message pipe control "
+ << "message. Closing the pipe.";
+ return false;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
new file mode 100644
index 0000000000..1029c2c491
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
@@ -0,0 +1,68 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h"
+
+namespace mojo {
+namespace {
+
+Message ConstructRunOrClosePipeMessage(
+ pipe_control::RunOrClosePipeInputPtr input_ptr) {
+ internal::SerializationContext context;
+
+ auto params_ptr = pipe_control::RunOrClosePipeMessageParams::New();
+ params_ptr->input = std::move(input_ptr);
+
+ size_t size = internal::PrepareToSerialize<
+ pipe_control::RunOrClosePipeMessageParamsDataView>(params_ptr, &context);
+ internal::MessageBuilder builder(pipe_control::kRunOrClosePipeMessageId, 0,
+ size, 0);
+
+ pipe_control::internal::RunOrClosePipeMessageParams_Data* params = nullptr;
+ internal::Serialize<pipe_control::RunOrClosePipeMessageParamsDataView>(
+ params_ptr, builder.buffer(), &params, &context);
+ builder.message()->set_interface_id(kInvalidInterfaceId);
+ return std::move(*builder.message());
+}
+
+} // namespace
+
+PipeControlMessageProxy::PipeControlMessageProxy(MessageReceiver* receiver)
+ : receiver_(receiver) {}
+
+void PipeControlMessageProxy::NotifyPeerEndpointClosed(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) {
+ Message message(ConstructPeerEndpointClosedMessage(id, reason));
+ ignore_result(receiver_->Accept(&message));
+}
+
+// static
+Message PipeControlMessageProxy::ConstructPeerEndpointClosedMessage(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) {
+ auto event = pipe_control::PeerAssociatedEndpointClosedEvent::New();
+ event->id = id;
+ if (reason) {
+ event->disconnect_reason = pipe_control::DisconnectReason::New();
+ event->disconnect_reason->custom_reason = reason->custom_reason;
+ event->disconnect_reason->description = reason->description;
+ }
+
+ auto input = pipe_control::RunOrClosePipeInput::New();
+ input->set_peer_associated_endpoint_closed_event(std::move(event));
+
+ return ConstructRunOrClosePipeMessage(std::move(input));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
new file mode 100644
index 0000000000..c1345079a5
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
@@ -0,0 +1,382 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/synchronization/lock.h"
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+#include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
+
+namespace mojo {
+
+// ScopedInterfaceEndpointHandle::State ----------------------------------------
+
+// State could be called from multiple threads.
+class ScopedInterfaceEndpointHandle::State
+ : public base::RefCountedThreadSafe<State> {
+ public:
+ State() = default;
+
+ State(InterfaceId id,
+ scoped_refptr<AssociatedGroupController> group_controller)
+ : id_(id), group_controller_(group_controller) {}
+
+ void InitPendingState(scoped_refptr<State> peer) {
+ DCHECK(!lock_);
+ DCHECK(!pending_association_);
+
+ lock_.emplace();
+ pending_association_ = true;
+ peer_state_ = std::move(peer);
+ }
+
+ void Close(const base::Optional<DisconnectReason>& reason) {
+ scoped_refptr<AssociatedGroupController> cached_group_controller;
+ InterfaceId cached_id = kInvalidInterfaceId;
+ scoped_refptr<State> cached_peer_state;
+
+ {
+ internal::MayAutoLock locker(&lock_);
+
+ if (!association_event_handler_.is_null()) {
+ association_event_handler_.Reset();
+ runner_ = nullptr;
+ }
+
+ if (!pending_association_) {
+ if (IsValidInterfaceId(id_)) {
+ // Intentionally keep |group_controller_| unchanged.
+ // That is because the callback created by
+ // CreateGroupControllerGetter() could still be used after this point,
+ // potentially from another thread. We would like it to continue
+ // returning the same group controller.
+ //
+ // Imagine there is a ThreadSafeForwarder A:
+ // (1) On the IO thread, A's underlying associated interface pointer
+ // is closed.
+ // (2) On the proxy thread, the user makes a call on A to pass an
+ // associated request B_asso_req. The callback returned by
+ // CreateGroupControllerGetter() is used to associate B_asso_req.
+ // (3) On the proxy thread, the user immediately binds B_asso_ptr_info
+ // to B_asso_ptr and makes calls on it.
+ //
+ // If we reset |group_controller_| in step (1), step (2) won't be able
+ // to associate B_asso_req. Therefore, in step (3) B_asso_ptr won't be
+ // able to serialize associated endpoints or send message because it
+ // is still in "pending_association" state and doesn't have a group
+ // controller.
+ //
+ // We could "address" this issue by ignoring messages if there isn't a
+ // group controller. But the side effect is that we cannot detect
+ // programming errors of "using associated interface pointer before
+ // sending associated request".
+
+ cached_group_controller = group_controller_;
+ cached_id = id_;
+ id_ = kInvalidInterfaceId;
+ }
+ } else {
+ pending_association_ = false;
+ cached_peer_state = std::move(peer_state_);
+ }
+ }
+
+ if (cached_group_controller) {
+ cached_group_controller->CloseEndpointHandle(cached_id, reason);
+ } else if (cached_peer_state) {
+ cached_peer_state->OnPeerClosedBeforeAssociation(reason);
+ }
+ }
+
+ void SetAssociationEventHandler(AssociationEventCallback handler) {
+ internal::MayAutoLock locker(&lock_);
+
+ if (!pending_association_ && !IsValidInterfaceId(id_))
+ return;
+
+ association_event_handler_ = std::move(handler);
+ if (association_event_handler_.is_null()) {
+ runner_ = nullptr;
+ return;
+ }
+
+ runner_ = base::ThreadTaskRunnerHandle::Get();
+ if (!pending_association_) {
+ runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler,
+ this, runner_, ASSOCIATED));
+ } else if (!peer_state_) {
+ runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler,
+ this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION));
+ }
+ }
+
+ bool NotifyAssociation(
+ InterfaceId id,
+ scoped_refptr<AssociatedGroupController> peer_group_controller) {
+ scoped_refptr<State> cached_peer_state;
+ {
+ internal::MayAutoLock locker(&lock_);
+
+ DCHECK(pending_association_);
+ pending_association_ = false;
+ cached_peer_state = std::move(peer_state_);
+ }
+
+ if (cached_peer_state) {
+ cached_peer_state->OnAssociated(id, std::move(peer_group_controller));
+ return true;
+ }
+ return false;
+ }
+
+ bool is_valid() const {
+ internal::MayAutoLock locker(&lock_);
+ return pending_association_ || IsValidInterfaceId(id_);
+ }
+
+ bool pending_association() const {
+ internal::MayAutoLock locker(&lock_);
+ return pending_association_;
+ }
+
+ InterfaceId id() const {
+ internal::MayAutoLock locker(&lock_);
+ return id_;
+ }
+
+ AssociatedGroupController* group_controller() const {
+ internal::MayAutoLock locker(&lock_);
+ return group_controller_.get();
+ }
+
+ const base::Optional<DisconnectReason>& disconnect_reason() const {
+ internal::MayAutoLock locker(&lock_);
+ return disconnect_reason_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<State>;
+
+ ~State() {
+ DCHECK(!pending_association_);
+ DCHECK(!IsValidInterfaceId(id_));
+ }
+
+ // Called by the peer, maybe from a different thread.
+ void OnAssociated(InterfaceId id,
+ scoped_refptr<AssociatedGroupController> group_controller) {
+ AssociationEventCallback handler;
+ {
+ internal::MayAutoLock locker(&lock_);
+
+ // There may be race between Close() of endpoint A and
+ // NotifyPeerAssociation() of endpoint A_peer on different threads.
+ // Therefore, it is possible that endpoint A has been closed but it
+ // still gets OnAssociated() call from its peer.
+ if (!pending_association_)
+ return;
+
+ pending_association_ = false;
+ peer_state_ = nullptr;
+ id_ = id;
+ group_controller_ = std::move(group_controller);
+
+ if (!association_event_handler_.is_null()) {
+ if (runner_->BelongsToCurrentThread()) {
+ handler = std::move(association_event_handler_);
+ runner_ = nullptr;
+ } else {
+ runner_->PostTask(FROM_HERE,
+ base::Bind(&ScopedInterfaceEndpointHandle::State::
+ RunAssociationEventHandler,
+ this, runner_, ASSOCIATED));
+ }
+ }
+ }
+
+ if (!handler.is_null())
+ std::move(handler).Run(ASSOCIATED);
+ }
+
+ // Called by the peer, maybe from a different thread.
+ void OnPeerClosedBeforeAssociation(
+ const base::Optional<DisconnectReason>& reason) {
+ AssociationEventCallback handler;
+ {
+ internal::MayAutoLock locker(&lock_);
+
+ // There may be race between Close()/NotifyPeerAssociation() of endpoint
+ // A and Close() of endpoint A_peer on different threads.
+ // Therefore, it is possible that endpoint A is not in pending association
+ // state but still gets OnPeerClosedBeforeAssociation() call from its
+ // peer.
+ if (!pending_association_)
+ return;
+
+ disconnect_reason_ = reason;
+ // NOTE: This handle itself is still pending.
+ peer_state_ = nullptr;
+
+ if (!association_event_handler_.is_null()) {
+ if (runner_->BelongsToCurrentThread()) {
+ handler = std::move(association_event_handler_);
+ runner_ = nullptr;
+ } else {
+ runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ScopedInterfaceEndpointHandle::State::
+ RunAssociationEventHandler,
+ this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION));
+ }
+ }
+ }
+
+ if (!handler.is_null())
+ std::move(handler).Run(PEER_CLOSED_BEFORE_ASSOCIATION);
+ }
+
+ void RunAssociationEventHandler(
+ scoped_refptr<base::SingleThreadTaskRunner> posted_to_runner,
+ AssociationEvent event) {
+ AssociationEventCallback handler;
+
+ {
+ internal::MayAutoLock locker(&lock_);
+ if (posted_to_runner == runner_) {
+ runner_ = nullptr;
+ handler = std::move(association_event_handler_);
+ }
+ }
+
+ if (!handler.is_null())
+ std::move(handler).Run(event);
+ }
+
+ // Protects the following members if the handle is initially set to pending
+ // association.
+ mutable base::Optional<base::Lock> lock_;
+
+ bool pending_association_ = false;
+ base::Optional<DisconnectReason> disconnect_reason_;
+
+ scoped_refptr<State> peer_state_;
+
+ AssociationEventCallback association_event_handler_;
+ scoped_refptr<base::SingleThreadTaskRunner> runner_;
+
+ InterfaceId id_ = kInvalidInterfaceId;
+ scoped_refptr<AssociatedGroupController> group_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+// ScopedInterfaceEndpointHandle -----------------------------------------------
+
+// static
+void ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(
+ ScopedInterfaceEndpointHandle* handle0,
+ ScopedInterfaceEndpointHandle* handle1) {
+ ScopedInterfaceEndpointHandle result0;
+ ScopedInterfaceEndpointHandle result1;
+ result0.state_->InitPendingState(result1.state_);
+ result1.state_->InitPendingState(result0.state_);
+
+ *handle0 = std::move(result0);
+ *handle1 = std::move(result1);
+}
+
+ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle()
+ : state_(new State) {}
+
+ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle(
+ ScopedInterfaceEndpointHandle&& other)
+ : state_(new State) {
+ state_.swap(other.state_);
+}
+
+ScopedInterfaceEndpointHandle::~ScopedInterfaceEndpointHandle() {
+ state_->Close(base::nullopt);
+}
+
+ScopedInterfaceEndpointHandle& ScopedInterfaceEndpointHandle::operator=(
+ ScopedInterfaceEndpointHandle&& other) {
+ reset();
+ state_.swap(other.state_);
+ return *this;
+}
+
+bool ScopedInterfaceEndpointHandle::is_valid() const {
+ return state_->is_valid();
+}
+
+bool ScopedInterfaceEndpointHandle::pending_association() const {
+ return state_->pending_association();
+}
+
+InterfaceId ScopedInterfaceEndpointHandle::id() const {
+ return state_->id();
+}
+
+AssociatedGroupController* ScopedInterfaceEndpointHandle::group_controller()
+ const {
+ return state_->group_controller();
+}
+
+const base::Optional<DisconnectReason>&
+ScopedInterfaceEndpointHandle::disconnect_reason() const {
+ return state_->disconnect_reason();
+}
+
+void ScopedInterfaceEndpointHandle::SetAssociationEventHandler(
+ AssociationEventCallback handler) {
+ state_->SetAssociationEventHandler(std::move(handler));
+}
+
+void ScopedInterfaceEndpointHandle::reset() {
+ ResetInternal(base::nullopt);
+}
+
+void ScopedInterfaceEndpointHandle::ResetWithReason(
+ uint32_t custom_reason,
+ const std::string& description) {
+ ResetInternal(DisconnectReason(custom_reason, description));
+}
+
+ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle(
+ InterfaceId id,
+ scoped_refptr<AssociatedGroupController> group_controller)
+ : state_(new State(id, std::move(group_controller))) {
+ DCHECK(!IsValidInterfaceId(state_->id()) || state_->group_controller());
+}
+
+bool ScopedInterfaceEndpointHandle::NotifyAssociation(
+ InterfaceId id,
+ scoped_refptr<AssociatedGroupController> peer_group_controller) {
+ return state_->NotifyAssociation(id, peer_group_controller);
+}
+
+void ScopedInterfaceEndpointHandle::ResetInternal(
+ const base::Optional<DisconnectReason>& reason) {
+ scoped_refptr<State> new_state(new State);
+ state_->Close(reason);
+ state_.swap(new_state);
+}
+
+base::Callback<AssociatedGroupController*()>
+ScopedInterfaceEndpointHandle::CreateGroupControllerGetter() const {
+ // We allow this callback to be run on any thread. If this handle is created
+ // in non-pending state, we don't have a lock but it should still be safe
+ // because the group controller never changes.
+ return base::Bind(&State::group_controller, state_);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/serialization.h b/mojo/public/cpp/bindings/lib/serialization.h
new file mode 100644
index 0000000000..2a7d288d55
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/serialization.h
@@ -0,0 +1,107 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_
+
+#include <string.h>
+
+#include "mojo/public/cpp/bindings/array_traits_carray.h"
+#include "mojo/public/cpp/bindings/array_traits_stl.h"
+#include "mojo/public/cpp/bindings/lib/array_serialization.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+#include "mojo/public/cpp/bindings/lib/handle_interface_serialization.h"
+#include "mojo/public/cpp/bindings/lib/map_serialization.h"
+#include "mojo/public/cpp/bindings/lib/native_enum_serialization.h"
+#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h"
+#include "mojo/public/cpp/bindings/lib/string_serialization.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/public/cpp/bindings/map_traits_stl.h"
+#include "mojo/public/cpp/bindings/string_traits_stl.h"
+#include "mojo/public/cpp/bindings/string_traits_string16.h"
+#include "mojo/public/cpp/bindings/string_traits_string_piece.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename MojomType, typename DataArrayType, typename UserType>
+DataArrayType StructSerializeImpl(UserType* input) {
+ static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value,
+ "Unexpected type.");
+
+ SerializationContext context;
+ size_t size = PrepareToSerialize<MojomType>(*input, &context);
+ DCHECK_EQ(size, Align(size));
+
+ DataArrayType result(size);
+ if (size == 0)
+ return result;
+
+ void* result_buffer = &result.front();
+ // The serialization logic requires that the buffer is 8-byte aligned. If the
+ // result buffer is not properly aligned, we have to do an extra copy. In
+ // practice, this should never happen for std::vector.
+ bool need_copy = !IsAligned(result_buffer);
+
+ if (need_copy) {
+ // calloc sets the memory to all zero.
+ result_buffer = calloc(size, 1);
+ DCHECK(IsAligned(result_buffer));
+ }
+
+ Buffer buffer;
+ buffer.Initialize(result_buffer, size);
+ typename MojomTypeTraits<MojomType>::Data* data = nullptr;
+ Serialize<MojomType>(*input, &buffer, &data, &context);
+
+ if (need_copy) {
+ memcpy(&result.front(), result_buffer, size);
+ free(result_buffer);
+ }
+
+ return result;
+}
+
+template <typename MojomType, typename DataArrayType, typename UserType>
+bool StructDeserializeImpl(const DataArrayType& input,
+ UserType* output,
+ bool (*validate_func)(const void*,
+ ValidationContext*)) {
+ static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value,
+ "Unexpected type.");
+ using DataType = typename MojomTypeTraits<MojomType>::Data;
+
+ // TODO(sammc): Use DataArrayType::empty() once WTF::Vector::empty() exists.
+ void* input_buffer =
+ input.size() == 0
+ ? nullptr
+ : const_cast<void*>(reinterpret_cast<const void*>(&input.front()));
+
+ // Please see comments in StructSerializeImpl.
+ bool need_copy = !IsAligned(input_buffer);
+
+ if (need_copy) {
+ input_buffer = malloc(input.size());
+ DCHECK(IsAligned(input_buffer));
+ memcpy(input_buffer, &input.front(), input.size());
+ }
+
+ ValidationContext validation_context(input_buffer, input.size(), 0, 0);
+ bool result = false;
+ if (validate_func(input_buffer, &validation_context)) {
+ auto data = reinterpret_cast<DataType*>(input_buffer);
+ SerializationContext context;
+ result = Deserialize<MojomType>(data, output, &context);
+ }
+
+ if (need_copy)
+ free(input_buffer);
+
+ return result;
+}
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/serialization_context.cc b/mojo/public/cpp/bindings/lib/serialization_context.cc
new file mode 100644
index 0000000000..e2fd5c6e18
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/serialization_context.cc
@@ -0,0 +1,57 @@
+// 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 "mojo/public/cpp/bindings/lib/serialization_context.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace internal {
+
+SerializedHandleVector::SerializedHandleVector() {}
+
+SerializedHandleVector::~SerializedHandleVector() {
+ for (auto handle : handles_) {
+ if (handle.is_valid()) {
+ MojoResult rv = MojoClose(handle.value());
+ DCHECK_EQ(rv, MOJO_RESULT_OK);
+ }
+ }
+}
+
+Handle_Data SerializedHandleVector::AddHandle(mojo::Handle handle) {
+ Handle_Data data;
+ if (!handle.is_valid()) {
+ data.value = kEncodedInvalidHandleValue;
+ } else {
+ DCHECK_LT(handles_.size(), std::numeric_limits<uint32_t>::max());
+ data.value = static_cast<uint32_t>(handles_.size());
+ handles_.push_back(handle);
+ }
+ return data;
+}
+
+mojo::Handle SerializedHandleVector::TakeHandle(
+ const Handle_Data& encoded_handle) {
+ if (!encoded_handle.is_valid())
+ return mojo::Handle();
+ DCHECK_LT(encoded_handle.value, handles_.size());
+ return FetchAndReset(&handles_[encoded_handle.value]);
+}
+
+void SerializedHandleVector::Swap(std::vector<mojo::Handle>* other) {
+ handles_.swap(*other);
+}
+
+SerializationContext::SerializationContext() {}
+
+SerializationContext::~SerializationContext() {
+ DCHECK(!custom_contexts || custom_contexts->empty());
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/serialization_context.h b/mojo/public/cpp/bindings/lib/serialization_context.h
new file mode 100644
index 0000000000..a34fe3d4ed
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/serialization_context.h
@@ -0,0 +1,77 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <queue>
+#include <vector>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/system/handle.h"
+
+namespace mojo {
+namespace internal {
+
+// A container for handles during serialization/deserialization.
+class MOJO_CPP_BINDINGS_EXPORT SerializedHandleVector {
+ public:
+ SerializedHandleVector();
+ ~SerializedHandleVector();
+
+ size_t size() const { return handles_.size(); }
+
+ // Adds a handle to the handle list and returns its index for encoding.
+ Handle_Data AddHandle(mojo::Handle handle);
+
+ // Takes a handle from the list of serialized handle data.
+ mojo::Handle TakeHandle(const Handle_Data& encoded_handle);
+
+ // Takes a handle from the list of serialized handle data and returns it in
+ // |*out_handle| as a specific scoped handle type.
+ template <typename T>
+ ScopedHandleBase<T> TakeHandleAs(const Handle_Data& encoded_handle) {
+ return MakeScopedHandle(T(TakeHandle(encoded_handle).value()));
+ }
+
+ // Swaps all owned handles out with another Handle vector.
+ void Swap(std::vector<mojo::Handle>* other);
+
+ private:
+ // Handles are owned by this object.
+ std::vector<mojo::Handle> handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerializedHandleVector);
+};
+
+// Context information for serialization/deserialization routines.
+struct MOJO_CPP_BINDINGS_EXPORT SerializationContext {
+ SerializationContext();
+
+ ~SerializationContext();
+
+ // Opaque context pointers returned by StringTraits::SetUpContext().
+ std::unique_ptr<std::queue<void*>> custom_contexts;
+
+ // Stashes handles encoded in a message by index.
+ SerializedHandleVector handles;
+
+ // The number of ScopedInterfaceEndpointHandles that need to be serialized.
+ // It is calculated by PrepareToSerialize().
+ uint32_t associated_endpoint_count = 0;
+
+ // Stashes ScopedInterfaceEndpointHandles encoded in a message by index.
+ std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles;
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_
diff --git a/mojo/public/cpp/bindings/lib/serialization_forward.h b/mojo/public/cpp/bindings/lib/serialization_forward.h
new file mode 100644
index 0000000000..55c9982ccc
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/serialization_forward.h
@@ -0,0 +1,123 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_
+
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/array_traits.h"
+#include "mojo/public/cpp/bindings/enum_traits.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/public/cpp/bindings/map_traits.h"
+#include "mojo/public/cpp/bindings/string_traits.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+
+// This file is included by serialization implementation files to avoid circular
+// includes.
+// Users of the serialization funtions should include serialization.h (and also
+// wtf_serialization.h if necessary).
+
+namespace mojo {
+namespace internal {
+
+template <typename MojomType, typename MaybeConstUserType>
+struct Serializer;
+
+template <typename T>
+struct IsOptionalWrapper {
+ static const bool value = IsSpecializationOf<
+ base::Optional,
+ typename std::remove_const<
+ typename std::remove_reference<T>::type>::type>::value;
+};
+
+// PrepareToSerialize() must be matched by a Serialize() for the same input
+// later. Moreover, within the same SerializationContext if PrepareToSerialize()
+// is called for |input_1|, ..., |input_n|, Serialize() must be called for
+// those objects in the exact same order.
+template <typename MojomType,
+ typename InputUserType,
+ typename... Args,
+ typename std::enable_if<
+ !IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
+size_t PrepareToSerialize(InputUserType&& input, Args&&... args) {
+ return Serializer<MojomType,
+ typename std::remove_reference<InputUserType>::type>::
+ PrepareToSerialize(std::forward<InputUserType>(input),
+ std::forward<Args>(args)...);
+}
+
+template <typename MojomType,
+ typename InputUserType,
+ typename... Args,
+ typename std::enable_if<
+ !IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
+void Serialize(InputUserType&& input, Args&&... args) {
+ Serializer<MojomType, typename std::remove_reference<InputUserType>::type>::
+ Serialize(std::forward<InputUserType>(input),
+ std::forward<Args>(args)...);
+}
+
+template <typename MojomType,
+ typename DataType,
+ typename InputUserType,
+ typename... Args,
+ typename std::enable_if<
+ !IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
+bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) {
+ return Serializer<MojomType, InputUserType>::Deserialize(
+ std::forward<DataType>(input), output, std::forward<Args>(args)...);
+}
+
+// Specialization that unwraps base::Optional<>.
+template <typename MojomType,
+ typename InputUserType,
+ typename... Args,
+ typename std::enable_if<
+ IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
+size_t PrepareToSerialize(InputUserType&& input, Args&&... args) {
+ if (!input)
+ return 0;
+ return PrepareToSerialize<MojomType>(*input, std::forward<Args>(args)...);
+}
+
+template <typename MojomType,
+ typename InputUserType,
+ typename DataType,
+ typename... Args,
+ typename std::enable_if<
+ IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
+void Serialize(InputUserType&& input,
+ Buffer* buffer,
+ DataType** output,
+ Args&&... args) {
+ if (!input) {
+ *output = nullptr;
+ return;
+ }
+ Serialize<MojomType>(*input, buffer, output, std::forward<Args>(args)...);
+}
+
+template <typename MojomType,
+ typename DataType,
+ typename InputUserType,
+ typename... Args,
+ typename std::enable_if<
+ IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
+bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) {
+ if (!input) {
+ *output = base::nullopt;
+ return true;
+ }
+ if (!*output)
+ output->emplace();
+ return Deserialize<MojomType>(std::forward<DataType>(input), &output->value(),
+ std::forward<Args>(args)...);
+}
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_
diff --git a/mojo/public/cpp/bindings/lib/serialization_util.h b/mojo/public/cpp/bindings/lib/serialization_util.h
new file mode 100644
index 0000000000..4820a014ec
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/serialization_util.h
@@ -0,0 +1,213 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <queue>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename T>
+struct HasIsNullMethod {
+ template <typename U>
+ static char Test(decltype(U::IsNull)*);
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <
+ typename Traits,
+ typename UserType,
+ typename std::enable_if<HasIsNullMethod<Traits>::value>::type* = nullptr>
+bool CallIsNullIfExists(const UserType& input) {
+ return Traits::IsNull(input);
+}
+
+template <
+ typename Traits,
+ typename UserType,
+ typename std::enable_if<!HasIsNullMethod<Traits>::value>::type* = nullptr>
+bool CallIsNullIfExists(const UserType& input) {
+ return false;
+}
+template <typename T>
+struct HasSetToNullMethod {
+ template <typename U>
+ static char Test(decltype(U::SetToNull)*);
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <
+ typename Traits,
+ typename UserType,
+ typename std::enable_if<HasSetToNullMethod<Traits>::value>::type* = nullptr>
+bool CallSetToNullIfExists(UserType* output) {
+ Traits::SetToNull(output);
+ return true;
+}
+
+template <typename Traits,
+ typename UserType,
+ typename std::enable_if<!HasSetToNullMethod<Traits>::value>::type* =
+ nullptr>
+bool CallSetToNullIfExists(UserType* output) {
+ LOG(ERROR) << "A null value is received. But the Struct/Array/StringTraits "
+ << "class doesn't define a SetToNull() function and therefore is "
+ << "unable to deserialize the value.";
+ return false;
+}
+
+template <typename T>
+struct HasSetUpContextMethod {
+ template <typename U>
+ static char Test(decltype(U::SetUpContext)*);
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <typename Traits,
+ bool has_context = HasSetUpContextMethod<Traits>::value>
+struct CustomContextHelper;
+
+template <typename Traits>
+struct CustomContextHelper<Traits, true> {
+ template <typename MaybeConstUserType>
+ static void* SetUp(MaybeConstUserType& input, SerializationContext* context) {
+ void* custom_context = Traits::SetUpContext(input);
+ if (!context->custom_contexts)
+ context->custom_contexts.reset(new std::queue<void*>());
+ context->custom_contexts->push(custom_context);
+ return custom_context;
+ }
+
+ static void* GetNext(SerializationContext* context) {
+ void* custom_context = context->custom_contexts->front();
+ context->custom_contexts->pop();
+ return custom_context;
+ }
+
+ template <typename MaybeConstUserType>
+ static void TearDown(MaybeConstUserType& input, void* custom_context) {
+ Traits::TearDownContext(input, custom_context);
+ }
+};
+
+template <typename Traits>
+struct CustomContextHelper<Traits, false> {
+ template <typename MaybeConstUserType>
+ static void* SetUp(MaybeConstUserType& input, SerializationContext* context) {
+ return nullptr;
+ }
+
+ static void* GetNext(SerializationContext* context) { return nullptr; }
+
+ template <typename MaybeConstUserType>
+ static void TearDown(MaybeConstUserType& input, void* custom_context) {
+ DCHECK(!custom_context);
+ }
+};
+
+template <typename ReturnType, typename ParamType, typename InputUserType>
+ReturnType CallWithContext(ReturnType (*f)(ParamType, void*),
+ InputUserType&& input,
+ void* context) {
+ return f(std::forward<InputUserType>(input), context);
+}
+
+template <typename ReturnType, typename ParamType, typename InputUserType>
+ReturnType CallWithContext(ReturnType (*f)(ParamType),
+ InputUserType&& input,
+ void* context) {
+ return f(std::forward<InputUserType>(input));
+}
+
+template <typename T, typename MaybeConstUserType>
+struct HasGetBeginMethod {
+ template <typename U>
+ static char Test(decltype(U::GetBegin(std::declval<MaybeConstUserType&>()))*);
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <
+ typename Traits,
+ typename MaybeConstUserType,
+ typename std::enable_if<
+ HasGetBeginMethod<Traits, MaybeConstUserType>::value>::type* = nullptr>
+decltype(Traits::GetBegin(std::declval<MaybeConstUserType&>()))
+CallGetBeginIfExists(MaybeConstUserType& input) {
+ return Traits::GetBegin(input);
+}
+
+template <
+ typename Traits,
+ typename MaybeConstUserType,
+ typename std::enable_if<
+ !HasGetBeginMethod<Traits, MaybeConstUserType>::value>::type* = nullptr>
+size_t CallGetBeginIfExists(MaybeConstUserType& input) {
+ return 0;
+}
+
+template <typename T, typename MaybeConstUserType>
+struct HasGetDataMethod {
+ template <typename U>
+ static char Test(decltype(U::GetData(std::declval<MaybeConstUserType&>()))*);
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ EnsureTypeIsComplete<T> check_t_;
+};
+
+template <
+ typename Traits,
+ typename MaybeConstUserType,
+ typename std::enable_if<
+ HasGetDataMethod<Traits, MaybeConstUserType>::value>::type* = nullptr>
+decltype(Traits::GetData(std::declval<MaybeConstUserType&>()))
+CallGetDataIfExists(MaybeConstUserType& input) {
+ return Traits::GetData(input);
+}
+
+template <
+ typename Traits,
+ typename MaybeConstUserType,
+ typename std::enable_if<
+ !HasGetDataMethod<Traits, MaybeConstUserType>::value>::type* = nullptr>
+void* CallGetDataIfExists(MaybeConstUserType& input) {
+ return nullptr;
+}
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h
new file mode 100644
index 0000000000..6e0c758576
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/string_serialization.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_
+
+#include <stddef.h>
+#include <string.h>
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/string_data_view.h"
+#include "mojo/public/cpp/bindings/string_traits.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename MaybeConstUserType>
+struct Serializer<StringDataView, MaybeConstUserType> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = StringTraits<UserType>;
+
+ static size_t PrepareToSerialize(MaybeConstUserType& input,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input))
+ return 0;
+
+ void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
+ return Align(sizeof(String_Data) +
+ CallWithContext(Traits::GetSize, input, custom_context));
+ }
+
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buffer,
+ String_Data** output,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input)) {
+ *output = nullptr;
+ return;
+ }
+
+ void* custom_context = CustomContextHelper<Traits>::GetNext(context);
+
+ String_Data* result = String_Data::New(
+ CallWithContext(Traits::GetSize, input, custom_context), buffer);
+ if (result) {
+ memcpy(result->storage(),
+ CallWithContext(Traits::GetData, input, custom_context),
+ CallWithContext(Traits::GetSize, input, custom_context));
+ }
+ *output = result;
+
+ CustomContextHelper<Traits>::TearDown(input, custom_context);
+ }
+
+ static bool Deserialize(String_Data* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!input)
+ return CallSetToNullIfExists<Traits>(output);
+ return Traits::Read(StringDataView(input, context), output);
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/string_traits_string16.cc b/mojo/public/cpp/bindings/lib/string_traits_string16.cc
new file mode 100644
index 0000000000..95ff6ccf25
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/string_traits_string16.cc
@@ -0,0 +1,42 @@
+// 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 "mojo/public/cpp/bindings/string_traits_string16.h"
+
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+
+namespace mojo {
+
+// static
+void* StringTraits<base::string16>::SetUpContext(const base::string16& input) {
+ return new std::string(base::UTF16ToUTF8(input));
+}
+
+// static
+void StringTraits<base::string16>::TearDownContext(const base::string16& input,
+ void* context) {
+ delete static_cast<std::string*>(context);
+}
+
+// static
+size_t StringTraits<base::string16>::GetSize(const base::string16& input,
+ void* context) {
+ return static_cast<std::string*>(context)->size();
+}
+
+// static
+const char* StringTraits<base::string16>::GetData(const base::string16& input,
+ void* context) {
+ return static_cast<std::string*>(context)->data();
+}
+
+// static
+bool StringTraits<base::string16>::Read(StringDataView input,
+ base::string16* output) {
+ return base::UTF8ToUTF16(input.storage(), input.size(), output);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/string_traits_wtf.cc b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
new file mode 100644
index 0000000000..203f6f5903
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
@@ -0,0 +1,84 @@
+// 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 "mojo/public/cpp/bindings/string_traits_wtf.h"
+
+#include <string.h>
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "third_party/WebKit/Source/wtf/text/StringUTF8Adaptor.h"
+
+namespace mojo {
+namespace {
+
+struct UTF8AdaptorInfo {
+ explicit UTF8AdaptorInfo(const WTF::String& input) : utf8_adaptor(input) {
+#if DCHECK_IS_ON()
+ original_size_in_bytes = input.charactersSizeInBytes();
+#endif
+ }
+
+ ~UTF8AdaptorInfo() {}
+
+ WTF::StringUTF8Adaptor utf8_adaptor;
+
+#if DCHECK_IS_ON()
+ // For sanity check only.
+ size_t original_size_in_bytes;
+#endif
+};
+
+UTF8AdaptorInfo* ToAdaptor(const WTF::String& input, void* context) {
+ UTF8AdaptorInfo* adaptor = static_cast<UTF8AdaptorInfo*>(context);
+
+#if DCHECK_IS_ON()
+ DCHECK_EQ(adaptor->original_size_in_bytes, input.charactersSizeInBytes());
+#endif
+ return adaptor;
+}
+
+} // namespace
+
+// static
+void StringTraits<WTF::String>::SetToNull(WTF::String* output) {
+ if (output->isNull())
+ return;
+
+ WTF::String result;
+ output->swap(result);
+}
+
+// static
+void* StringTraits<WTF::String>::SetUpContext(const WTF::String& input) {
+ return new UTF8AdaptorInfo(input);
+}
+
+// static
+void StringTraits<WTF::String>::TearDownContext(const WTF::String& input,
+ void* context) {
+ delete ToAdaptor(input, context);
+}
+
+// static
+size_t StringTraits<WTF::String>::GetSize(const WTF::String& input,
+ void* context) {
+ return ToAdaptor(input, context)->utf8_adaptor.length();
+}
+
+// static
+const char* StringTraits<WTF::String>::GetData(const WTF::String& input,
+ void* context) {
+ return ToAdaptor(input, context)->utf8_adaptor.data();
+}
+
+// static
+bool StringTraits<WTF::String>::Read(StringDataView input,
+ WTF::String* output) {
+ WTF::String result = WTF::String::fromUTF8(input.storage(), input.size());
+ output->swap(result);
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
new file mode 100644
index 0000000000..585a8f094c
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
@@ -0,0 +1,93 @@
+// 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 "mojo/public/cpp/bindings/sync_call_restrictions.h"
+
+#if ENABLE_SYNC_CALL_RESTRICTIONS
+
+#include "base/debug/leak_annotations.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/threading/thread_local.h"
+#include "mojo/public/c/system/core.h"
+
+namespace mojo {
+
+namespace {
+
+class SyncCallSettings {
+ public:
+ static SyncCallSettings* current();
+
+ bool allowed() const {
+ return scoped_allow_count_ > 0 || system_defined_value_;
+ }
+
+ void IncreaseScopedAllowCount() { scoped_allow_count_++; }
+ void DecreaseScopedAllowCount() {
+ DCHECK_LT(0u, scoped_allow_count_);
+ scoped_allow_count_--;
+ }
+
+ private:
+ SyncCallSettings();
+ ~SyncCallSettings();
+
+ bool system_defined_value_ = true;
+ size_t scoped_allow_count_ = 0;
+};
+
+base::LazyInstance<base::ThreadLocalPointer<SyncCallSettings>>::DestructorAtExit
+ g_sync_call_settings = LAZY_INSTANCE_INITIALIZER;
+
+// static
+SyncCallSettings* SyncCallSettings::current() {
+ SyncCallSettings* result = g_sync_call_settings.Pointer()->Get();
+ if (!result) {
+ result = new SyncCallSettings();
+ ANNOTATE_LEAKING_OBJECT_PTR(result);
+ DCHECK_EQ(result, g_sync_call_settings.Pointer()->Get());
+ }
+ return result;
+}
+
+SyncCallSettings::SyncCallSettings() {
+ MojoResult result = MojoGetProperty(MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED,
+ &system_defined_value_);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+
+ DCHECK(!g_sync_call_settings.Pointer()->Get());
+ g_sync_call_settings.Pointer()->Set(this);
+}
+
+SyncCallSettings::~SyncCallSettings() {
+ g_sync_call_settings.Pointer()->Set(nullptr);
+}
+
+} // namespace
+
+// static
+void SyncCallRestrictions::AssertSyncCallAllowed() {
+ if (!SyncCallSettings::current()->allowed()) {
+ LOG(FATAL) << "Mojo sync calls are not allowed in this process because "
+ << "they can lead to jank and deadlock. If you must make an "
+ << "exception, please see "
+ << "SyncCallRestrictions::ScopedAllowSyncCall and consult "
+ << "mojo/OWNERS.";
+ }
+}
+
+// static
+void SyncCallRestrictions::IncreaseScopedAllowCount() {
+ SyncCallSettings::current()->IncreaseScopedAllowCount();
+}
+
+// static
+void SyncCallRestrictions::DecreaseScopedAllowCount() {
+ SyncCallSettings::current()->DecreaseScopedAllowCount();
+}
+
+} // namespace mojo
+
+#endif // ENABLE_SYNC_CALL_RESTRICTIONS
diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
new file mode 100644
index 0000000000..b1c97e3691
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+
+SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event,
+ const base::Closure& callback)
+ : event_(event),
+ callback_(callback),
+ registry_(SyncHandleRegistry::current()),
+ destroyed_(new base::RefCountedData<bool>(false)) {}
+
+SyncEventWatcher::~SyncEventWatcher() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (registered_)
+ registry_->UnregisterEvent(event_);
+ destroyed_->data = true;
+}
+
+void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ IncrementRegisterCount();
+}
+
+bool SyncEventWatcher::SyncWatch(const bool* should_stop) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ IncrementRegisterCount();
+ if (!registered_) {
+ DecrementRegisterCount();
+ return false;
+ }
+
+ // This object may be destroyed during the Wait() call. So we have to preserve
+ // the boolean that Wait uses.
+ auto destroyed = destroyed_;
+ const bool* should_stop_array[] = {should_stop, &destroyed->data};
+ bool result = registry_->Wait(should_stop_array, 2);
+
+ // This object has been destroyed.
+ if (destroyed->data)
+ return false;
+
+ DecrementRegisterCount();
+ return result;
+}
+
+void SyncEventWatcher::IncrementRegisterCount() {
+ register_request_count_++;
+ if (!registered_)
+ registered_ = registry_->RegisterEvent(event_, callback_);
+}
+
+void SyncEventWatcher::DecrementRegisterCount() {
+ DCHECK_GT(register_request_count_, 0u);
+ register_request_count_--;
+ if (register_request_count_ == 0 && registered_) {
+ registry_->UnregisterEvent(event_);
+ registered_ = false;
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
new file mode 100644
index 0000000000..fd3df396ec
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
@@ -0,0 +1,135 @@
+// 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 "mojo/public/cpp/bindings/sync_handle_registry.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_local.h"
+#include "mojo/public/c/system/core.h"
+
+namespace mojo {
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<SyncHandleRegistry>>::Leaky
+ g_current_sync_handle_watcher = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+scoped_refptr<SyncHandleRegistry> SyncHandleRegistry::current() {
+ scoped_refptr<SyncHandleRegistry> result(
+ g_current_sync_handle_watcher.Pointer()->Get());
+ if (!result) {
+ result = new SyncHandleRegistry();
+ DCHECK_EQ(result.get(), g_current_sync_handle_watcher.Pointer()->Get());
+ }
+ return result;
+}
+
+bool SyncHandleRegistry::RegisterHandle(const Handle& handle,
+ MojoHandleSignals handle_signals,
+ const HandleCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (base::ContainsKey(handles_, handle))
+ return false;
+
+ MojoResult result = wait_set_.AddHandle(handle, handle_signals);
+ if (result != MOJO_RESULT_OK)
+ return false;
+
+ handles_[handle] = callback;
+ return true;
+}
+
+void SyncHandleRegistry::UnregisterHandle(const Handle& handle) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!base::ContainsKey(handles_, handle))
+ return;
+
+ MojoResult result = wait_set_.RemoveHandle(handle);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ handles_.erase(handle);
+}
+
+bool SyncHandleRegistry::RegisterEvent(base::WaitableEvent* event,
+ const base::Closure& callback) {
+ auto result = events_.insert({event, callback});
+ DCHECK(result.second);
+ MojoResult rv = wait_set_.AddEvent(event);
+ if (rv == MOJO_RESULT_OK)
+ return true;
+ DCHECK_EQ(MOJO_RESULT_ALREADY_EXISTS, rv);
+ return false;
+}
+
+void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event) {
+ auto it = events_.find(event);
+ DCHECK(it != events_.end());
+ events_.erase(it);
+ MojoResult rv = wait_set_.RemoveEvent(event);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+}
+
+bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ size_t num_ready_handles;
+ Handle ready_handle;
+ MojoResult ready_handle_result;
+
+ scoped_refptr<SyncHandleRegistry> preserver(this);
+ while (true) {
+ for (size_t i = 0; i < count; ++i)
+ if (*should_stop[i])
+ return true;
+
+ // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we
+ // give priority to the handle that is waiting for sync response.
+ base::WaitableEvent* ready_event = nullptr;
+ num_ready_handles = 1;
+ wait_set_.Wait(&ready_event, &num_ready_handles, &ready_handle,
+ &ready_handle_result);
+ if (num_ready_handles) {
+ DCHECK_EQ(1u, num_ready_handles);
+ const auto iter = handles_.find(ready_handle);
+ iter->second.Run(ready_handle_result);
+ }
+
+ if (ready_event) {
+ const auto iter = events_.find(ready_event);
+ DCHECK(iter != events_.end());
+ iter->second.Run();
+ }
+ };
+
+ return false;
+}
+
+SyncHandleRegistry::SyncHandleRegistry() {
+ DCHECK(!g_current_sync_handle_watcher.Pointer()->Get());
+ g_current_sync_handle_watcher.Pointer()->Set(this);
+}
+
+SyncHandleRegistry::~SyncHandleRegistry() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // This object may be destructed after the thread local storage slot used by
+ // |g_current_sync_handle_watcher| is reset during thread shutdown.
+ // For example, another slot in the thread local storage holds a referrence to
+ // this object, and that slot is cleaned up after
+ // |g_current_sync_handle_watcher|.
+ if (!g_current_sync_handle_watcher.Pointer()->Get())
+ return;
+
+ // If this breaks, it is likely that the global variable is bulit into and
+ // accessed from multiple modules.
+ DCHECK_EQ(this, g_current_sync_handle_watcher.Pointer()->Get());
+
+ g_current_sync_handle_watcher.Pointer()->Set(nullptr);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
new file mode 100644
index 0000000000..f20af56b20
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
@@ -0,0 +1,76 @@
+// 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 "mojo/public/cpp/bindings/sync_handle_watcher.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+
+SyncHandleWatcher::SyncHandleWatcher(
+ const Handle& handle,
+ MojoHandleSignals handle_signals,
+ const SyncHandleRegistry::HandleCallback& callback)
+ : handle_(handle),
+ handle_signals_(handle_signals),
+ callback_(callback),
+ registered_(false),
+ register_request_count_(0),
+ registry_(SyncHandleRegistry::current()),
+ destroyed_(new base::RefCountedData<bool>(false)) {}
+
+SyncHandleWatcher::~SyncHandleWatcher() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (registered_)
+ registry_->UnregisterHandle(handle_);
+
+ destroyed_->data = true;
+}
+
+void SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ IncrementRegisterCount();
+}
+
+bool SyncHandleWatcher::SyncWatch(const bool* should_stop) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ IncrementRegisterCount();
+ if (!registered_) {
+ DecrementRegisterCount();
+ return false;
+ }
+
+ // This object may be destroyed during the Wait() call. So we have to preserve
+ // the boolean that Wait uses.
+ auto destroyed = destroyed_;
+ const bool* should_stop_array[] = {should_stop, &destroyed->data};
+ bool result = registry_->Wait(should_stop_array, 2);
+
+ // This object has been destroyed.
+ if (destroyed->data)
+ return false;
+
+ DecrementRegisterCount();
+ return result;
+}
+
+void SyncHandleWatcher::IncrementRegisterCount() {
+ register_request_count_++;
+ if (!registered_) {
+ registered_ =
+ registry_->RegisterHandle(handle_, handle_signals_, callback_);
+ }
+}
+
+void SyncHandleWatcher::DecrementRegisterCount() {
+ DCHECK_GT(register_request_count_, 0u);
+
+ register_request_count_--;
+ if (register_request_count_ == 0 && registered_) {
+ registry_->UnregisterHandle(handle_);
+ registered_ = false;
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/template_util.h b/mojo/public/cpp/bindings/lib/template_util.h
new file mode 100644
index 0000000000..5151123ac0
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/template_util.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_
+
+#include <type_traits>
+
+namespace mojo {
+namespace internal {
+
+template <class T, T v>
+struct IntegralConstant {
+ static const T value = v;
+};
+
+template <class T, T v>
+const T IntegralConstant<T, v>::value;
+
+typedef IntegralConstant<bool, true> TrueType;
+typedef IntegralConstant<bool, false> FalseType;
+
+template <class T>
+struct IsConst : FalseType {};
+template <class T>
+struct IsConst<const T> : TrueType {};
+
+template <class T>
+struct IsPointer : FalseType {};
+template <class T>
+struct IsPointer<T*> : TrueType {};
+
+template <bool B, typename T = void>
+struct EnableIf {};
+
+template <typename T>
+struct EnableIf<true, T> {
+ typedef T type;
+};
+
+// Types YesType and NoType are guaranteed such that sizeof(YesType) <
+// sizeof(NoType).
+typedef char YesType;
+
+struct NoType {
+ YesType dummy[2];
+};
+
+// A helper template to determine if given type is non-const move-only-type,
+// i.e. if a value of the given type should be passed via std::move() in a
+// destructive way.
+template <typename T>
+struct IsMoveOnlyType {
+ static const bool value = std::is_constructible<T, T&&>::value &&
+ !std::is_constructible<T, const T&>::value;
+};
+
+// This goop is a trick used to implement a template that can be used to
+// determine if a given class is the base class of another given class.
+template <typename, typename>
+struct IsSame {
+ static bool const value = false;
+};
+template <typename A>
+struct IsSame<A, A> {
+ static bool const value = true;
+};
+
+template <typename T>
+struct EnsureTypeIsComplete {
+ // sizeof() cannot be applied to incomplete types, this line will fail
+ // compilation if T is forward declaration.
+ using CheckSize = char (*)[sizeof(T)];
+};
+
+template <typename Base, typename Derived>
+struct IsBaseOf {
+ private:
+ static Derived* CreateDerived();
+ static char(&Check(Base*))[1];
+ static char(&Check(...))[2];
+
+ EnsureTypeIsComplete<Base> check_base_;
+ EnsureTypeIsComplete<Derived> check_derived_;
+
+ public:
+ static bool const value = sizeof Check(CreateDerived()) == 1 &&
+ !IsSame<Base const, void const>::value;
+};
+
+template <class T>
+struct RemovePointer {
+ typedef T type;
+};
+template <class T>
+struct RemovePointer<T*> {
+ typedef T type;
+};
+
+template <template <typename...> class Template, typename T>
+struct IsSpecializationOf : FalseType {};
+
+template <template <typename...> class Template, typename... Args>
+struct IsSpecializationOf<Template, Template<Args...>> : TrueType {};
+
+template <bool B, typename T, typename F>
+struct Conditional {
+ typedef T type;
+};
+
+template <typename T, typename F>
+struct Conditional<false, T, F> {
+ typedef F type;
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/union_accessor.h b/mojo/public/cpp/bindings/lib/union_accessor.h
new file mode 100644
index 0000000000..821aede595
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/union_accessor.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_
+
+namespace mojo {
+namespace internal {
+
+// When serializing and deserializing Unions, it is necessary to access
+// the private fields and methods of the Union. This allows us to do that
+// without leaking those same fields and methods in the Union interface.
+// All Union wrappers are friends of this class allowing such access.
+template <typename U>
+class UnionAccessor {
+ public:
+ explicit UnionAccessor(U* u) : u_(u) {}
+
+ typename U::Union_* data() { return &(u_->data_); }
+
+ typename U::Tag* tag() { return &(u_->tag_); }
+
+ void SwitchActive(typename U::Tag new_tag) { u_->SwitchActive(new_tag); }
+
+ private:
+ U* u_;
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_
diff --git a/mojo/public/cpp/bindings/lib/validate_params.h b/mojo/public/cpp/bindings/lib/validate_params.h
new file mode 100644
index 0000000000..c0ee8e02a7
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validate_params.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+
+namespace mojo {
+namespace internal {
+
+class ValidationContext;
+
+using ValidateEnumFunc = bool (*)(int32_t, ValidationContext*);
+
+class ContainerValidateParams {
+ public:
+ // Validates a map. A map is validated as a pair of arrays, one for the keys
+ // and one for the values. Both arguments must be non-null.
+ //
+ // ContainerValidateParams takes ownership of |in_key_validate params| and
+ // |in_element_validate params|.
+ ContainerValidateParams(ContainerValidateParams* in_key_validate_params,
+ ContainerValidateParams* in_element_validate_params)
+ : key_validate_params(in_key_validate_params),
+ element_validate_params(in_element_validate_params) {
+ DCHECK(in_key_validate_params)
+ << "Map validate params require key validate params";
+ DCHECK(in_element_validate_params)
+ << "Map validate params require element validate params";
+ }
+
+ // Validates an array.
+ //
+ // ContainerValidateParams takes ownership of |in_element_validate params|.
+ ContainerValidateParams(uint32_t in_expected_num_elements,
+ bool in_element_is_nullable,
+ ContainerValidateParams* in_element_validate_params)
+ : expected_num_elements(in_expected_num_elements),
+ element_is_nullable(in_element_is_nullable),
+ element_validate_params(in_element_validate_params) {}
+
+ // Validates an array of enums.
+ ContainerValidateParams(uint32_t in_expected_num_elements,
+ ValidateEnumFunc in_validate_enum_func)
+ : expected_num_elements(in_expected_num_elements),
+ validate_enum_func(in_validate_enum_func) {}
+
+ ~ContainerValidateParams() {
+ if (element_validate_params)
+ delete element_validate_params;
+ if (key_validate_params)
+ delete key_validate_params;
+ }
+
+ // If |expected_num_elements| is not 0, the array is expected to have exactly
+ // that number of elements.
+ uint32_t expected_num_elements = 0;
+
+ // Whether the elements are nullable.
+ bool element_is_nullable = false;
+
+ // Validation information for the map key array. May contain other
+ // ArrayValidateParams e.g. if the keys are strings.
+ ContainerValidateParams* key_validate_params = nullptr;
+
+ // For arrays: validation information for elements. It is either a pointer to
+ // another instance of ArrayValidateParams (if elements are arrays or maps),
+ // or nullptr.
+ //
+ // For maps: validation information for the whole value array. May contain
+ // other ArrayValidateParams e.g. if the values are arrays or maps.
+ ContainerValidateParams* element_validate_params = nullptr;
+
+ // Validation function for enum elements.
+ ValidateEnumFunc validate_enum_func = nullptr;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContainerValidateParams);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_
diff --git a/mojo/public/cpp/bindings/lib/validation_context.cc b/mojo/public/cpp/bindings/lib/validation_context.cc
new file mode 100644
index 0000000000..ad0a3646eb
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_context.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 "mojo/public/cpp/bindings/lib/validation_context.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace internal {
+
+ValidationContext::ValidationContext(const void* data,
+ size_t data_num_bytes,
+ size_t num_handles,
+ size_t num_associated_endpoint_handles,
+ Message* message,
+ const base::StringPiece& description,
+ int stack_depth)
+ : message_(message),
+ description_(description),
+ data_begin_(reinterpret_cast<uintptr_t>(data)),
+ data_end_(data_begin_ + data_num_bytes),
+ handle_begin_(0),
+ handle_end_(static_cast<uint32_t>(num_handles)),
+ associated_endpoint_handle_begin_(0),
+ associated_endpoint_handle_end_(
+ static_cast<uint32_t>(num_associated_endpoint_handles)),
+ stack_depth_(stack_depth) {
+ // Check whether the calculation of |data_end_| or static_cast from size_t to
+ // uint32_t causes overflow.
+ // They shouldn't happen but they do, set the corresponding range to empty.
+ if (data_end_ < data_begin_) {
+ NOTREACHED();
+ data_end_ = data_begin_;
+ }
+ if (handle_end_ < num_handles) {
+ NOTREACHED();
+ handle_end_ = 0;
+ }
+ if (associated_endpoint_handle_end_ < num_associated_endpoint_handles) {
+ NOTREACHED();
+ associated_endpoint_handle_end_ = 0;
+ }
+}
+
+ValidationContext::~ValidationContext() {
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/validation_context.h b/mojo/public/cpp/bindings/lib/validation_context.h
new file mode 100644
index 0000000000..ed6c6542e7
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_context.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_CONTEXT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_CONTEXT_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+
+static const int kMaxRecursionDepth = 100;
+
+namespace mojo {
+
+class Message;
+
+namespace internal {
+
+// ValidationContext is used when validating object sizes, pointers and handle
+// indices in the payload of incoming messages.
+class MOJO_CPP_BINDINGS_EXPORT ValidationContext {
+ public:
+ // [data, data + data_num_bytes) specifies the initial valid memory range.
+ // [0, num_handles) specifies the initial valid range of handle indices.
+ // [0, num_associated_endpoint_handles) specifies the initial valid range of
+ // associated endpoint handle indices.
+ //
+ // If provided, |message| and |description| provide additional information
+ // to use when reporting validation errors. In addition if |message| is
+ // provided, the MojoNotifyBadMessage API will be used to notify the system of
+ // such errors.
+ ValidationContext(const void* data,
+ size_t data_num_bytes,
+ size_t num_handles,
+ size_t num_associated_endpoint_handles,
+ Message* message = nullptr,
+ const base::StringPiece& description = "",
+ int stack_depth = 0);
+
+ ~ValidationContext();
+
+ // Claims the specified memory range.
+ // The method succeeds if the range is valid to claim. (Please see
+ // the comments for IsValidRange().)
+ // On success, the valid memory range is shrinked to begin right after the end
+ // of the claimed range.
+ bool ClaimMemory(const void* position, uint32_t num_bytes) {
+ uintptr_t begin = reinterpret_cast<uintptr_t>(position);
+ uintptr_t end = begin + num_bytes;
+
+ if (!InternalIsValidRange(begin, end))
+ return false;
+
+ data_begin_ = end;
+ return true;
+ }
+
+ // Claims the specified encoded handle (which is basically a handle index).
+ // The method succeeds if:
+ // - |encoded_handle|'s value is |kEncodedInvalidHandleValue|.
+ // - the handle is contained inside the valid range of handle indices. In this
+ // case, the valid range is shinked to begin right after the claimed handle.
+ bool ClaimHandle(const Handle_Data& encoded_handle) {
+ uint32_t index = encoded_handle.value;
+ if (index == kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < handle_begin_ || index >= handle_end_)
+ return false;
+
+ // |index| + 1 shouldn't overflow, because |index| is not the max value of
+ // uint32_t (it is less than |handle_end_|).
+ handle_begin_ = index + 1;
+ return true;
+ }
+
+ // Claims the specified encoded associated endpoint handle.
+ // The method succeeds if:
+ // - |encoded_handle|'s value is |kEncodedInvalidHandleValue|.
+ // - the handle is contained inside the valid range of associated endpoint
+ // handle indices. In this case, the valid range is shinked to begin right
+ // after the claimed handle.
+ bool ClaimAssociatedEndpointHandle(
+ const AssociatedEndpointHandle_Data& encoded_handle) {
+ uint32_t index = encoded_handle.value;
+ if (index == kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < associated_endpoint_handle_begin_ ||
+ index >= associated_endpoint_handle_end_)
+ return false;
+
+ // |index| + 1 shouldn't overflow, because |index| is not the max value of
+ // uint32_t (it is less than |associated_endpoint_handle_end_|).
+ associated_endpoint_handle_begin_ = index + 1;
+ return true;
+ }
+
+ // Returns true if the specified range is not empty, and the range is
+ // contained inside the valid memory range.
+ bool IsValidRange(const void* position, uint32_t num_bytes) const {
+ uintptr_t begin = reinterpret_cast<uintptr_t>(position);
+ uintptr_t end = begin + num_bytes;
+
+ return InternalIsValidRange(begin, end);
+ }
+
+ // This object should be created on the stack once every time we recurse down
+ // into a subfield during validation to make sure we don't recurse too deep
+ // and blow the stack.
+ class ScopedDepthTracker {
+ public:
+ // |ctx| must outlive this object.
+ explicit ScopedDepthTracker(ValidationContext* ctx) : ctx_(ctx) {
+ ++ctx_->stack_depth_;
+ }
+
+ ~ScopedDepthTracker() { --ctx_->stack_depth_; }
+
+ private:
+ ValidationContext* ctx_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedDepthTracker);
+ };
+
+ // Returns true if the recursion depth limit has been reached.
+ bool ExceedsMaxDepth() WARN_UNUSED_RESULT {
+ return stack_depth_ > kMaxRecursionDepth;
+ }
+
+ Message* message() const { return message_; }
+ const base::StringPiece& description() const { return description_; }
+
+ private:
+ bool InternalIsValidRange(uintptr_t begin, uintptr_t end) const {
+ return end > begin && begin >= data_begin_ && end <= data_end_;
+ }
+
+ Message* const message_;
+ const base::StringPiece description_;
+
+ // [data_begin_, data_end_) is the valid memory range.
+ uintptr_t data_begin_;
+ uintptr_t data_end_;
+
+ // [handle_begin_, handle_end_) is the valid handle index range.
+ uint32_t handle_begin_;
+ uint32_t handle_end_;
+
+ // [associated_endpoint_handle_begin_, associated_endpoint_handle_end_) is the
+ // valid associated endpoint handle index range.
+ uint32_t associated_endpoint_handle_begin_;
+ uint32_t associated_endpoint_handle_end_;
+
+ int stack_depth_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValidationContext);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_CONTEXT_H_
diff --git a/mojo/public/cpp/bindings/lib/validation_errors.cc b/mojo/public/cpp/bindings/lib/validation_errors.cc
new file mode 100644
index 0000000000..904f5e4c72
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_errors.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 "mojo/public/cpp/bindings/lib/validation_errors.h"
+
+#include "base/strings/stringprintf.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace internal {
+namespace {
+
+ValidationErrorObserverForTesting* g_validation_error_observer = nullptr;
+SerializationWarningObserverForTesting* g_serialization_warning_observer =
+ nullptr;
+bool g_suppress_logging = false;
+
+} // namespace
+
+const char* ValidationErrorToString(ValidationError error) {
+ switch (error) {
+ case VALIDATION_ERROR_NONE:
+ return "VALIDATION_ERROR_NONE";
+ case VALIDATION_ERROR_MISALIGNED_OBJECT:
+ return "VALIDATION_ERROR_MISALIGNED_OBJECT";
+ case VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE:
+ return "VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE";
+ case VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER:
+ return "VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER";
+ case VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER:
+ return "VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER";
+ case VALIDATION_ERROR_ILLEGAL_HANDLE:
+ return "VALIDATION_ERROR_ILLEGAL_HANDLE";
+ case VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE:
+ return "VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE";
+ case VALIDATION_ERROR_ILLEGAL_POINTER:
+ return "VALIDATION_ERROR_ILLEGAL_POINTER";
+ case VALIDATION_ERROR_UNEXPECTED_NULL_POINTER:
+ return "VALIDATION_ERROR_UNEXPECTED_NULL_POINTER";
+ case VALIDATION_ERROR_ILLEGAL_INTERFACE_ID:
+ return "VALIDATION_ERROR_ILLEGAL_INTERFACE_ID";
+ case VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID:
+ return "VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID";
+ case VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS:
+ return "VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS";
+ case VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID:
+ return "VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID";
+ case VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD:
+ return "VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD";
+ case VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP:
+ return "VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP";
+ case VALIDATION_ERROR_UNKNOWN_UNION_TAG:
+ return "VALIDATION_ERROR_UNKNOWN_UNION_TAG";
+ case VALIDATION_ERROR_UNKNOWN_ENUM_VALUE:
+ return "VALIDATION_ERROR_UNKNOWN_ENUM_VALUE";
+ case VALIDATION_ERROR_DESERIALIZATION_FAILED:
+ return "VALIDATION_ERROR_DESERIALIZATION_FAILED";
+ case VALIDATION_ERROR_MAX_RECURSION_DEPTH:
+ return "VALIDATION_ERROR_MAX_RECURSION_DEPTH";
+ }
+
+ return "Unknown error";
+}
+
+void ReportValidationError(ValidationContext* context,
+ ValidationError error,
+ const char* description) {
+ if (g_validation_error_observer) {
+ g_validation_error_observer->set_last_error(error);
+ return;
+ }
+
+ if (description) {
+ if (!g_suppress_logging) {
+ LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error)
+ << " (" << description << ")";
+ }
+ if (context->message()) {
+ context->message()->NotifyBadMessage(
+ base::StringPrintf("Validation failed for %s [%s (%s)]",
+ context->description().data(),
+ ValidationErrorToString(error), description));
+ }
+ } else {
+ if (!g_suppress_logging)
+ LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error);
+ if (context->message()) {
+ context->message()->NotifyBadMessage(
+ base::StringPrintf("Validation failed for %s [%s]",
+ context->description().data(),
+ ValidationErrorToString(error)));
+ }
+ }
+}
+
+void ReportValidationErrorForMessage(
+ mojo::Message* message,
+ ValidationError error,
+ const char* description) {
+ ValidationContext validation_context(nullptr, 0, 0, 0, message, description);
+ ReportValidationError(&validation_context, error);
+}
+
+ScopedSuppressValidationErrorLoggingForTests
+ ::ScopedSuppressValidationErrorLoggingForTests()
+ : was_suppressed_(g_suppress_logging) {
+ g_suppress_logging = true;
+}
+
+ScopedSuppressValidationErrorLoggingForTests
+ ::~ScopedSuppressValidationErrorLoggingForTests() {
+ g_suppress_logging = was_suppressed_;
+}
+
+ValidationErrorObserverForTesting::ValidationErrorObserverForTesting(
+ const base::Closure& callback)
+ : last_error_(VALIDATION_ERROR_NONE), callback_(callback) {
+ DCHECK(!g_validation_error_observer);
+ g_validation_error_observer = this;
+}
+
+ValidationErrorObserverForTesting::~ValidationErrorObserverForTesting() {
+ DCHECK(g_validation_error_observer == this);
+ g_validation_error_observer = nullptr;
+}
+
+bool ReportSerializationWarning(ValidationError error) {
+ if (g_serialization_warning_observer) {
+ g_serialization_warning_observer->set_last_warning(error);
+ return true;
+ }
+
+ return false;
+}
+
+SerializationWarningObserverForTesting::SerializationWarningObserverForTesting()
+ : last_warning_(VALIDATION_ERROR_NONE) {
+ DCHECK(!g_serialization_warning_observer);
+ g_serialization_warning_observer = this;
+}
+
+SerializationWarningObserverForTesting::
+ ~SerializationWarningObserverForTesting() {
+ DCHECK(g_serialization_warning_observer == this);
+ g_serialization_warning_observer = nullptr;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/validation_errors.h b/mojo/public/cpp/bindings/lib/validation_errors.h
new file mode 100644
index 0000000000..122418d9e3
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_errors.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+
+namespace mojo {
+
+class Message;
+
+namespace internal {
+
+enum ValidationError {
+ // There is no validation error.
+ VALIDATION_ERROR_NONE,
+ // An object (struct or array) is not 8-byte aligned.
+ VALIDATION_ERROR_MISALIGNED_OBJECT,
+ // An object is not contained inside the message data, or it overlaps other
+ // objects.
+ VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE,
+ // A struct header doesn't make sense, for example:
+ // - |num_bytes| is smaller than the size of the struct header.
+ // - |num_bytes| and |version| don't match.
+ // TODO(yzshen): Consider splitting it into two different error codes. Because
+ // the former indicates someone is misbehaving badly whereas the latter could
+ // be due to an inappropriately-modified .mojom file.
+ VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER,
+ // An array header doesn't make sense, for example:
+ // - |num_bytes| is smaller than the size of the header plus the size required
+ // to store |num_elements| elements.
+ // - For fixed-size arrays, |num_elements| is different than the specified
+ // size.
+ VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ // An encoded handle is illegal.
+ VALIDATION_ERROR_ILLEGAL_HANDLE,
+ // A non-nullable handle field is set to invalid handle.
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ // An encoded pointer is illegal.
+ VALIDATION_ERROR_ILLEGAL_POINTER,
+ // A non-nullable pointer field is set to null.
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ // An interface ID is illegal.
+ VALIDATION_ERROR_ILLEGAL_INTERFACE_ID,
+ // A non-nullable interface ID field is set to invalid.
+ VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+ // |flags| in the message header is invalid. The flags are either
+ // inconsistent with one another, inconsistent with other parts of the
+ // message, or unexpected for the message receiver. For example the
+ // receiver is expecting a request message but the flags indicate that
+ // the message is a response message.
+ VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS,
+ // |flags| in the message header indicates that a request ID is required but
+ // there isn't one.
+ VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID,
+ // The |name| field in a message header contains an unexpected value.
+ VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD,
+ // Two parallel arrays which are supposed to represent a map have different
+ // lengths.
+ VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP,
+ // Attempted to deserialize a tagged union with an unknown tag.
+ VALIDATION_ERROR_UNKNOWN_UNION_TAG,
+ // A value of a non-extensible enum type is unknown.
+ VALIDATION_ERROR_UNKNOWN_ENUM_VALUE,
+ // Message deserialization failure, for example due to rejection by custom
+ // validation logic.
+ VALIDATION_ERROR_DESERIALIZATION_FAILED,
+ // The message contains a too deeply nested value, for example a recursively
+ // defined field which runtime value is too large.
+ VALIDATION_ERROR_MAX_RECURSION_DEPTH,
+};
+
+MOJO_CPP_BINDINGS_EXPORT const char* ValidationErrorToString(
+ ValidationError error);
+
+MOJO_CPP_BINDINGS_EXPORT void ReportValidationError(
+ ValidationContext* context,
+ ValidationError error,
+ const char* description = nullptr);
+
+MOJO_CPP_BINDINGS_EXPORT void ReportValidationErrorForMessage(
+ mojo::Message* message,
+ ValidationError error,
+ const char* description = nullptr);
+
+// This class may be used by tests to suppress validation error logging. This is
+// not thread-safe and must only be instantiated on the main thread with no
+// other threads using Mojo bindings at the time of construction or destruction.
+class MOJO_CPP_BINDINGS_EXPORT ScopedSuppressValidationErrorLoggingForTests {
+ public:
+ ScopedSuppressValidationErrorLoggingForTests();
+ ~ScopedSuppressValidationErrorLoggingForTests();
+
+ private:
+ const bool was_suppressed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSuppressValidationErrorLoggingForTests);
+};
+
+// Only used by validation tests and when there is only one thread doing message
+// validation.
+class MOJO_CPP_BINDINGS_EXPORT ValidationErrorObserverForTesting {
+ public:
+ explicit ValidationErrorObserverForTesting(const base::Closure& callback);
+ ~ValidationErrorObserverForTesting();
+
+ ValidationError last_error() const { return last_error_; }
+ void set_last_error(ValidationError error) {
+ last_error_ = error;
+ callback_.Run();
+ }
+
+ private:
+ ValidationError last_error_;
+ base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValidationErrorObserverForTesting);
+};
+
+// Used only by MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING. Don't use it directly.
+//
+// The function returns true if the error is recorded (by a
+// SerializationWarningObserverForTesting object), false otherwise.
+MOJO_CPP_BINDINGS_EXPORT bool ReportSerializationWarning(ValidationError error);
+
+// Only used by serialization tests and when there is only one thread doing
+// message serialization.
+class MOJO_CPP_BINDINGS_EXPORT SerializationWarningObserverForTesting {
+ public:
+ SerializationWarningObserverForTesting();
+ ~SerializationWarningObserverForTesting();
+
+ ValidationError last_warning() const { return last_warning_; }
+ void set_last_warning(ValidationError error) { last_warning_ = error; }
+
+ private:
+ ValidationError last_warning_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerializationWarningObserverForTesting);
+};
+
+} // namespace internal
+} // namespace mojo
+
+// In debug build, logs a serialization warning if |condition| evaluates to
+// true:
+// - if there is a SerializationWarningObserverForTesting object alive,
+// records |error| in it;
+// - otherwise, logs a fatal-level message.
+// |error| is the validation error that will be triggered by the receiver
+// of the serialzation result.
+//
+// In non-debug build, does nothing (not even compiling |condition|).
+#define MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(condition, error, \
+ description) \
+ DLOG_IF(FATAL, (condition) && !ReportSerializationWarning(error)) \
+ << "The outgoing message will trigger " \
+ << ValidationErrorToString(error) << " at the receiving side (" \
+ << description << ").";
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
diff --git a/mojo/public/cpp/bindings/lib/validation_util.cc b/mojo/public/cpp/bindings/lib/validation_util.cc
new file mode 100644
index 0000000000..7614df5cbc
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_util.cc
@@ -0,0 +1,210 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "mojo/public/cpp/bindings/lib/message_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h"
+
+namespace mojo {
+namespace internal {
+
+bool ValidateStructHeaderAndClaimMemory(const void* data,
+ ValidationContext* validation_context) {
+ if (!IsAligned(data)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MISALIGNED_OBJECT);
+ return false;
+ }
+ if (!validation_context->IsValidRange(data, sizeof(StructHeader))) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE);
+ return false;
+ }
+
+ const StructHeader* header = static_cast<const StructHeader*>(data);
+
+ if (header->num_bytes < sizeof(StructHeader)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER);
+ return false;
+ }
+
+ if (!validation_context->ClaimMemory(data, header->num_bytes)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE);
+ return false;
+ }
+
+ return true;
+}
+
+bool ValidateNonInlinedUnionHeaderAndClaimMemory(
+ const void* data,
+ ValidationContext* validation_context) {
+ if (!IsAligned(data)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MISALIGNED_OBJECT);
+ return false;
+ }
+
+ if (!validation_context->ClaimMemory(data, kUnionDataSize) ||
+ *static_cast<const uint32_t*>(data) != kUnionDataSize) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE);
+ return false;
+ }
+
+ return true;
+}
+
+bool ValidateMessageIsRequestWithoutResponse(
+ const Message* message,
+ ValidationContext* validation_context) {
+ if (message->has_flag(Message::kFlagIsResponse) ||
+ message->has_flag(Message::kFlagExpectsResponse)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS);
+ return false;
+ }
+ return true;
+}
+
+bool ValidateMessageIsRequestExpectingResponse(
+ const Message* message,
+ ValidationContext* validation_context) {
+ if (message->has_flag(Message::kFlagIsResponse) ||
+ !message->has_flag(Message::kFlagExpectsResponse)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS);
+ return false;
+ }
+ return true;
+}
+
+bool ValidateMessageIsResponse(const Message* message,
+ ValidationContext* validation_context) {
+ if (message->has_flag(Message::kFlagExpectsResponse) ||
+ !message->has_flag(Message::kFlagIsResponse)) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS);
+ return false;
+ }
+ return true;
+}
+
+bool IsHandleOrInterfaceValid(const AssociatedInterface_Data& input) {
+ return input.handle.is_valid();
+}
+
+bool IsHandleOrInterfaceValid(const AssociatedEndpointHandle_Data& input) {
+ return input.is_valid();
+}
+
+bool IsHandleOrInterfaceValid(const Interface_Data& input) {
+ return input.handle.is_valid();
+}
+
+bool IsHandleOrInterfaceValid(const Handle_Data& input) {
+ return input.is_valid();
+}
+
+bool ValidateHandleOrInterfaceNonNullable(
+ const AssociatedInterface_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context) {
+ if (IsHandleOrInterfaceValid(input))
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+ error_message);
+ return false;
+}
+
+bool ValidateHandleOrInterfaceNonNullable(
+ const AssociatedEndpointHandle_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context) {
+ if (IsHandleOrInterfaceValid(input))
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+ error_message);
+ return false;
+}
+
+bool ValidateHandleOrInterfaceNonNullable(
+ const Interface_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context) {
+ if (IsHandleOrInterfaceValid(input))
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ error_message);
+ return false;
+}
+
+bool ValidateHandleOrInterfaceNonNullable(
+ const Handle_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context) {
+ if (IsHandleOrInterfaceValid(input))
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ error_message);
+ return false;
+}
+
+bool ValidateHandleOrInterface(const AssociatedInterface_Data& input,
+ ValidationContext* validation_context) {
+ if (validation_context->ClaimAssociatedEndpointHandle(input.handle))
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_INTERFACE_ID);
+ return false;
+}
+
+bool ValidateHandleOrInterface(const AssociatedEndpointHandle_Data& input,
+ ValidationContext* validation_context) {
+ if (validation_context->ClaimAssociatedEndpointHandle(input))
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_ILLEGAL_INTERFACE_ID);
+ return false;
+}
+
+bool ValidateHandleOrInterface(const Interface_Data& input,
+ ValidationContext* validation_context) {
+ if (validation_context->ClaimHandle(input.handle))
+ return true;
+
+ ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_HANDLE);
+ return false;
+}
+
+bool ValidateHandleOrInterface(const Handle_Data& input,
+ ValidationContext* validation_context) {
+ if (validation_context->ClaimHandle(input))
+ return true;
+
+ ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_HANDLE);
+ return false;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/validation_util.h b/mojo/public/cpp/bindings/lib/validation_util.h
new file mode 100644
index 0000000000..ea5a991668
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_util.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_
+
+#include <stdint.h>
+
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/lib/validate_params.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace internal {
+
+// Checks whether decoding the pointer will overflow and produce a pointer
+// smaller than |offset|.
+inline bool ValidateEncodedPointer(const uint64_t* offset) {
+ // - Make sure |*offset| is no more than 32-bits.
+ // - Cast |offset| to uintptr_t so overflow behavior is well defined across
+ // 32-bit and 64-bit systems.
+ return *offset <= std::numeric_limits<uint32_t>::max() &&
+ (reinterpret_cast<uintptr_t>(offset) +
+ static_cast<uint32_t>(*offset) >=
+ reinterpret_cast<uintptr_t>(offset));
+}
+
+template <typename T>
+bool ValidatePointer(const Pointer<T>& input,
+ ValidationContext* validation_context) {
+ bool result = ValidateEncodedPointer(&input.offset);
+ if (!result)
+ ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_POINTER);
+
+ return result;
+}
+
+// Validates that |data| contains a valid struct header, in terms of alignment
+// and size (i.e., the |num_bytes| field of the header is sufficient for storing
+// the header itself). Besides, it checks that the memory range
+// [data, data + num_bytes) is not marked as occupied by other objects in
+// |validation_context|. On success, the memory range is marked as occupied.
+// Note: Does not verify |version| or that |num_bytes| is correct for the
+// claimed version.
+MOJO_CPP_BINDINGS_EXPORT bool ValidateStructHeaderAndClaimMemory(
+ const void* data,
+ ValidationContext* validation_context);
+
+// Validates that |data| contains a valid union header, in terms of alignment
+// and size. It checks that the memory range [data, data + kUnionDataSize) is
+// not marked as occupied by other objects in |validation_context|. On success,
+// the memory range is marked as occupied.
+MOJO_CPP_BINDINGS_EXPORT bool ValidateNonInlinedUnionHeaderAndClaimMemory(
+ const void* data,
+ ValidationContext* validation_context);
+
+// Validates that the message is a request which doesn't expect a response.
+MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsRequestWithoutResponse(
+ const Message* message,
+ ValidationContext* validation_context);
+
+// Validates that the message is a request expecting a response.
+MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsRequestExpectingResponse(
+ const Message* message,
+ ValidationContext* validation_context);
+
+// Validates that the message is a response.
+MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsResponse(
+ const Message* message,
+ ValidationContext* validation_context);
+
+// Validates that the message payload is a valid struct of type ParamsType.
+template <typename ParamsType>
+bool ValidateMessagePayload(const Message* message,
+ ValidationContext* validation_context) {
+ return ParamsType::Validate(message->payload(), validation_context);
+}
+
+// The following Validate.*NonNullable() functions validate that the given
+// |input| is not null/invalid.
+template <typename T>
+bool ValidatePointerNonNullable(const T& input,
+ const char* error_message,
+ ValidationContext* validation_context) {
+ if (input.offset)
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ error_message);
+ return false;
+}
+
+template <typename T>
+bool ValidateInlinedUnionNonNullable(const T& input,
+ const char* error_message,
+ ValidationContext* validation_context) {
+ if (!input.is_null())
+ return true;
+
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ error_message);
+ return false;
+}
+
+MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
+ const AssociatedInterface_Data& input);
+MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
+ const AssociatedEndpointHandle_Data& input);
+MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
+ const Interface_Data& input);
+MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
+ const Handle_Data& input);
+
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+ const AssociatedInterface_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context);
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+ const AssociatedEndpointHandle_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context);
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+ const Interface_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context);
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+ const Handle_Data& input,
+ const char* error_message,
+ ValidationContext* validation_context);
+
+template <typename T>
+bool ValidateContainer(const Pointer<T>& input,
+ ValidationContext* validation_context,
+ const ContainerValidateParams* validate_params) {
+ ValidationContext::ScopedDepthTracker depth_tracker(validation_context);
+ if (validation_context->ExceedsMaxDepth()) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MAX_RECURSION_DEPTH);
+ return false;
+ }
+ return ValidatePointer(input, validation_context) &&
+ T::Validate(input.Get(), validation_context, validate_params);
+}
+
+template <typename T>
+bool ValidateStruct(const Pointer<T>& input,
+ ValidationContext* validation_context) {
+ ValidationContext::ScopedDepthTracker depth_tracker(validation_context);
+ if (validation_context->ExceedsMaxDepth()) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MAX_RECURSION_DEPTH);
+ return false;
+ }
+ return ValidatePointer(input, validation_context) &&
+ T::Validate(input.Get(), validation_context);
+}
+
+template <typename T>
+bool ValidateInlinedUnion(const T& input,
+ ValidationContext* validation_context) {
+ ValidationContext::ScopedDepthTracker depth_tracker(validation_context);
+ if (validation_context->ExceedsMaxDepth()) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MAX_RECURSION_DEPTH);
+ return false;
+ }
+ return T::Validate(&input, validation_context, true);
+}
+
+template <typename T>
+bool ValidateNonInlinedUnion(const Pointer<T>& input,
+ ValidationContext* validation_context) {
+ ValidationContext::ScopedDepthTracker depth_tracker(validation_context);
+ if (validation_context->ExceedsMaxDepth()) {
+ ReportValidationError(validation_context,
+ VALIDATION_ERROR_MAX_RECURSION_DEPTH);
+ return false;
+ }
+ return ValidatePointer(input, validation_context) &&
+ T::Validate(input.Get(), validation_context, false);
+}
+
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
+ const AssociatedInterface_Data& input,
+ ValidationContext* validation_context);
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
+ const AssociatedEndpointHandle_Data& input,
+ ValidationContext* validation_context);
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
+ const Interface_Data& input,
+ ValidationContext* validation_context);
+MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
+ const Handle_Data& input,
+ ValidationContext* validation_context);
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h b/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h
new file mode 100644
index 0000000000..cb24bc46ee
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h
@@ -0,0 +1,78 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_
+
+#include <type_traits>
+
+#include "mojo/public/cpp/bindings/clone_traits.h"
+#include "mojo/public/cpp/bindings/lib/equals_traits.h"
+#include "third_party/WebKit/Source/wtf/HashMap.h"
+#include "third_party/WebKit/Source/wtf/Optional.h"
+#include "third_party/WebKit/Source/wtf/Vector.h"
+#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+
+namespace mojo {
+
+template <typename T>
+struct CloneTraits<WTF::Vector<T>, false> {
+ static WTF::Vector<T> Clone(const WTF::Vector<T>& input) {
+ WTF::Vector<T> result;
+ result.reserveCapacity(input.size());
+ for (const auto& element : input)
+ result.push_back(mojo::Clone(element));
+
+ return result;
+ }
+};
+
+template <typename K, typename V>
+struct CloneTraits<WTF::HashMap<K, V>, false> {
+ static WTF::HashMap<K, V> Clone(const WTF::HashMap<K, V>& input) {
+ WTF::HashMap<K, V> result;
+ auto input_end = input.end();
+ for (auto it = input.begin(); it != input_end; ++it)
+ result.add(mojo::Clone(it->key), mojo::Clone(it->value));
+ return result;
+ }
+};
+
+namespace internal {
+
+template <typename T>
+struct EqualsTraits<WTF::Vector<T>, false> {
+ static bool Equals(const WTF::Vector<T>& a, const WTF::Vector<T>& b) {
+ if (a.size() != b.size())
+ return false;
+ for (size_t i = 0; i < a.size(); ++i) {
+ if (!internal::Equals(a[i], b[i]))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename K, typename V>
+struct EqualsTraits<WTF::HashMap<K, V>, false> {
+ static bool Equals(const WTF::HashMap<K, V>& a, const WTF::HashMap<K, V>& b) {
+ if (a.size() != b.size())
+ return false;
+
+ auto a_end = a.end();
+ auto b_end = b.end();
+
+ for (auto iter = a.begin(); iter != a_end; ++iter) {
+ auto b_iter = b.find(iter->key);
+ if (b_iter == b_end || !internal::Equals(iter->value, b_iter->value))
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/wtf_hash_util.h b/mojo/public/cpp/bindings/lib/wtf_hash_util.h
new file mode 100644
index 0000000000..cc590da67a
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/wtf_hash_util.h
@@ -0,0 +1,132 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_HASH_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_HASH_UTIL_H_
+
+#include <type_traits>
+
+#include "mojo/public/cpp/bindings/lib/hash_util.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "third_party/WebKit/Source/wtf/HashFunctions.h"
+#include "third_party/WebKit/Source/wtf/text/StringHash.h"
+#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename T>
+size_t WTFHashCombine(size_t seed, const T& value) {
+ // Based on proposal in:
+ // http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf
+ //
+ // TODO(tibell): We'd like to use WTF::DefaultHash instead of std::hash, but
+ // there is no general template specialization of DefaultHash for enums
+ // and there can't be an instance for bool.
+ return seed ^ (std::hash<T>()(value) + (seed << 6) + (seed >> 2));
+}
+
+template <typename T, bool has_hash_method = HasHashMethod<T>::value>
+struct WTFHashTraits;
+
+template <typename T>
+size_t WTFHash(size_t seed, const T& value);
+
+template <typename T>
+struct WTFHashTraits<T, true> {
+ static size_t Hash(size_t seed, const T& value) { return value.Hash(seed); }
+};
+
+template <typename T>
+struct WTFHashTraits<T, false> {
+ static size_t Hash(size_t seed, const T& value) {
+ return WTFHashCombine(seed, value);
+ }
+};
+
+template <>
+struct WTFHashTraits<WTF::String, false> {
+ static size_t Hash(size_t seed, const WTF::String& value) {
+ return HashCombine(seed, WTF::StringHash::hash(value));
+ }
+};
+
+template <typename T>
+size_t WTFHash(size_t seed, const T& value) {
+ return WTFHashTraits<T>::Hash(seed, value);
+}
+
+template <typename T>
+struct StructPtrHashFn {
+ static unsigned hash(const StructPtr<T>& value) {
+ return value.Hash(kHashSeed);
+ }
+ static bool equal(const StructPtr<T>& left, const StructPtr<T>& right) {
+ return left.Equals(right);
+ }
+ static const bool safeToCompareToEmptyOrDeleted = false;
+};
+
+template <typename T>
+struct InlinedStructPtrHashFn {
+ static unsigned hash(const InlinedStructPtr<T>& value) {
+ return value.Hash(kHashSeed);
+ }
+ static bool equal(const InlinedStructPtr<T>& left,
+ const InlinedStructPtr<T>& right) {
+ return left.Equals(right);
+ }
+ static const bool safeToCompareToEmptyOrDeleted = false;
+};
+
+} // namespace internal
+} // namespace mojo
+
+namespace WTF {
+
+template <typename T>
+struct DefaultHash<mojo::StructPtr<T>> {
+ using Hash = mojo::internal::StructPtrHashFn<T>;
+};
+
+template <typename T>
+struct HashTraits<mojo::StructPtr<T>>
+ : public GenericHashTraits<mojo::StructPtr<T>> {
+ static const bool hasIsEmptyValueFunction = true;
+ static bool isEmptyValue(const mojo::StructPtr<T>& value) {
+ return value.is_null();
+ }
+ static void constructDeletedValue(mojo::StructPtr<T>& slot, bool) {
+ mojo::internal::StructPtrWTFHelper<T>::ConstructDeletedValue(slot);
+ }
+ static bool isDeletedValue(const mojo::StructPtr<T>& value) {
+ return mojo::internal::StructPtrWTFHelper<T>::IsHashTableDeletedValue(
+ value);
+ }
+};
+
+template <typename T>
+struct DefaultHash<mojo::InlinedStructPtr<T>> {
+ using Hash = mojo::internal::InlinedStructPtrHashFn<T>;
+};
+
+template <typename T>
+struct HashTraits<mojo::InlinedStructPtr<T>>
+ : public GenericHashTraits<mojo::InlinedStructPtr<T>> {
+ static const bool hasIsEmptyValueFunction = true;
+ static bool isEmptyValue(const mojo::InlinedStructPtr<T>& value) {
+ return value.is_null();
+ }
+ static void constructDeletedValue(mojo::InlinedStructPtr<T>& slot, bool) {
+ mojo::internal::InlinedStructPtrWTFHelper<T>::ConstructDeletedValue(slot);
+ }
+ static bool isDeletedValue(const mojo::InlinedStructPtr<T>& value) {
+ return mojo::internal::InlinedStructPtrWTFHelper<
+ T>::IsHashTableDeletedValue(value);
+ }
+};
+
+} // namespace WTF
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_HASH_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/wtf_serialization.h b/mojo/public/cpp/bindings/lib/wtf_serialization.h
new file mode 100644
index 0000000000..0f112b9143
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/wtf_serialization.h
@@ -0,0 +1,12 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_SERIALIZATION_H_
+
+#include "mojo/public/cpp/bindings/array_traits_wtf_vector.h"
+#include "mojo/public/cpp/bindings/map_traits_wtf_hash_map.h"
+#include "mojo/public/cpp/bindings/string_traits_wtf.h"
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/map.h b/mojo/public/cpp/bindings/map.h
new file mode 100644
index 0000000000..c1ba0756a3
--- /dev/null
+++ b/mojo/public/cpp/bindings/map.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 MOJO_PUBLIC_CPP_BINDINGS_MAP_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MAP_H_
+
+#include <map>
+#include <unordered_map>
+#include <utility>
+
+namespace mojo {
+
+// TODO(yzshen): These conversion functions should be removed and callsites
+// should be revisited and changed to use the same map type.
+template <typename Key, typename Value>
+std::unordered_map<Key, Value> MapToUnorderedMap(
+ const std::map<Key, Value>& input) {
+ return std::unordered_map<Key, Value>(input.begin(), input.end());
+}
+
+template <typename Key, typename Value>
+std::unordered_map<Key, Value> MapToUnorderedMap(std::map<Key, Value>&& input) {
+ return std::unordered_map<Key, Value>(std::make_move_iterator(input.begin()),
+ std::make_move_iterator(input.end()));
+}
+
+template <typename Key, typename Value>
+std::map<Key, Value> UnorderedMapToMap(
+ const std::unordered_map<Key, Value>& input) {
+ return std::map<Key, Value>(input.begin(), input.end());
+}
+
+template <typename Key, typename Value>
+std::map<Key, Value> UnorderedMapToMap(std::unordered_map<Key, Value>&& input) {
+ return std::map<Key, Value>(std::make_move_iterator(input.begin()),
+ std::make_move_iterator(input.end()));
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_H_
diff --git a/mojo/public/cpp/bindings/map_data_view.h b/mojo/public/cpp/bindings/map_data_view.h
new file mode 100644
index 0000000000..a65bb9eca1
--- /dev/null
+++ b/mojo/public/cpp/bindings/map_data_view.h
@@ -0,0 +1,63 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/array_data_view.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/map_data_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+
+namespace mojo {
+
+template <typename K, typename V>
+class MapDataView {
+ public:
+ using Data_ = typename internal::MojomTypeTraits<MapDataView<K, V>>::Data;
+
+ MapDataView() {}
+
+ MapDataView(Data_* data, internal::SerializationContext* context)
+ : keys_(data ? data->keys.Get() : nullptr, context),
+ values_(data ? data->values.Get() : nullptr, context) {}
+
+ bool is_null() const {
+ DCHECK_EQ(keys_.is_null(), values_.is_null());
+ return keys_.is_null();
+ }
+
+ size_t size() const {
+ DCHECK_EQ(keys_.size(), values_.size());
+ return keys_.size();
+ }
+
+ ArrayDataView<K>& keys() { return keys_; }
+ const ArrayDataView<K>& keys() const { return keys_; }
+
+ template <typename U>
+ bool ReadKeys(U* output) {
+ return internal::Deserialize<ArrayDataView<K>>(keys_.data_, output,
+ keys_.context_);
+ }
+
+ ArrayDataView<V>& values() { return values_; }
+ const ArrayDataView<V>& values() const { return values_; }
+
+ template <typename U>
+ bool ReadValues(U* output) {
+ return internal::Deserialize<ArrayDataView<V>>(values_.data_, output,
+ values_.context_);
+ }
+
+ private:
+ ArrayDataView<K> keys_;
+ ArrayDataView<V> values_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_
diff --git a/mojo/public/cpp/bindings/map_traits.h b/mojo/public/cpp/bindings/map_traits.h
new file mode 100644
index 0000000000..5c0d8b2846
--- /dev/null
+++ b/mojo/public/cpp/bindings/map_traits.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 MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_
+
+namespace mojo {
+
+// This must be specialized for any type |T| to be serialized/deserialized as
+// a mojom map.
+//
+// Usually you would like to do a partial specialization for a map template.
+// Imagine you want to specialize it for CustomMap<>, you need to implement:
+//
+// template <typename K, typename V>
+// struct MapTraits<CustomMap<K, V>> {
+// using Key = K;
+// using Value = V;
+//
+// // These two methods are optional. Please see comments in struct_traits.h
+// static bool IsNull(const CustomMap<K, V>& input);
+// static void SetToNull(CustomMap<K, V>* output);
+//
+// static size_t GetSize(const CustomMap<K, V>& input);
+//
+// static CustomConstIterator GetBegin(const CustomMap<K, V>& input);
+// static CustomIterator GetBegin(CustomMap<K, V>& input);
+//
+// static void AdvanceIterator(CustomConstIterator& iterator);
+// static void AdvanceIterator(CustomIterator& iterator);
+//
+// static const K& GetKey(CustomIterator& iterator);
+// static const K& GetKey(CustomConstIterator& iterator);
+//
+// static V& GetValue(CustomIterator& iterator);
+// static const V& GetValue(CustomConstIterator& iterator);
+//
+// // Returning false results in deserialization failure and causes the
+// // message pipe receiving it to be disconnected. |IK| and |IV| are
+// // separate input key/value template parameters that allows for the
+// // the key/value types to be forwarded.
+// template <typename IK, typename IV>
+// static bool Insert(CustomMap<K, V>& input,
+// IK&& key,
+// IV&& value);
+//
+// static void SetToEmpty(CustomMap<K, V>* output);
+// };
+//
+template <typename T>
+struct MapTraits;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/map_traits_stl.h b/mojo/public/cpp/bindings/map_traits_stl.h
new file mode 100644
index 0000000000..83a4399ce0
--- /dev/null
+++ b/mojo/public/cpp/bindings/map_traits_stl.h
@@ -0,0 +1,109 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_STL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_STL_H_
+
+#include <map>
+#include <unordered_map>
+
+#include "mojo/public/cpp/bindings/map_traits.h"
+
+namespace mojo {
+
+template <typename K, typename V>
+struct MapTraits<std::map<K, V>> {
+ using Key = K;
+ using Value = V;
+ using Iterator = typename std::map<K, V>::iterator;
+ using ConstIterator = typename std::map<K, V>::const_iterator;
+
+ static bool IsNull(const std::map<K, V>& input) {
+ // std::map<> is always converted to non-null mojom map.
+ return false;
+ }
+
+ static void SetToNull(std::map<K, V>* output) {
+ // std::map<> doesn't support null state. Set it to empty instead.
+ output->clear();
+ }
+
+ static size_t GetSize(const std::map<K, V>& input) { return input.size(); }
+
+ static ConstIterator GetBegin(const std::map<K, V>& input) {
+ return input.begin();
+ }
+ static Iterator GetBegin(std::map<K, V>& input) { return input.begin(); }
+
+ static void AdvanceIterator(ConstIterator& iterator) { iterator++; }
+ static void AdvanceIterator(Iterator& iterator) { iterator++; }
+
+ static const K& GetKey(Iterator& iterator) { return iterator->first; }
+ static const K& GetKey(ConstIterator& iterator) { return iterator->first; }
+
+ static V& GetValue(Iterator& iterator) { return iterator->second; }
+ static const V& GetValue(ConstIterator& iterator) { return iterator->second; }
+
+ static bool Insert(std::map<K, V>& input, const K& key, V&& value) {
+ input.insert(std::make_pair(key, std::forward<V>(value)));
+ return true;
+ }
+ static bool Insert(std::map<K, V>& input, const K& key, const V& value) {
+ input.insert(std::make_pair(key, value));
+ return true;
+ }
+
+ static void SetToEmpty(std::map<K, V>* output) { output->clear(); }
+};
+
+template <typename K, typename V>
+struct MapTraits<std::unordered_map<K, V>> {
+ using Key = K;
+ using Value = V;
+ using Iterator = typename std::unordered_map<K, V>::iterator;
+ using ConstIterator = typename std::unordered_map<K, V>::const_iterator;
+
+ static bool IsNull(const std::unordered_map<K, V>& input) {
+ // std::unordered_map<> is always converted to non-null mojom map.
+ return false;
+ }
+
+ static void SetToNull(std::unordered_map<K, V>* output) {
+ // std::unordered_map<> doesn't support null state. Set it to empty instead.
+ output->clear();
+ }
+
+ static size_t GetSize(const std::unordered_map<K, V>& input) {
+ return input.size();
+ }
+
+ static ConstIterator GetBegin(const std::unordered_map<K, V>& input) {
+ return input.begin();
+ }
+ static Iterator GetBegin(std::unordered_map<K, V>& input) {
+ return input.begin();
+ }
+
+ static void AdvanceIterator(ConstIterator& iterator) { iterator++; }
+ static void AdvanceIterator(Iterator& iterator) { iterator++; }
+
+ static const K& GetKey(Iterator& iterator) { return iterator->first; }
+ static const K& GetKey(ConstIterator& iterator) { return iterator->first; }
+
+ static V& GetValue(Iterator& iterator) { return iterator->second; }
+ static const V& GetValue(ConstIterator& iterator) { return iterator->second; }
+
+ template <typename IK, typename IV>
+ static bool Insert(std::unordered_map<K, V>& input, IK&& key, IV&& value) {
+ input.insert(
+ std::make_pair(std::forward<IK>(key), std::forward<IV>(value)));
+ return true;
+ }
+
+ static void SetToEmpty(std::unordered_map<K, V>* output) { output->clear(); }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_STL_H_
diff --git a/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h b/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h
new file mode 100644
index 0000000000..dd68b3686a
--- /dev/null
+++ b/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h
@@ -0,0 +1,64 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_WTF_HASH_MAP_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_WTF_HASH_MAP_H_
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/map_traits.h"
+#include "third_party/WebKit/Source/wtf/HashMap.h"
+
+namespace mojo {
+
+template <typename K, typename V>
+struct MapTraits<WTF::HashMap<K, V>> {
+ using Key = K;
+ using Value = V;
+ using Iterator = typename WTF::HashMap<K, V>::iterator;
+ using ConstIterator = typename WTF::HashMap<K, V>::const_iterator;
+
+ static bool IsNull(const WTF::HashMap<K, V>& input) {
+ // WTF::HashMap<> is always converted to non-null mojom map.
+ return false;
+ }
+
+ static void SetToNull(WTF::HashMap<K, V>* output) {
+ // WTF::HashMap<> doesn't support null state. Set it to empty instead.
+ output->clear();
+ }
+
+ static size_t GetSize(const WTF::HashMap<K, V>& input) {
+ return input.size();
+ }
+
+ static ConstIterator GetBegin(const WTF::HashMap<K, V>& input) {
+ return input.begin();
+ }
+ static Iterator GetBegin(WTF::HashMap<K, V>& input) { return input.begin(); }
+
+ static void AdvanceIterator(ConstIterator& iterator) { ++iterator; }
+ static void AdvanceIterator(Iterator& iterator) { ++iterator; }
+
+ static const K& GetKey(Iterator& iterator) { return iterator->key; }
+ static const K& GetKey(ConstIterator& iterator) { return iterator->key; }
+
+ static V& GetValue(Iterator& iterator) { return iterator->value; }
+ static const V& GetValue(ConstIterator& iterator) { return iterator->value; }
+
+ template <typename IK, typename IV>
+ static bool Insert(WTF::HashMap<K, V>& input, IK&& key, IV&& value) {
+ if (!WTF::HashMap<K, V>::isValidKey(key)) {
+ LOG(ERROR) << "The key value is disallowed by WTF::HashMap";
+ return false;
+ }
+ input.insert(std::forward<IK>(key), std::forward<IV>(value));
+ return true;
+ }
+
+ static void SetToEmpty(WTF::HashMap<K, V>* output) { output->clear(); }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_WTF_HASH_MAP_H_
diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h
new file mode 100644
index 0000000000..48e6900306
--- /dev/null
+++ b/mojo/public/cpp/bindings/message.h
@@ -0,0 +1,302 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/message_buffer.h"
+#include "mojo/public/cpp/bindings/lib/message_internal.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/system/message.h"
+
+namespace mojo {
+
+class AssociatedGroupController;
+
+using ReportBadMessageCallback = base::Callback<void(const std::string& error)>;
+
+// Message is a holder for the data and handles to be sent over a MessagePipe.
+// Message owns its data and handles, but a consumer of Message is free to
+// mutate the data and handles. The message's data is comprised of a header
+// followed by payload.
+class MOJO_CPP_BINDINGS_EXPORT Message {
+ public:
+ static const uint32_t kFlagExpectsResponse = 1 << 0;
+ static const uint32_t kFlagIsResponse = 1 << 1;
+ static const uint32_t kFlagIsSync = 1 << 2;
+
+ Message();
+ Message(Message&& other);
+
+ ~Message();
+
+ Message& operator=(Message&& other);
+
+ // Resets the Message to an uninitialized state. Upon reset, the Message
+ // exists as if it were default-constructed: it has no data buffer and owns no
+ // handles.
+ void Reset();
+
+ // Indicates whether this Message is uninitialized.
+ bool IsNull() const { return !buffer_; }
+
+ // Initializes a Message with enough space for |capacity| bytes.
+ void Initialize(size_t capacity, bool zero_initialized);
+
+ // Initializes a Message from an existing Mojo MessageHandle.
+ void InitializeFromMojoMessage(ScopedMessageHandle message,
+ uint32_t num_bytes,
+ std::vector<Handle>* handles);
+
+ uint32_t data_num_bytes() const {
+ return static_cast<uint32_t>(buffer_->size());
+ }
+
+ // Access the raw bytes of the message.
+ const uint8_t* data() const {
+ return static_cast<const uint8_t*>(buffer_->data());
+ }
+
+ uint8_t* mutable_data() { return static_cast<uint8_t*>(buffer_->data()); }
+
+ // Access the header.
+ const internal::MessageHeader* header() const {
+ return static_cast<const internal::MessageHeader*>(buffer_->data());
+ }
+ internal::MessageHeader* header() {
+ return static_cast<internal::MessageHeader*>(buffer_->data());
+ }
+
+ const internal::MessageHeaderV1* header_v1() const {
+ DCHECK_GE(version(), 1u);
+ return static_cast<const internal::MessageHeaderV1*>(buffer_->data());
+ }
+ internal::MessageHeaderV1* header_v1() {
+ DCHECK_GE(version(), 1u);
+ return static_cast<internal::MessageHeaderV1*>(buffer_->data());
+ }
+
+ const internal::MessageHeaderV2* header_v2() const {
+ DCHECK_GE(version(), 2u);
+ return static_cast<const internal::MessageHeaderV2*>(buffer_->data());
+ }
+ internal::MessageHeaderV2* header_v2() {
+ DCHECK_GE(version(), 2u);
+ return static_cast<internal::MessageHeaderV2*>(buffer_->data());
+ }
+
+ uint32_t version() const { return header()->version; }
+
+ uint32_t interface_id() const { return header()->interface_id; }
+ void set_interface_id(uint32_t id) { header()->interface_id = id; }
+
+ uint32_t name() const { return header()->name; }
+ bool has_flag(uint32_t flag) const { return !!(header()->flags & flag); }
+
+ // Access the request_id field (if present).
+ uint64_t request_id() const { return header_v1()->request_id; }
+ void set_request_id(uint64_t request_id) {
+ header_v1()->request_id = request_id;
+ }
+
+ // Access the payload.
+ const uint8_t* payload() const;
+ uint8_t* mutable_payload() { return const_cast<uint8_t*>(payload()); }
+ uint32_t payload_num_bytes() const;
+
+ uint32_t payload_num_interface_ids() const;
+ const uint32_t* payload_interface_ids() const;
+
+ // Access the handles.
+ const std::vector<Handle>* handles() const { return &handles_; }
+ std::vector<Handle>* mutable_handles() { return &handles_; }
+
+ const std::vector<ScopedInterfaceEndpointHandle>*
+ associated_endpoint_handles() const {
+ return &associated_endpoint_handles_;
+ }
+ std::vector<ScopedInterfaceEndpointHandle>*
+ mutable_associated_endpoint_handles() {
+ return &associated_endpoint_handles_;
+ }
+
+ // Access the underlying Buffer interface.
+ internal::Buffer* buffer() { return buffer_.get(); }
+
+ // Takes a scoped MessageHandle which may be passed to |WriteMessageNew()| for
+ // transmission. Note that this invalidates this Message object, taking
+ // ownership of its internal storage and any attached handles.
+ ScopedMessageHandle TakeMojoMessage();
+
+ // Notifies the system that this message is "bad," in this case meaning it was
+ // rejected by bindings validation code.
+ void NotifyBadMessage(const std::string& error);
+
+ // Serializes |associated_endpoint_handles_| into the payload_interface_ids
+ // field.
+ void SerializeAssociatedEndpointHandles(
+ AssociatedGroupController* group_controller);
+
+ // Deserializes |associated_endpoint_handles_| from the payload_interface_ids
+ // field.
+ bool DeserializeAssociatedEndpointHandles(
+ AssociatedGroupController* group_controller);
+
+ private:
+ void CloseHandles();
+
+ std::unique_ptr<internal::MessageBuffer> buffer_;
+ std::vector<Handle> handles_;
+ std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(Message);
+};
+
+class MessageReceiver {
+ public:
+ virtual ~MessageReceiver() {}
+
+ // The receiver may mutate the given message. Returns true if the message
+ // was accepted and false otherwise, indicating that the message was invalid
+ // or malformed.
+ virtual bool Accept(Message* message) WARN_UNUSED_RESULT = 0;
+};
+
+class MessageReceiverWithResponder : public MessageReceiver {
+ public:
+ ~MessageReceiverWithResponder() override {}
+
+ // A variant on Accept that registers a MessageReceiver (known as the
+ // responder) to handle the response message generated from the given
+ // message. The responder's Accept method may be called during
+ // AcceptWithResponder or some time after its return.
+ virtual bool AcceptWithResponder(Message* message,
+ std::unique_ptr<MessageReceiver> responder)
+ WARN_UNUSED_RESULT = 0;
+};
+
+// A MessageReceiver that is also able to provide status about the state
+// of the underlying MessagePipe to which it will be forwarding messages
+// received via the |Accept()| call.
+class MessageReceiverWithStatus : public MessageReceiver {
+ public:
+ ~MessageReceiverWithStatus() override {}
+
+ // Returns |true| if this MessageReceiver is currently bound to a MessagePipe,
+ // the pipe has not been closed, and the pipe has not encountered an error.
+ virtual bool IsValid() = 0;
+
+ // DCHECKs if this MessageReceiver is currently bound to a MessagePipe, the
+ // pipe has not been closed, and the pipe has not encountered an error.
+ // This function may be called on any thread.
+ virtual void DCheckInvalid(const std::string& message) = 0;
+};
+
+// An alternative to MessageReceiverWithResponder for cases in which it
+// is necessary for the implementor of this interface to know about the status
+// of the MessagePipe which will carry the responses.
+class MessageReceiverWithResponderStatus : public MessageReceiver {
+ public:
+ ~MessageReceiverWithResponderStatus() override {}
+
+ // A variant on Accept that registers a MessageReceiverWithStatus (known as
+ // the responder) to handle the response message generated from the given
+ // message. Any of the responder's methods (Accept or IsValid) may be called
+ // during AcceptWithResponder or some time after its return.
+ virtual bool AcceptWithResponder(Message* message,
+ std::unique_ptr<MessageReceiverWithStatus>
+ responder) WARN_UNUSED_RESULT = 0;
+};
+
+class MOJO_CPP_BINDINGS_EXPORT PassThroughFilter
+ : NON_EXPORTED_BASE(public MessageReceiver) {
+ public:
+ PassThroughFilter();
+ ~PassThroughFilter() override;
+
+ // MessageReceiver:
+ bool Accept(Message* message) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PassThroughFilter);
+};
+
+namespace internal {
+class SyncMessageResponseSetup;
+}
+
+// An object which should be constructed on the stack immediately before making
+// a sync request for which the caller wishes to perform custom validation of
+// the response value(s). It is illegal to make more than one sync call during
+// the lifetime of the topmost SyncMessageResponseContext, but it is legal to
+// nest contexts to support reentrancy.
+//
+// Usage should look something like:
+//
+// SyncMessageResponseContext response_context;
+// foo_interface->SomeSyncCall(&response_value);
+// if (response_value.IsBad())
+// response_context.ReportBadMessage("Bad response_value!");
+//
+class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseContext {
+ public:
+ SyncMessageResponseContext();
+ ~SyncMessageResponseContext();
+
+ static SyncMessageResponseContext* current();
+
+ void ReportBadMessage(const std::string& error);
+
+ const ReportBadMessageCallback& GetBadMessageCallback();
+
+ private:
+ friend class internal::SyncMessageResponseSetup;
+
+ SyncMessageResponseContext* outer_context_;
+ Message response_;
+ ReportBadMessageCallback bad_message_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncMessageResponseContext);
+};
+
+// Read a single message from the pipe. The caller should have created the
+// Message, but not called Initialize(). Returns MOJO_RESULT_SHOULD_WAIT if
+// the caller should wait on the handle to become readable. Returns
+// MOJO_RESULT_OK if the message was read successfully and should be
+// dispatched, otherwise returns an error code if something went wrong.
+//
+// NOTE: The message hasn't been validated and may be malformed!
+MojoResult ReadMessage(MessagePipeHandle handle, Message* message);
+
+// Reports the currently dispatching Message as bad. Note that this is only
+// legal to call from directly within the stack frame of a message dispatch. If
+// you need to do asynchronous work before you can determine the legitimacy of
+// a message, use TakeBadMessageCallback() and retain its result until you're
+// ready to invoke or discard it.
+MOJO_CPP_BINDINGS_EXPORT
+void ReportBadMessage(const std::string& error);
+
+// Acquires a callback which may be run to report the currently dispatching
+// Message as bad. Note that this is only legal to call from directly within the
+// stack frame of a message dispatch, but the returned callback may be called
+// exactly once any time thereafter to report the message as bad. This may only
+// be called once per message.
+MOJO_CPP_BINDINGS_EXPORT
+ReportBadMessageCallback GetBadMessageCallback();
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_
diff --git a/mojo/public/cpp/bindings/message_header_validator.h b/mojo/public/cpp/bindings/message_header_validator.h
new file mode 100644
index 0000000000..50c19dbe04
--- /dev/null
+++ b/mojo/public/cpp/bindings/message_header_validator.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 MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_
+
+#include "base/compiler_specific.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+
+class MOJO_CPP_BINDINGS_EXPORT MessageHeaderValidator
+ : NON_EXPORTED_BASE(public MessageReceiver) {
+ public:
+ MessageHeaderValidator();
+ explicit MessageHeaderValidator(const std::string& description);
+
+ // Sets the description associated with this validator. Used for reporting
+ // more detailed validation errors.
+ void SetDescription(const std::string& description);
+
+ bool Accept(Message* message) override;
+
+ private:
+ std::string description_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_
diff --git a/mojo/public/cpp/bindings/native_enum.h b/mojo/public/cpp/bindings/native_enum.h
new file mode 100644
index 0000000000..08b43b78bf
--- /dev/null
+++ b/mojo/public/cpp/bindings/native_enum.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 MOJO_PUBLIC_CPP_BINDINGS_NATIVE_ENUM_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_ENUM_H_
+
+#include <functional>
+
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/native_enum_data.h"
+
+namespace mojo {
+
+// Native-only enums correspond to "[Native] enum Foo;" definitions in mojom.
+enum class NativeEnum : int32_t {};
+
+} // namespace mojo
+
+namespace std {
+
+template <>
+struct hash<mojo::NativeEnum>
+ : public mojo::internal::EnumHashImpl<mojo::NativeEnum> {};
+
+} // namespace std
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_ENUM_H_
diff --git a/mojo/public/cpp/bindings/native_struct.h b/mojo/public/cpp/bindings/native_struct.h
new file mode 100644
index 0000000000..ac27250bcc
--- /dev/null
+++ b/mojo/public/cpp/bindings/native_struct.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 MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_
+
+#include <vector>
+
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "mojo/public/cpp/bindings/type_converter.h"
+
+namespace mojo {
+
+class NativeStruct;
+using NativeStructPtr = StructPtr<NativeStruct>;
+
+// Native-only structs correspond to "[Native] struct Foo;" definitions in
+// mojom.
+class MOJO_CPP_BINDINGS_EXPORT NativeStruct {
+ public:
+ using Data_ = internal::NativeStruct_Data;
+
+ static NativeStructPtr New();
+
+ template <typename U>
+ static NativeStructPtr From(const U& u) {
+ return TypeConverter<NativeStructPtr, U>::Convert(u);
+ }
+
+ template <typename U>
+ U To() const {
+ return TypeConverter<U, NativeStruct>::Convert(*this);
+ }
+
+ NativeStruct();
+ ~NativeStruct();
+
+ NativeStructPtr Clone() const;
+ bool Equals(const NativeStruct& other) const;
+ size_t Hash(size_t seed) const;
+
+ base::Optional<std::vector<uint8_t>> data;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_
diff --git a/mojo/public/cpp/bindings/native_struct_data_view.h b/mojo/public/cpp/bindings/native_struct_data_view.h
new file mode 100644
index 0000000000..613bd7a0b0
--- /dev/null
+++ b/mojo/public/cpp/bindings/native_struct_data_view.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_
+
+#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+
+namespace mojo {
+
+class NativeStructDataView {
+ public:
+ using Data_ = internal::NativeStruct_Data;
+
+ NativeStructDataView() {}
+
+ NativeStructDataView(Data_* data, internal::SerializationContext* context)
+ : data_(data) {}
+
+ bool is_null() const { return !data_; }
+
+ size_t size() const { return data_->data.size(); }
+
+ uint8_t operator[](size_t index) const { return data_->data.at(index); }
+
+ const uint8_t* data() const { return data_->data.storage(); }
+
+ private:
+ Data_* data_ = nullptr;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_
diff --git a/mojo/public/cpp/bindings/pipe_control_message_handler.h b/mojo/public/cpp/bindings/pipe_control_message_handler.h
new file mode 100644
index 0000000000..a5c04da627
--- /dev/null
+++ b/mojo/public/cpp/bindings/pipe_control_message_handler.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 MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+
+class PipeControlMessageHandlerDelegate;
+
+// Handler for messages defined in pipe_control_messages.mojom.
+class MOJO_CPP_BINDINGS_EXPORT PipeControlMessageHandler
+ : NON_EXPORTED_BASE(public MessageReceiver) {
+ public:
+ explicit PipeControlMessageHandler(
+ PipeControlMessageHandlerDelegate* delegate);
+ ~PipeControlMessageHandler() override;
+
+ // Sets the description for this handler. Used only when reporting validation
+ // errors.
+ void SetDescription(const std::string& description);
+
+ // NOTE: |message| must have passed message header validation.
+ static bool IsPipeControlMessage(const Message* message);
+
+ // MessageReceiver implementation:
+
+ // NOTE: |message| must:
+ // - have passed message header validation; and
+ // - be a pipe control message (i.e., IsPipeControlMessage() returns true).
+ // If the method returns false, the message pipe should be closed.
+ bool Accept(Message* message) override;
+
+ private:
+ // |message| must have passed message header validation.
+ bool Validate(Message* message);
+ bool RunOrClosePipe(Message* message);
+
+ std::string description_;
+ PipeControlMessageHandlerDelegate* const delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(PipeControlMessageHandler);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_H_
diff --git a/mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h b/mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h
new file mode 100644
index 0000000000..16fd918d5d
--- /dev/null
+++ b/mojo/public/cpp/bindings/pipe_control_message_handler_delegate.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 MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_DELEGATE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_DELEGATE_H_
+
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/disconnect_reason.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+
+namespace mojo {
+
+class PipeControlMessageHandlerDelegate {
+ public:
+ // The implementation of the following methods should return false if the
+ // notification is unexpected. In that case, the user of this delegate is
+ // expected to close the message pipe.
+ virtual bool OnPeerAssociatedEndpointClosed(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason) = 0;
+
+ protected:
+ virtual ~PipeControlMessageHandlerDelegate() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_DELEGATE_H_
diff --git a/mojo/public/cpp/bindings/pipe_control_message_proxy.h b/mojo/public/cpp/bindings/pipe_control_message_proxy.h
new file mode 100644
index 0000000000..52c408f827
--- /dev/null
+++ b/mojo/public/cpp/bindings/pipe_control_message_proxy.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 MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_PROXY_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_PROXY_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/disconnect_reason.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+
+class MessageReceiver;
+
+// Proxy for request messages defined in pipe_control_messages.mojom.
+//
+// NOTE: This object may be used from multiple threads.
+class MOJO_CPP_BINDINGS_EXPORT PipeControlMessageProxy {
+ public:
+ // Doesn't take ownership of |receiver|. If This PipeControlMessageProxy will
+ // be used from multiple threads, |receiver| must be thread-safe.
+ explicit PipeControlMessageProxy(MessageReceiver* receiver);
+
+ void NotifyPeerEndpointClosed(InterfaceId id,
+ const base::Optional<DisconnectReason>& reason);
+
+ static Message ConstructPeerEndpointClosedMessage(
+ InterfaceId id,
+ const base::Optional<DisconnectReason>& reason);
+
+ private:
+ // Not owned.
+ MessageReceiver* receiver_;
+
+ DISALLOW_COPY_AND_ASSIGN(PipeControlMessageProxy);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_PROXY_H_
diff --git a/mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h b/mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h
new file mode 100644
index 0000000000..4d40cdfc12
--- /dev/null
+++ b/mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h
@@ -0,0 +1,22 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_RAW_PTR_IMPL_REF_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_RAW_PTR_IMPL_REF_TRAITS_H_
+
+namespace mojo {
+
+// Default traits for a binding's implementation reference type. This
+// corresponds to a raw pointer.
+template <typename Interface>
+struct RawPtrImplRefTraits {
+ using PointerType = Interface*;
+
+ static bool IsNull(PointerType ptr) { return !ptr; }
+ static Interface* GetRawPointer(PointerType* ptr) { return *ptr; }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_RAW_PTR_IMPL_REF_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h b/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h
new file mode 100644
index 0000000000..16527cf747
--- /dev/null
+++ b/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_SCOPED_INTERFACE_ENDPOINT_HANDLE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SCOPED_INTERFACE_ENDPOINT_HANDLE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/disconnect_reason.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+
+namespace mojo {
+
+class AssociatedGroupController;
+
+// ScopedInterfaceEndpointHandle refers to one end of an interface, either the
+// implementation side or the client side.
+// Threading: At any given time, a ScopedInterfaceEndpointHandle should only
+// be accessed from a single thread.
+class MOJO_CPP_BINDINGS_EXPORT ScopedInterfaceEndpointHandle {
+ public:
+ // Creates a pair of handles representing the two endpoints of an interface,
+ // which are not yet associated with a message pipe.
+ static void CreatePairPendingAssociation(
+ ScopedInterfaceEndpointHandle* handle0,
+ ScopedInterfaceEndpointHandle* handle1);
+
+ // Creates an invalid endpoint handle.
+ ScopedInterfaceEndpointHandle();
+
+ ScopedInterfaceEndpointHandle(ScopedInterfaceEndpointHandle&& other);
+
+ ~ScopedInterfaceEndpointHandle();
+
+ ScopedInterfaceEndpointHandle& operator=(
+ ScopedInterfaceEndpointHandle&& other);
+
+ bool is_valid() const;
+
+ // Returns true if the interface hasn't associated with a message pipe.
+ bool pending_association() const;
+
+ // Returns kInvalidInterfaceId when in pending association state or the handle
+ // is invalid.
+ InterfaceId id() const;
+
+ // Returns null when in pending association state or the handle is invalid.
+ AssociatedGroupController* group_controller() const;
+
+ // Returns the disconnect reason if the peer handle is closed before
+ // association and specifies a custom disconnect reason.
+ const base::Optional<DisconnectReason>& disconnect_reason() const;
+
+ enum AssociationEvent {
+ // The interface has been associated with a message pipe.
+ ASSOCIATED,
+ // The peer of this object has been closed before association.
+ PEER_CLOSED_BEFORE_ASSOCIATION
+ };
+
+ using AssociationEventCallback = base::OnceCallback<void(AssociationEvent)>;
+ // Note:
+ // - |handler| won't run if the handle is invalid. Otherwise, |handler| is run
+ // on the calling thread asynchronously, even if the interface has already
+ // been associated or the peer has been closed before association.
+ // - |handler| won't be called after this object is destroyed or reset.
+ // - A null |handler| can be used to cancel the previous callback.
+ void SetAssociationEventHandler(AssociationEventCallback handler);
+
+ void reset();
+ void ResetWithReason(uint32_t custom_reason, const std::string& description);
+
+ private:
+ friend class AssociatedGroupController;
+ friend class AssociatedGroup;
+
+ class State;
+
+ // Used by AssociatedGroupController.
+ ScopedInterfaceEndpointHandle(
+ InterfaceId id,
+ scoped_refptr<AssociatedGroupController> group_controller);
+
+ // Used by AssociatedGroupController.
+ // The peer of this handle will join |peer_group_controller|.
+ bool NotifyAssociation(
+ InterfaceId id,
+ scoped_refptr<AssociatedGroupController> peer_group_controller);
+
+ void ResetInternal(const base::Optional<DisconnectReason>& reason);
+
+ // Used by AssociatedGroup.
+ // It is safe to run the returned callback on any thread, or after this handle
+ // is destroyed.
+ // The return value of the getter:
+ // - If the getter is retrieved when the handle is invalid, the return value
+ // of the getter will always be null.
+ // - If the getter is retrieved when the handle is valid and non-pending,
+ // the return value of the getter will be non-null and remain unchanged
+ // even if the handle is later reset.
+ // - If the getter is retrieved when the handle is valid but pending
+ // asssociation, the return value of the getter will initially be null,
+ // change to non-null when the handle is associated, and remain unchanged
+ // ever since.
+ base::Callback<AssociatedGroupController*()> CreateGroupControllerGetter()
+ const;
+
+ scoped_refptr<State> state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedInterfaceEndpointHandle);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_SCOPED_INTERFACE_ENDPOINT_HANDLE_H_
diff --git a/mojo/public/cpp/bindings/string_data_view.h b/mojo/public/cpp/bindings/string_data_view.h
new file mode 100644
index 0000000000..2b091b45f8
--- /dev/null
+++ b/mojo/public/cpp/bindings/string_data_view.h
@@ -0,0 +1,34 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_STRING_DATA_VIEW_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRING_DATA_VIEW_H_
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+
+namespace mojo {
+
+// Access to the contents of a serialized string.
+class StringDataView {
+ public:
+ StringDataView() {}
+
+ StringDataView(internal::String_Data* data,
+ internal::SerializationContext* context)
+ : data_(data) {}
+
+ bool is_null() const { return !data_; }
+
+ const char* storage() const { return data_->storage(); }
+
+ size_t size() const { return data_->size(); }
+
+ private:
+ internal::String_Data* data_ = nullptr;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_DATA_VIEW_H_
diff --git a/mojo/public/cpp/bindings/string_traits.h b/mojo/public/cpp/bindings/string_traits.h
new file mode 100644
index 0000000000..7d3075a579
--- /dev/null
+++ b/mojo/public/cpp/bindings/string_traits.h
@@ -0,0 +1,54 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_
+
+#include "mojo/public/cpp/bindings/string_data_view.h"
+
+namespace mojo {
+
+// This must be specialized for any type |T| to be serialized/deserialized as
+// a mojom string.
+//
+// Imagine you want to specialize it for CustomString, usually you need to
+// implement:
+//
+// template <T>
+// struct StringTraits<CustomString> {
+// // These two methods are optional. Please see comments in struct_traits.h
+// static bool IsNull(const CustomString& input);
+// static void SetToNull(CustomString* output);
+//
+// static size_t GetSize(const CustomString& input);
+// static const char* GetData(const CustomString& input);
+//
+// // The caller guarantees that |!input.is_null()|.
+// static bool Read(StringDataView input, CustomString* output);
+// };
+//
+// In some cases, you may need to do conversion before you can return the size
+// and data as 8-bit characters for serialization. (For example, CustomString is
+// UTF-16 string). In that case, you can add two optional methods:
+//
+// static void* SetUpContext(const CustomString& input);
+// static void TearDownContext(const CustomString& input, void* context);
+//
+// And then you append a second parameter, void* context, to GetSize() and
+// GetData():
+//
+// static size_t GetSize(const CustomString& input, void* context);
+// static const char* GetData(const CustomString& input, void* context);
+//
+// If a CustomString instance is not null, the serialization code will call
+// SetUpContext() at the beginning, and pass the resulting context pointer to
+// GetSize()/GetData(). After serialization is done, it calls TearDownContext()
+// so that you can do any necessary cleanup.
+//
+template <typename T>
+struct StringTraits;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/string_traits_stl.h b/mojo/public/cpp/bindings/string_traits_stl.h
new file mode 100644
index 0000000000..f6cc8ad098
--- /dev/null
+++ b/mojo/public/cpp/bindings/string_traits_stl.h
@@ -0,0 +1,38 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STL_H_
+
+#include <string>
+
+#include "mojo/public/cpp/bindings/string_traits.h"
+
+namespace mojo {
+
+template <>
+struct StringTraits<std::string> {
+ static bool IsNull(const std::string& input) {
+ // std::string is always converted to non-null mojom string.
+ return false;
+ }
+
+ static void SetToNull(std::string* output) {
+ // std::string doesn't support null state. Set it to empty instead.
+ output->clear();
+ }
+
+ static size_t GetSize(const std::string& input) { return input.size(); }
+
+ static const char* GetData(const std::string& input) { return input.data(); }
+
+ static bool Read(StringDataView input, std::string* output) {
+ output->assign(input.storage(), input.size());
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STL_H_
diff --git a/mojo/public/cpp/bindings/string_traits_string16.h b/mojo/public/cpp/bindings/string_traits_string16.h
new file mode 100644
index 0000000000..f96973ad91
--- /dev/null
+++ b/mojo/public/cpp/bindings/string_traits_string16.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_
+
+#include "base/strings/string16.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/string_traits.h"
+
+namespace mojo {
+
+template <>
+struct MOJO_CPP_BINDINGS_EXPORT StringTraits<base::string16> {
+ static bool IsNull(const base::string16& input) {
+ // base::string16 is always converted to non-null mojom string.
+ return false;
+ }
+
+ static void SetToNull(base::string16* output) {
+ // Convert null to an "empty" base::string16.
+ output->clear();
+ }
+
+ static void* SetUpContext(const base::string16& input);
+ static void TearDownContext(const base::string16& input, void* context);
+
+ static size_t GetSize(const base::string16& input, void* context);
+ static const char* GetData(const base::string16& input, void* context);
+
+ static bool Read(StringDataView input, base::string16* output);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_
diff --git a/mojo/public/cpp/bindings/string_traits_string_piece.h b/mojo/public/cpp/bindings/string_traits_string_piece.h
new file mode 100644
index 0000000000..af6be893ac
--- /dev/null
+++ b/mojo/public/cpp/bindings/string_traits_string_piece.h
@@ -0,0 +1,43 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING_PIECE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING_PIECE_H_
+
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/bindings/string_traits.h"
+
+namespace mojo {
+
+template <>
+struct StringTraits<base::StringPiece> {
+ static bool IsNull(const base::StringPiece& input) {
+ // base::StringPiece is always converted to non-null mojom string. We could
+ // have let StringPiece containing a null data pointer map to null mojom
+ // string, but StringPiece::empty() returns true in this case. It seems
+ // confusing to mix the concept of empty and null strings, especially
+ // because they mean different things in mojom.
+ return false;
+ }
+
+ static void SetToNull(base::StringPiece* output) {
+ // Convert null to an "empty" base::StringPiece.
+ output->set(nullptr, 0);
+ }
+
+ static size_t GetSize(const base::StringPiece& input) { return input.size(); }
+
+ static const char* GetData(const base::StringPiece& input) {
+ return input.data();
+ }
+
+ static bool Read(StringDataView input, base::StringPiece* output) {
+ output->set(input.storage(), input.size());
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING_PIECE_H_
diff --git a/mojo/public/cpp/bindings/string_traits_wtf.h b/mojo/public/cpp/bindings/string_traits_wtf.h
new file mode 100644
index 0000000000..238c2eb119
--- /dev/null
+++ b/mojo/public/cpp/bindings/string_traits_wtf.h
@@ -0,0 +1,31 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_WTF_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_WTF_H_
+
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/string_traits.h"
+#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+
+namespace mojo {
+
+template <>
+struct StringTraits<WTF::String> {
+ static bool IsNull(const WTF::String& input) { return input.isNull(); }
+ static void SetToNull(WTF::String* output);
+
+ static void* SetUpContext(const WTF::String& input);
+ static void TearDownContext(const WTF::String& input, void* context);
+
+ static size_t GetSize(const WTF::String& input, void* context);
+
+ static const char* GetData(const WTF::String& input, void* context);
+
+ static bool Read(StringDataView input, WTF::String* output);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_WTF_H_
diff --git a/mojo/public/cpp/bindings/strong_associated_binding.h b/mojo/public/cpp/bindings/strong_associated_binding.h
new file mode 100644
index 0000000000..a1e299bb2d
--- /dev/null
+++ b/mojo/public/cpp/bindings/strong_associated_binding.h
@@ -0,0 +1,125 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+template <typename Interface>
+class StrongAssociatedBinding;
+
+template <typename Interface>
+using StrongAssociatedBindingPtr =
+ base::WeakPtr<StrongAssociatedBinding<Interface>>;
+
+// This connects an interface implementation strongly to an associated pipe.
+// When a connection error is detected the implementation is deleted. If the
+// task runner that a StrongAssociatedBinding is bound on is stopped, the
+// connection error handler will not be invoked and the implementation will not
+// be deleted.
+//
+// To use, call StrongAssociatedBinding<T>::Create() (see below) or the helper
+// MakeStrongAssociatedBinding function:
+//
+// mojo::MakeStrongAssociatedBinding(base::MakeUnique<FooImpl>(),
+// std::move(foo_request));
+//
+template <typename Interface>
+class StrongAssociatedBinding {
+ public:
+ // Create a new StrongAssociatedBinding instance. The instance owns itself,
+ // cleaning up only in the event of a pipe connection error. Returns a WeakPtr
+ // to the new StrongAssociatedBinding instance.
+ static StrongAssociatedBindingPtr<Interface> Create(
+ std::unique_ptr<Interface> impl,
+ AssociatedInterfaceRequest<Interface> request) {
+ StrongAssociatedBinding* binding =
+ new StrongAssociatedBinding(std::move(impl), std::move(request));
+ return binding->weak_factory_.GetWeakPtr();
+ }
+
+ // Note: The error handler must not delete the interface implementation.
+ //
+ // This method may only be called after this StrongAssociatedBinding has been
+ // bound to a message pipe.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(binding_.is_bound());
+ connection_error_handler_ = error_handler;
+ connection_error_with_reason_handler_.Reset();
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(binding_.is_bound());
+ connection_error_with_reason_handler_ = error_handler;
+ connection_error_handler_.Reset();
+ }
+
+ // Forces the binding to close. This destroys the StrongBinding instance.
+ void Close() { delete this; }
+
+ Interface* impl() { return impl_.get(); }
+
+ // Sends a message on the underlying message pipe and runs the current
+ // message loop until its response is received. This can be used in tests to
+ // verify that no message was sent on a message pipe in response to some
+ // stimulus.
+ void FlushForTesting() { binding_.FlushForTesting(); }
+
+ private:
+ StrongAssociatedBinding(std::unique_ptr<Interface> impl,
+ AssociatedInterfaceRequest<Interface> request)
+ : impl_(std::move(impl)),
+ binding_(impl_.get(), std::move(request)),
+ weak_factory_(this) {
+ binding_.set_connection_error_with_reason_handler(base::Bind(
+ &StrongAssociatedBinding::OnConnectionError, base::Unretained(this)));
+ }
+
+ ~StrongAssociatedBinding() {}
+
+ void OnConnectionError(uint32_t custom_reason,
+ const std::string& description) {
+ if (!connection_error_handler_.is_null())
+ connection_error_handler_.Run();
+ else if (!connection_error_with_reason_handler_.is_null())
+ connection_error_with_reason_handler_.Run(custom_reason, description);
+ Close();
+ }
+
+ std::unique_ptr<Interface> impl_;
+ base::Closure connection_error_handler_;
+ ConnectionErrorWithReasonCallback connection_error_with_reason_handler_;
+ AssociatedBinding<Interface> binding_;
+ base::WeakPtrFactory<StrongAssociatedBinding> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(StrongAssociatedBinding);
+};
+
+template <typename Interface, typename Impl>
+StrongAssociatedBindingPtr<Interface> MakeStrongAssociatedBinding(
+ std::unique_ptr<Impl> impl,
+ AssociatedInterfaceRequest<Interface> request) {
+ return StrongAssociatedBinding<Interface>::Create(std::move(impl),
+ std::move(request));
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_H_
diff --git a/mojo/public/cpp/bindings/strong_binding.h b/mojo/public/cpp/bindings/strong_binding.h
new file mode 100644
index 0000000000..f4b4a061cd
--- /dev/null
+++ b/mojo/public/cpp/bindings/strong_binding.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/message_header_validator.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+template <typename Interface>
+class StrongBinding;
+
+template <typename Interface>
+using StrongBindingPtr = base::WeakPtr<StrongBinding<Interface>>;
+
+// This connects an interface implementation strongly to a pipe. When a
+// connection error is detected the implementation is deleted. If the task
+// runner that a StrongBinding is bound on is stopped, the connection error
+// handler will not be invoked and the implementation will not be deleted.
+//
+// To use, call StrongBinding<T>::Create() (see below) or the helper
+// MakeStrongBinding function:
+//
+// mojo::MakeStrongBinding(base::MakeUnique<FooImpl>(),
+// std::move(foo_request));
+//
+template <typename Interface>
+class StrongBinding {
+ public:
+ // Create a new StrongBinding instance. The instance owns itself, cleaning up
+ // only in the event of a pipe connection error. Returns a WeakPtr to the new
+ // StrongBinding instance.
+ static StrongBindingPtr<Interface> Create(
+ std::unique_ptr<Interface> impl,
+ InterfaceRequest<Interface> request) {
+ StrongBinding* binding =
+ new StrongBinding(std::move(impl), std::move(request));
+ return binding->weak_factory_.GetWeakPtr();
+ }
+
+ // Note: The error handler must not delete the interface implementation.
+ //
+ // This method may only be called after this StrongBinding has been bound to a
+ // message pipe.
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(binding_.is_bound());
+ connection_error_handler_ = error_handler;
+ connection_error_with_reason_handler_.Reset();
+ }
+
+ void set_connection_error_with_reason_handler(
+ const ConnectionErrorWithReasonCallback& error_handler) {
+ DCHECK(binding_.is_bound());
+ connection_error_with_reason_handler_ = error_handler;
+ connection_error_handler_.Reset();
+ }
+
+ // Forces the binding to close. This destroys the StrongBinding instance.
+ void Close() { delete this; }
+
+ Interface* impl() { return impl_.get(); }
+
+ // Sends a message on the underlying message pipe and runs the current
+ // message loop until its response is received. This can be used in tests to
+ // verify that no message was sent on a message pipe in response to some
+ // stimulus.
+ void FlushForTesting() { binding_.FlushForTesting(); }
+
+ private:
+ StrongBinding(std::unique_ptr<Interface> impl,
+ InterfaceRequest<Interface> request)
+ : impl_(std::move(impl)),
+ binding_(impl_.get(), std::move(request)),
+ weak_factory_(this) {
+ binding_.set_connection_error_with_reason_handler(
+ base::Bind(&StrongBinding::OnConnectionError, base::Unretained(this)));
+ }
+
+ ~StrongBinding() {}
+
+ void OnConnectionError(uint32_t custom_reason,
+ const std::string& description) {
+ if (!connection_error_handler_.is_null())
+ connection_error_handler_.Run();
+ else if (!connection_error_with_reason_handler_.is_null())
+ connection_error_with_reason_handler_.Run(custom_reason, description);
+ Close();
+ }
+
+ std::unique_ptr<Interface> impl_;
+ base::Closure connection_error_handler_;
+ ConnectionErrorWithReasonCallback connection_error_with_reason_handler_;
+ Binding<Interface> binding_;
+ base::WeakPtrFactory<StrongBinding> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(StrongBinding);
+};
+
+template <typename Interface, typename Impl>
+StrongBindingPtr<Interface> MakeStrongBinding(
+ std::unique_ptr<Impl> impl,
+ InterfaceRequest<Interface> request) {
+ return StrongBinding<Interface>::Create(std::move(impl), std::move(request));
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_
diff --git a/mojo/public/cpp/bindings/strong_binding_set.h b/mojo/public/cpp/bindings/strong_binding_set.h
new file mode 100644
index 0000000000..f6bcd5259c
--- /dev/null
+++ b/mojo/public/cpp/bindings/strong_binding_set.h
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_SET_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_SET_H_
+
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h"
+
+namespace mojo {
+
+// This class manages a set of bindings. When the pipe a binding is bound to is
+// disconnected, the binding is automatically destroyed and removed from the
+// set, and the interface implementation is deleted. When the StrongBindingSet
+// is destructed, all outstanding bindings in the set are destroyed and all the
+// bound interface implementations are automatically deleted.
+template <typename Interface, typename ContextType = void>
+using StrongBindingSet =
+ BindingSetBase<Interface,
+ Binding<Interface, UniquePtrImplRefTraits<Interface>>,
+ ContextType>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_SET_H_
diff --git a/mojo/public/cpp/bindings/struct_ptr.h b/mojo/public/cpp/bindings/struct_ptr.h
new file mode 100644
index 0000000000..b135312e39
--- /dev/null
+++ b/mojo/public/cpp/bindings/struct_ptr.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 MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_
+
+#include <functional>
+#include <memory>
+#include <new>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/lib/hash_util.h"
+#include "mojo/public/cpp/bindings/type_converter.h"
+
+namespace mojo {
+namespace internal {
+
+constexpr size_t kHashSeed = 31;
+
+template <typename Struct>
+class StructPtrWTFHelper;
+
+template <typename Struct>
+class InlinedStructPtrWTFHelper;
+
+} // namespace internal
+
+// Smart pointer wrapping a mojom structure with move-only semantics.
+template <typename S>
+class StructPtr {
+ public:
+ using Struct = S;
+
+ StructPtr() = default;
+ StructPtr(decltype(nullptr)) {}
+
+ ~StructPtr() = default;
+
+ StructPtr& operator=(decltype(nullptr)) {
+ reset();
+ return *this;
+ }
+
+ StructPtr(StructPtr&& other) { Take(&other); }
+ StructPtr& operator=(StructPtr&& other) {
+ Take(&other);
+ return *this;
+ }
+
+ template <typename... Args>
+ StructPtr(base::in_place_t, Args&&... args)
+ : ptr_(new Struct(std::forward<Args>(args)...)) {}
+
+ template <typename U>
+ U To() const {
+ return TypeConverter<U, StructPtr>::Convert(*this);
+ }
+
+ void reset() { ptr_.reset(); }
+
+ bool is_null() const { return !ptr_; }
+
+ Struct& operator*() const {
+ DCHECK(ptr_);
+ return *ptr_;
+ }
+ Struct* operator->() const {
+ DCHECK(ptr_);
+ return ptr_.get();
+ }
+ Struct* get() const { return ptr_.get(); }
+
+ void Swap(StructPtr* other) { std::swap(ptr_, other->ptr_); }
+
+ // Please note that calling this method will fail compilation if the value
+ // type |Struct| doesn't have a Clone() method defined (which usually means
+ // that it contains Mojo handles).
+ StructPtr Clone() const { return is_null() ? StructPtr() : ptr_->Clone(); }
+
+ // Compares the pointees (which might both be null).
+ // TODO(tibell): Get rid of Equals in favor of the operator. Same for Hash.
+ bool Equals(const StructPtr& other) const {
+ if (is_null() || other.is_null())
+ return is_null() && other.is_null();
+ return ptr_->Equals(*other.ptr_);
+ }
+
+ // Hashes based on the pointee (which might be null).
+ size_t Hash(size_t seed) const {
+ if (is_null())
+ return internal::HashCombine(seed, 0);
+ return ptr_->Hash(seed);
+ }
+
+ explicit operator bool() const { return !is_null(); }
+
+ private:
+ friend class internal::StructPtrWTFHelper<Struct>;
+ void Take(StructPtr* other) {
+ reset();
+ Swap(other);
+ }
+
+ std::unique_ptr<Struct> ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(StructPtr);
+};
+
+template <typename T>
+bool operator==(const StructPtr<T>& lhs, const StructPtr<T>& rhs) {
+ return lhs.Equals(rhs);
+}
+template <typename T>
+bool operator!=(const StructPtr<T>& lhs, const StructPtr<T>& rhs) {
+ return !(lhs == rhs);
+}
+
+// Designed to be used when Struct is small and copyable.
+template <typename S>
+class InlinedStructPtr {
+ public:
+ using Struct = S;
+
+ InlinedStructPtr() : state_(NIL) {}
+ InlinedStructPtr(decltype(nullptr)) : state_(NIL) {}
+
+ ~InlinedStructPtr() {}
+
+ InlinedStructPtr& operator=(decltype(nullptr)) {
+ reset();
+ return *this;
+ }
+
+ InlinedStructPtr(InlinedStructPtr&& other) : state_(NIL) { Take(&other); }
+ InlinedStructPtr& operator=(InlinedStructPtr&& other) {
+ Take(&other);
+ return *this;
+ }
+
+ template <typename... Args>
+ InlinedStructPtr(base::in_place_t, Args&&... args)
+ : value_(std::forward<Args>(args)...), state_(VALID) {}
+
+ template <typename U>
+ U To() const {
+ return TypeConverter<U, InlinedStructPtr>::Convert(*this);
+ }
+
+ void reset() {
+ state_ = NIL;
+ value_. ~Struct();
+ new (&value_) Struct();
+ }
+
+ bool is_null() const { return state_ == NIL; }
+
+ Struct& operator*() const {
+ DCHECK(state_ == VALID);
+ return value_;
+ }
+ Struct* operator->() const {
+ DCHECK(state_ == VALID);
+ return &value_;
+ }
+ Struct* get() const { return &value_; }
+
+ void Swap(InlinedStructPtr* other) {
+ std::swap(value_, other->value_);
+ std::swap(state_, other->state_);
+ }
+
+ InlinedStructPtr Clone() const {
+ return is_null() ? InlinedStructPtr() : value_.Clone();
+ }
+
+ // Compares the pointees (which might both be null).
+ bool Equals(const InlinedStructPtr& other) const {
+ if (is_null() || other.is_null())
+ return is_null() && other.is_null();
+ return value_.Equals(other.value_);
+ }
+
+ // Hashes based on the pointee (which might be null).
+ size_t Hash(size_t seed) const {
+ if (is_null())
+ return internal::HashCombine(seed, 0);
+ return value_.Hash(seed);
+ }
+
+ explicit operator bool() const { return !is_null(); }
+
+ private:
+ friend class internal::InlinedStructPtrWTFHelper<Struct>;
+ void Take(InlinedStructPtr* other) {
+ reset();
+ Swap(other);
+ }
+
+ enum State {
+ VALID,
+ NIL,
+ DELETED, // For use in WTF::HashMap only
+ };
+
+ mutable Struct value_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(InlinedStructPtr);
+};
+
+template <typename T>
+bool operator==(const InlinedStructPtr<T>& lhs,
+ const InlinedStructPtr<T>& rhs) {
+ return lhs.Equals(rhs);
+}
+template <typename T>
+bool operator!=(const InlinedStructPtr<T>& lhs,
+ const InlinedStructPtr<T>& rhs) {
+ return !(lhs == rhs);
+}
+
+namespace internal {
+
+template <typename Struct>
+class StructPtrWTFHelper {
+ public:
+ static bool IsHashTableDeletedValue(const StructPtr<Struct>& value) {
+ return value.ptr_.get() == reinterpret_cast<Struct*>(1u);
+ }
+
+ static void ConstructDeletedValue(mojo::StructPtr<Struct>& slot) {
+ // |slot| refers to a previous, real value that got deleted and had its
+ // destructor run, so this is the first time the "deleted value" has its
+ // constructor called.
+ //
+ // Dirty trick: implant an invalid pointer in |ptr_|. Destructor isn't
+ // called for deleted buckets, so this is okay.
+ new (&slot) StructPtr<Struct>();
+ slot.ptr_.reset(reinterpret_cast<Struct*>(1u));
+ }
+};
+
+template <typename Struct>
+class InlinedStructPtrWTFHelper {
+ public:
+ static bool IsHashTableDeletedValue(const InlinedStructPtr<Struct>& value) {
+ return value.state_ == InlinedStructPtr<Struct>::DELETED;
+ }
+
+ static void ConstructDeletedValue(mojo::InlinedStructPtr<Struct>& slot) {
+ // |slot| refers to a previous, real value that got deleted and had its
+ // destructor run, so this is the first time the "deleted value" has its
+ // constructor called.
+ new (&slot) InlinedStructPtr<Struct>();
+ slot.state_ = InlinedStructPtr<Struct>::DELETED;
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+namespace std {
+
+template <typename T>
+struct hash<mojo::StructPtr<T>> {
+ size_t operator()(const mojo::StructPtr<T>& value) const {
+ return value.Hash(mojo::internal::kHashSeed);
+ }
+};
+
+template <typename T>
+struct hash<mojo::InlinedStructPtr<T>> {
+ size_t operator()(const mojo::InlinedStructPtr<T>& value) const {
+ return value.Hash(mojo::internal::kHashSeed);
+ }
+};
+
+} // namespace std
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_
diff --git a/mojo/public/cpp/bindings/struct_traits.h b/mojo/public/cpp/bindings/struct_traits.h
new file mode 100644
index 0000000000..6cc070fc48
--- /dev/null
+++ b/mojo/public/cpp/bindings/struct_traits.h
@@ -0,0 +1,165 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_
+
+namespace mojo {
+
+// This must be specialized for any type |T| to be serialized/deserialized as
+// a mojom struct. |DataViewType| is the corresponding data view type of the
+// mojom struct. For example, if the mojom struct is example.Foo,
+// |DataViewType| will be example::FooDataView, which can also be referred to by
+// example::Foo::DataView (in chromium) and example::blink::Foo::DataView (in
+// blink).
+//
+// Each specialization needs to implement a few things:
+// 1. Static getters for each field in the Mojom type. These should be
+// of the form:
+//
+// static <return type> <field name>(const T& input);
+//
+// and should return a serializable form of the named field as extracted
+// from |input|.
+//
+// Serializable form of a field:
+// Value or reference of the same type used in the generated stuct
+// wrapper type, or the following alternatives:
+// - string:
+// Value or reference of any type that has a StringTraits defined.
+// Supported by default: base::StringPiece, std::string,
+// WTF::String (in blink).
+//
+// - array:
+// Value or reference of any type that has an ArrayTraits defined.
+// Supported by default: std::vector, CArray, WTF::Vector (in blink)
+//
+// - map:
+// Value or reference of any type that has a MapTraits defined.
+// Supported by default: std::map, std::unordered_map,
+// WTF::HashMap (in blink).
+//
+// - struct:
+// Value or reference of any type that has a StructTraits defined.
+//
+// - enum:
+// Value of any type that has an EnumTraits defined.
+//
+// For any nullable string/struct/array/map/union field you could also
+// return value or reference of base::Optional<T>/WTF::Optional<T>, if T
+// has the right *Traits defined.
+//
+// During serialization, getters for string/struct/array/map/union fields
+// are called twice (one for size calculation and one for actual
+// serialization). If you want to return a value (as opposed to a
+// reference) from these getters, you have to be sure that constructing and
+// copying the returned object is really cheap.
+//
+// Getters for fields of other types are called once.
+//
+// 2. A static Read() method to set the contents of a |T| instance from a
+// DataViewType.
+//
+// static bool Read(DataViewType data, T* output);
+//
+// The generated DataViewType provides a convenient, inexpensive view of a
+// serialized struct's field data. The caller guarantees that
+// |!data.is_null()|.
+//
+// Returning false indicates invalid incoming data and causes the message
+// pipe receiving it to be disconnected. Therefore, you can do custom
+// validation for |T| in this method.
+//
+// 3. [Optional] A static IsNull() method indicating whether a given |T|
+// instance is null:
+//
+// static bool IsNull(const T& input);
+//
+// If this method returns true, it is guaranteed that none of the getters
+// (described in section 1) will be called for the same |input|. So you
+// don't have to check whether |input| is null in those getters.
+//
+// If it is not defined, |T| instances are always considered non-null.
+//
+// [Optional] A static SetToNull() method to set the contents of a given
+// |T| instance to null.
+//
+// static void SetToNull(T* output);
+//
+// When a null serialized struct is received, the deserialization code
+// calls this method instead of Read().
+//
+// NOTE: It is to set |*output|'s contents to a null state, not to set the
+// |output| pointer itself to null. "Null state" means whatever state you
+// think it makes sense to map a null serialized struct to.
+//
+// If it is not defined, null is not allowed to be converted to |T|. In
+// that case, an incoming null value is considered invalid and causes the
+// message pipe to be disconnected.
+//
+// 4. [Optional] As mentioned above, getters for string/struct/array/map/union
+// fields are called multiple times (twice to be exact). If you need to do
+// some expensive calculation/conversion, you probably want to cache the
+// result across multiple calls. You can introduce an arbitrary context
+// object by adding two optional methods:
+// static void* SetUpContext(const T& input);
+// static void TearDownContext(const T& input, void* context);
+//
+// And then you append a second parameter, void* context, to getters:
+// static <return type> <field name>(const T& input, void* context);
+//
+// If a T instance is not null, the serialization code will call
+// SetUpContext() at the beginning, and pass the resulting context pointer
+// to getters. After serialization is done, it calls TearDownContext() so
+// that you can do any necessary cleanup.
+//
+// In the description above, methods having an |input| parameter define it as
+// const reference of T. Actually, it can be a non-const reference of T too.
+// E.g., if T contains Mojo handles or interfaces whose ownership needs to be
+// transferred. Correspondingly, it requies you to always give non-const T
+// reference/value to the Mojo bindings for serialization:
+// - if T is used in the "type_mappings" section of a typemap config file,
+// you need to declare it as pass-by-value:
+// type_mappings = [ "MojomType=T[move_only]" ]
+// or
+// type_mappings = [ "MojomType=T[copyable_pass_by_value]" ]
+//
+// - if another type U's StructTraits/UnionTraits has a getter for T, it
+// needs to return non-const reference/value.
+//
+// EXAMPLE:
+//
+// Mojom definition:
+// struct Bar {};
+// struct Foo {
+// int32 f_integer;
+// string f_string;
+// array<string> f_string_array;
+// Bar f_bar;
+// };
+//
+// StructTraits for Foo:
+// template <>
+// struct StructTraits<FooDataView, CustomFoo> {
+// // Optional methods dealing with null:
+// static bool IsNull(const CustomFoo& input);
+// static void SetToNull(CustomFoo* output);
+//
+// // Field getters:
+// static int32_t f_integer(const CustomFoo& input);
+// static const std::string& f_string(const CustomFoo& input);
+// static const std::vector<std::string>& f_string_array(
+// const CustomFoo& input);
+// // Assuming there is a StructTraits<Bar, CustomBar> defined.
+// static const CustomBar& f_bar(const CustomFoo& input);
+//
+// static bool Read(FooDataView data, CustomFoo* output);
+// };
+//
+template <typename DataViewType, typename T>
+struct StructTraits;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h
new file mode 100644
index 0000000000..5529042784
--- /dev/null
+++ b/mojo/public/cpp/bindings/sync_call_restrictions.h
@@ -0,0 +1,108 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_
+
+#include "base/macros.h"
+#include "base/threading/thread_restrictions.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+
+#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
+#define ENABLE_SYNC_CALL_RESTRICTIONS 1
+#else
+#define ENABLE_SYNC_CALL_RESTRICTIONS 0
+#endif
+
+namespace leveldb {
+class LevelDBMojoProxy;
+}
+
+namespace prefs {
+class PersistentPrefStoreClient;
+}
+
+namespace ui {
+class Gpu;
+}
+
+namespace views {
+class ClipboardMus;
+}
+
+namespace mojo {
+
+// In some processes, sync calls are disallowed. For example, in the browser
+// process we don't want any sync calls to child processes for performance,
+// security and stability reasons. SyncCallRestrictions helps to enforce such
+// rules.
+//
+// Before processing a sync call, the bindings call
+// SyncCallRestrictions::AssertSyncCallAllowed() to check whether sync calls are
+// allowed. By default, it is determined by the mojo system property
+// MOJO_PROPERTY_SYNC_CALL_ALLOWED. If the default setting says no but you have
+// a very compelling reason to disregard that (which should be very very rare),
+// you can override it by constructing a ScopedAllowSyncCall object, which
+// allows making sync calls on the current thread during its lifetime.
+class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions {
+ public:
+#if ENABLE_SYNC_CALL_RESTRICTIONS
+ // Checks whether the current thread is allowed to make sync calls, and causes
+ // a DCHECK if not.
+ static void AssertSyncCallAllowed();
+#else
+ // Inline the empty definitions of functions so that they can be compiled out.
+ static void AssertSyncCallAllowed() {}
+#endif
+
+ private:
+ // DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to mojo/OWNERS first.
+ // BEGIN ALLOWED USAGE.
+ friend class ui::Gpu; // http://crbug.com/620058
+ // LevelDBMojoProxy makes same-process sync calls from the DB thread.
+ friend class leveldb::LevelDBMojoProxy;
+ // Pref service connection is sync at startup.
+ friend class prefs::PersistentPrefStoreClient;
+
+ // END ALLOWED USAGE.
+
+ // BEGIN USAGE THAT NEEDS TO BE FIXED.
+ // In the non-mus case, we called blocking OS functions in the ui::Clipboard
+ // implementation which weren't caught by sync call restrictions. Our blocking
+ // calls to mus, however, are.
+ friend class views::ClipboardMus;
+ // END USAGE THAT NEEDS TO BE FIXED.
+
+#if ENABLE_SYNC_CALL_RESTRICTIONS
+ static void IncreaseScopedAllowCount();
+ static void DecreaseScopedAllowCount();
+#else
+ static void IncreaseScopedAllowCount() {}
+ static void DecreaseScopedAllowCount() {}
+#endif
+
+ // If a process is configured to disallow sync calls in general, constructing
+ // a ScopedAllowSyncCall object temporarily allows making sync calls on the
+ // current thread. Doing this is almost always incorrect, which is why we
+ // limit who can use this through friend. If you find yourself needing to use
+ // this, talk to mojo/OWNERS.
+ class ScopedAllowSyncCall {
+ public:
+ ScopedAllowSyncCall() { IncreaseScopedAllowCount(); }
+ ~ScopedAllowSyncCall() { DecreaseScopedAllowCount(); }
+
+ private:
+#if ENABLE_SYNC_CALL_RESTRICTIONS
+ base::ThreadRestrictions::ScopedAllowWait allow_wait_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowSyncCall);
+ };
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(SyncCallRestrictions);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_
diff --git a/mojo/public/cpp/bindings/sync_event_watcher.h b/mojo/public/cpp/bindings/sync_event_watcher.h
new file mode 100644
index 0000000000..6e254844e9
--- /dev/null
+++ b/mojo/public/cpp/bindings/sync_event_watcher.h
@@ -0,0 +1,68 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_
+
+#include <stddef.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/sync_handle_registry.h"
+
+namespace mojo {
+
+// SyncEventWatcher supports waiting on a base::WaitableEvent to signal while
+// also allowing other SyncEventWatchers and SyncHandleWatchers on the same
+// thread to wake up as needed.
+//
+// This class is not thread safe.
+class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher {
+ public:
+ SyncEventWatcher(base::WaitableEvent* event, const base::Closure& callback);
+
+ ~SyncEventWatcher();
+
+ // Registers |event_| with SyncHandleRegistry, so that when others perform
+ // sync watching on the same thread, |event_| will be watched along with them.
+ void AllowWokenUpBySyncWatchOnSameThread();
+
+ // Waits on |event_| plus all other events and handles registered with this
+ // thread's SyncHandleRegistry, running callbacks synchronously for any ready
+ // events and handles.
+ // This method:
+ // - returns true when |should_stop| is set to true;
+ // - return false when any error occurs, including this object being
+ // destroyed during a callback.
+ bool SyncWatch(const bool* should_stop);
+
+ private:
+ void IncrementRegisterCount();
+ void DecrementRegisterCount();
+
+ base::WaitableEvent* const event_;
+ const base::Closure callback_;
+
+ // Whether |event_| has been registered with SyncHandleRegistry.
+ bool registered_ = false;
+
+ // If non-zero, |event_| should be registered with SyncHandleRegistry.
+ size_t register_request_count_ = 0;
+
+ scoped_refptr<SyncHandleRegistry> registry_;
+
+ scoped_refptr<base::RefCountedData<bool>> destroyed_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncEventWatcher);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_
diff --git a/mojo/public/cpp/bindings/sync_handle_registry.h b/mojo/public/cpp/bindings/sync_handle_registry.h
new file mode 100644
index 0000000000..afb3b56bf4
--- /dev/null
+++ b/mojo/public/cpp/bindings/sync_handle_registry.h
@@ -0,0 +1,71 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_
+
+#include <map>
+#include <unordered_map>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/wait_set.h"
+
+namespace mojo {
+
+// SyncHandleRegistry is a thread-local storage to register handles that want to
+// be watched together.
+//
+// This class is not thread safe.
+class MOJO_CPP_BINDINGS_EXPORT SyncHandleRegistry
+ : public base::RefCounted<SyncHandleRegistry> {
+ public:
+ // Returns a thread-local object.
+ static scoped_refptr<SyncHandleRegistry> current();
+
+ using HandleCallback = base::Callback<void(MojoResult)>;
+ bool RegisterHandle(const Handle& handle,
+ MojoHandleSignals handle_signals,
+ const HandleCallback& callback);
+
+ void UnregisterHandle(const Handle& handle);
+
+ // Registers a |base::WaitableEvent| which can be used to wake up
+ // Wait() before any handle signals. |event| is not owned, and if it signals
+ // during Wait(), |callback| is invoked. Returns |true| if registered
+ // successfully or |false| if |event| was already registered.
+ bool RegisterEvent(base::WaitableEvent* event, const base::Closure& callback);
+
+ void UnregisterEvent(base::WaitableEvent* event);
+
+ // Waits on all the registered handles and events and runs callbacks
+ // synchronously for any that become ready.
+ // The method:
+ // - returns true when any element of |should_stop| is set to true;
+ // - returns false when any error occurs.
+ bool Wait(const bool* should_stop[], size_t count);
+
+ private:
+ friend class base::RefCounted<SyncHandleRegistry>;
+
+ SyncHandleRegistry();
+ ~SyncHandleRegistry();
+
+ WaitSet wait_set_;
+ std::map<Handle, HandleCallback> handles_;
+ std::map<base::WaitableEvent*, base::Closure> events_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncHandleRegistry);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_
diff --git a/mojo/public/cpp/bindings/sync_handle_watcher.h b/mojo/public/cpp/bindings/sync_handle_watcher.h
new file mode 100644
index 0000000000..eff73dd66e
--- /dev/null
+++ b/mojo/public/cpp/bindings/sync_handle_watcher.h
@@ -0,0 +1,75 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_WATCHER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_WATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/sync_handle_registry.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+// SyncHandleWatcher supports watching a handle synchronously. It also supports
+// registering the handle with a thread-local storage (SyncHandleRegistry), so
+// that when other SyncHandleWatcher instances on the same thread perform sync
+// handle watching, this handle will be watched together.
+//
+// SyncHandleWatcher is used for sync methods. While a sync call is waiting for
+// response, we would like to block the thread. On the other hand, we need
+// incoming sync method requests on the same thread to be able to reenter. We
+// also need master interface endpoints to continue dispatching messages for
+// associated endpoints on different threads.
+//
+// This class is not thread safe.
+class MOJO_CPP_BINDINGS_EXPORT SyncHandleWatcher {
+ public:
+ // Note: |handle| must outlive this object.
+ SyncHandleWatcher(const Handle& handle,
+ MojoHandleSignals handle_signals,
+ const SyncHandleRegistry::HandleCallback& callback);
+
+ ~SyncHandleWatcher();
+
+ // Registers |handle_| with SyncHandleRegistry, so that when others perform
+ // sync handle watching on the same thread, |handle_| will be watched
+ // together.
+ void AllowWokenUpBySyncWatchOnSameThread();
+
+ // Waits on |handle_| plus all handles registered with SyncHandleRegistry and
+ // runs callbacks synchronously for those ready handles.
+ // This method:
+ // - returns true when |should_stop| is set to true;
+ // - return false when any error occurs, including this object being
+ // destroyed during a callback.
+ bool SyncWatch(const bool* should_stop);
+
+ private:
+ void IncrementRegisterCount();
+ void DecrementRegisterCount();
+
+ const Handle handle_;
+ const MojoHandleSignals handle_signals_;
+ SyncHandleRegistry::HandleCallback callback_;
+
+ // Whether |handle_| has been registered with SyncHandleRegistry.
+ bool registered_;
+ // If non-zero, |handle_| should be registered with SyncHandleRegistry.
+ size_t register_request_count_;
+
+ scoped_refptr<SyncHandleRegistry> registry_;
+
+ scoped_refptr<base::RefCountedData<bool>> destroyed_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncHandleWatcher);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_WATCHER_H_
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
new file mode 100644
index 0000000000..668ca6da90
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -0,0 +1,146 @@
+# Copyright 2014 The Chromium 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("tests") {
+ testonly = true
+
+ sources = [
+ "associated_interface_unittest.cc",
+ "bind_task_runner_unittest.cc",
+ "binding_callback_unittest.cc",
+ "binding_set_unittest.cc",
+ "binding_unittest.cc",
+ "buffer_unittest.cc",
+ "connector_unittest.cc",
+ "constant_unittest.cc",
+ "container_test_util.cc",
+ "container_test_util.h",
+ "data_view_unittest.cc",
+ "equals_unittest.cc",
+ "handle_passing_unittest.cc",
+ "hash_unittest.cc",
+ "interface_ptr_unittest.cc",
+ "map_unittest.cc",
+ "message_queue.cc",
+ "message_queue.h",
+ "multiplex_router_unittest.cc",
+ "report_bad_message_unittest.cc",
+ "request_response_unittest.cc",
+ "router_test_util.cc",
+ "router_test_util.h",
+ "sample_service_unittest.cc",
+ "serialization_warning_unittest.cc",
+ "struct_unittest.cc",
+ "sync_method_unittest.cc",
+ "type_conversion_unittest.cc",
+ "union_unittest.cc",
+ "validation_context_unittest.cc",
+ "validation_unittest.cc",
+ "variant_test_util.h",
+ ]
+
+ deps = [
+ ":mojo_public_bindings_test_utils",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/interfaces/bindings/tests:test_associated_interfaces",
+ "//mojo/public/interfaces/bindings/tests:test_export_component",
+ "//mojo/public/interfaces/bindings/tests:test_export_component2",
+ "//mojo/public/interfaces/bindings/tests:test_exported_import",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//mojo/public/interfaces/bindings/tests:test_struct_traits_interfaces",
+ "//testing/gtest",
+ ]
+
+ data = [
+ "//mojo/public/interfaces/bindings/tests/data/validation/",
+ ]
+
+ if (is_ios) {
+ assert_no_deps = [ "//third_party/WebKit/*" ]
+ } else {
+ sources += [
+ "pickle_unittest.cc",
+ "struct_traits_unittest.cc",
+ ]
+
+ deps += [ "//mojo/public/interfaces/bindings/tests:test_interfaces_blink" ]
+ }
+}
+
+if (!is_ios) {
+ source_set("for_blink_tests") {
+ testonly = true
+
+ sources = [
+ "container_test_util.cc",
+ "container_test_util.h",
+ "variant_test_util.h",
+ "wtf_hash_unittest.cc",
+ "wtf_map_unittest.cc",
+ "wtf_types_unittest.cc",
+ ]
+
+ deps = [
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/system",
+ "//mojo/public/interfaces/bindings/tests:test_export_blink_component",
+ "//mojo/public/interfaces/bindings/tests:test_exported_import_blink",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces_blink",
+ "//mojo/public/interfaces/bindings/tests:test_wtf_types",
+ "//mojo/public/interfaces/bindings/tests:test_wtf_types_blink",
+ "//testing/gtest",
+ ]
+ }
+}
+
+source_set("struct_with_traits_impl") {
+ sources = [
+ "struct_with_traits_impl.cc",
+ "struct_with_traits_impl.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/public/cpp/system:system",
+ ]
+}
+
+source_set("perftests") {
+ testonly = true
+
+ sources = [
+ "bindings_perftest.cc",
+ ]
+
+ if (!is_ios) {
+ sources += [ "e2e_perftest.cc" ]
+ }
+
+ deps = [
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//mojo/edk/test:test_support",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//testing/gtest",
+ ]
+}
+
+source_set("mojo_public_bindings_test_utils") {
+ sources = [
+ "validation_test_input_parser.cc",
+ "validation_test_input_parser.h",
+ ]
+
+ deps = [
+ "//mojo/public/c/system",
+ ]
+}
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
new file mode 100644
index 0000000000..be225e4761
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
@@ -0,0 +1,1189 @@
+// Copyright 2015 The Chromium Authors. 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 <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using mojo::internal::MultiplexRouter;
+
+class IntegerSenderImpl : public IntegerSender {
+ public:
+ explicit IntegerSenderImpl(AssociatedInterfaceRequest<IntegerSender> request)
+ : binding_(this, std::move(request)) {}
+
+ ~IntegerSenderImpl() override {}
+
+ void set_notify_send_method_called(
+ const base::Callback<void(int32_t)>& callback) {
+ notify_send_method_called_ = callback;
+ }
+
+ void Echo(int32_t value, const EchoCallback& callback) override {
+ callback.Run(value);
+ }
+ void Send(int32_t value) override { notify_send_method_called_.Run(value); }
+
+ AssociatedBinding<IntegerSender>* binding() { return &binding_; }
+
+ void set_connection_error_handler(const base::Closure& handler) {
+ binding_.set_connection_error_handler(handler);
+ }
+
+ private:
+ AssociatedBinding<IntegerSender> binding_;
+ base::Callback<void(int32_t)> notify_send_method_called_;
+};
+
+class IntegerSenderConnectionImpl : public IntegerSenderConnection {
+ public:
+ explicit IntegerSenderConnectionImpl(
+ InterfaceRequest<IntegerSenderConnection> request)
+ : binding_(this, std::move(request)) {}
+
+ ~IntegerSenderConnectionImpl() override {}
+
+ void GetSender(AssociatedInterfaceRequest<IntegerSender> sender) override {
+ IntegerSenderImpl* sender_impl = new IntegerSenderImpl(std::move(sender));
+ sender_impl->set_connection_error_handler(
+ base::Bind(&DeleteSender, sender_impl));
+ }
+
+ void AsyncGetSender(const AsyncGetSenderCallback& callback) override {
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ auto request = MakeRequest(&ptr_info);
+ GetSender(std::move(request));
+ callback.Run(std::move(ptr_info));
+ }
+
+ Binding<IntegerSenderConnection>* binding() { return &binding_; }
+
+ private:
+ static void DeleteSender(IntegerSenderImpl* sender) { delete sender; }
+
+ Binding<IntegerSenderConnection> binding_;
+};
+
+class AssociatedInterfaceTest : public testing::Test {
+ public:
+ AssociatedInterfaceTest() {}
+ ~AssociatedInterfaceTest() override { base::RunLoop().RunUntilIdle(); }
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ template <typename T>
+ AssociatedInterfacePtrInfo<T> EmulatePassingAssociatedPtrInfo(
+ AssociatedInterfacePtrInfo<T> ptr_info,
+ scoped_refptr<MultiplexRouter> source,
+ scoped_refptr<MultiplexRouter> target) {
+ ScopedInterfaceEndpointHandle handle = ptr_info.PassHandle();
+ CHECK(handle.pending_association());
+ auto id = source->AssociateInterface(std::move(handle));
+ return AssociatedInterfacePtrInfo<T>(target->CreateLocalEndpointHandle(id),
+ ptr_info.version());
+ }
+
+ void CreateRouterPair(scoped_refptr<MultiplexRouter>* router0,
+ scoped_refptr<MultiplexRouter>* router1) {
+ MessagePipe pipe;
+ *router0 = new MultiplexRouter(std::move(pipe.handle0),
+ MultiplexRouter::MULTI_INTERFACE, true,
+ base::ThreadTaskRunnerHandle::Get());
+ *router1 = new MultiplexRouter(std::move(pipe.handle1),
+ MultiplexRouter::MULTI_INTERFACE, false,
+ base::ThreadTaskRunnerHandle::Get());
+ }
+
+ void CreateIntegerSenderWithExistingRouters(
+ scoped_refptr<MultiplexRouter> router0,
+ IntegerSenderAssociatedPtrInfo* ptr_info0,
+ scoped_refptr<MultiplexRouter> router1,
+ IntegerSenderAssociatedRequest* request1) {
+ *request1 = MakeRequest(ptr_info0);
+ *ptr_info0 = EmulatePassingAssociatedPtrInfo(std::move(*ptr_info0), router1,
+ router0);
+ }
+
+ void CreateIntegerSender(IntegerSenderAssociatedPtrInfo* ptr_info,
+ IntegerSenderAssociatedRequest* request) {
+ scoped_refptr<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+ CreateIntegerSenderWithExistingRouters(router1, ptr_info, router0, request);
+ }
+
+ // Okay to call from any thread.
+ void QuitRunLoop(base::RunLoop* run_loop) {
+ if (loop_.task_runner()->BelongsToCurrentThread()) {
+ run_loop->Quit();
+ } else {
+ loop_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(run_loop)));
+ }
+ }
+
+ private:
+ base::MessageLoop loop_;
+};
+
+void DoSetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
+ *flag = true;
+ closure.Run();
+}
+
+void DoExpectValueSetFlagAndRunClosure(int32_t expected_value,
+ bool* flag,
+ const base::Closure& closure,
+ int32_t value) {
+ EXPECT_EQ(expected_value, value);
+ DoSetFlagAndRunClosure(flag, closure);
+}
+
+base::Closure SetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
+ return base::Bind(&DoSetFlagAndRunClosure, flag, closure);
+}
+
+base::Callback<void(int32_t)> ExpectValueSetFlagAndRunClosure(
+ int32_t expected_value,
+ bool* flag,
+ const base::Closure& closure) {
+ return base::Bind(
+ &DoExpectValueSetFlagAndRunClosure, expected_value, flag, closure);
+}
+
+void Fail() {
+ FAIL() << "Unexpected connection error";
+}
+
+TEST_F(AssociatedInterfaceTest, InterfacesAtBothEnds) {
+ // Bind to the same pipe two associated interfaces, whose implementation lives
+ // at different ends. Test that the two don't interfere with each other.
+
+ scoped_refptr<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, &request);
+
+ IntegerSenderImpl impl0(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> ptr0;
+ ptr0.Bind(std::move(ptr_info));
+
+ CreateIntegerSenderWithExistingRouters(router0, &ptr_info, router1, &request);
+
+ IntegerSenderImpl impl1(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> ptr1;
+ ptr1.Bind(std::move(ptr_info));
+
+ base::RunLoop run_loop, run_loop2;
+ bool ptr0_callback_run = false;
+ ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run,
+ run_loop.QuitClosure()));
+
+ bool ptr1_callback_run = false;
+ ptr1->Echo(456, ExpectValueSetFlagAndRunClosure(456, &ptr1_callback_run,
+ run_loop2.QuitClosure()));
+
+ run_loop.Run();
+ run_loop2.Run();
+ EXPECT_TRUE(ptr0_callback_run);
+ EXPECT_TRUE(ptr1_callback_run);
+
+ bool ptr0_error_callback_run = false;
+ base::RunLoop run_loop3;
+ ptr0.set_connection_error_handler(
+ SetFlagAndRunClosure(&ptr0_error_callback_run, run_loop3.QuitClosure()));
+
+ impl0.binding()->Close();
+ run_loop3.Run();
+ EXPECT_TRUE(ptr0_error_callback_run);
+
+ bool impl1_error_callback_run = false;
+ base::RunLoop run_loop4;
+ impl1.binding()->set_connection_error_handler(
+ SetFlagAndRunClosure(&impl1_error_callback_run, run_loop4.QuitClosure()));
+
+ ptr1.reset();
+ run_loop4.Run();
+ EXPECT_TRUE(impl1_error_callback_run);
+}
+
+class TestSender {
+ public:
+ TestSender()
+ : sender_thread_("TestSender"),
+ next_sender_(nullptr),
+ max_value_to_send_(-1) {
+ sender_thread_.Start();
+ }
+
+ // The following three methods are called on the corresponding sender thread.
+ void SetUp(IntegerSenderAssociatedPtrInfo ptr_info,
+ TestSender* next_sender,
+ int32_t max_value_to_send) {
+ CHECK(sender_thread_.task_runner()->BelongsToCurrentThread());
+
+ ptr_.Bind(std::move(ptr_info));
+ next_sender_ = next_sender ? next_sender : this;
+ max_value_to_send_ = max_value_to_send;
+ }
+
+ void Send(int32_t value) {
+ CHECK(sender_thread_.task_runner()->BelongsToCurrentThread());
+
+ if (value > max_value_to_send_)
+ return;
+
+ ptr_->Send(value);
+
+ next_sender_->sender_thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestSender::Send, base::Unretained(next_sender_), ++value));
+ }
+
+ void TearDown() {
+ CHECK(sender_thread_.task_runner()->BelongsToCurrentThread());
+
+ ptr_.reset();
+ }
+
+ base::Thread* sender_thread() { return &sender_thread_; }
+
+ private:
+ base::Thread sender_thread_;
+ TestSender* next_sender_;
+ int32_t max_value_to_send_;
+
+ AssociatedInterfacePtr<IntegerSender> ptr_;
+};
+
+class TestReceiver {
+ public:
+ TestReceiver() : receiver_thread_("TestReceiver"), expected_calls_(0) {
+ receiver_thread_.Start();
+ }
+
+ void SetUp(AssociatedInterfaceRequest<IntegerSender> request0,
+ AssociatedInterfaceRequest<IntegerSender> request1,
+ size_t expected_calls,
+ const base::Closure& notify_finish) {
+ CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread());
+
+ impl0_.reset(new IntegerSenderImpl(std::move(request0)));
+ impl0_->set_notify_send_method_called(
+ base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this)));
+ impl1_.reset(new IntegerSenderImpl(std::move(request1)));
+ impl1_->set_notify_send_method_called(
+ base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this)));
+
+ expected_calls_ = expected_calls;
+ notify_finish_ = notify_finish;
+ }
+
+ void TearDown() {
+ CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread());
+
+ impl0_.reset();
+ impl1_.reset();
+ }
+
+ base::Thread* receiver_thread() { return &receiver_thread_; }
+ const std::vector<int32_t>& values() const { return values_; }
+
+ private:
+ void SendMethodCalled(int32_t value) {
+ values_.push_back(value);
+
+ if (values_.size() >= expected_calls_)
+ notify_finish_.Run();
+ }
+
+ base::Thread receiver_thread_;
+ size_t expected_calls_;
+
+ std::unique_ptr<IntegerSenderImpl> impl0_;
+ std::unique_ptr<IntegerSenderImpl> impl1_;
+
+ std::vector<int32_t> values_;
+
+ base::Closure notify_finish_;
+};
+
+class NotificationCounter {
+ public:
+ NotificationCounter(size_t total_count, const base::Closure& notify_finish)
+ : total_count_(total_count),
+ current_count_(0),
+ notify_finish_(notify_finish) {}
+
+ ~NotificationCounter() {}
+
+ // Okay to call from any thread.
+ void OnGotNotification() {
+ bool finshed = false;
+ {
+ base::AutoLock locker(lock_);
+ CHECK_LT(current_count_, total_count_);
+ current_count_++;
+ finshed = current_count_ == total_count_;
+ }
+
+ if (finshed)
+ notify_finish_.Run();
+ }
+
+ private:
+ base::Lock lock_;
+ const size_t total_count_;
+ size_t current_count_;
+ base::Closure notify_finish_;
+};
+
+TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
+ // Set up four associated interfaces on a message pipe. Use the inteface
+ // pointers on four threads in parallel; run the interface implementations on
+ // two threads. Test that multi-threaded access works.
+
+ const int32_t kMaxValue = 1000;
+ MessagePipe pipe;
+ scoped_refptr<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> requests[4];
+ IntegerSenderAssociatedPtrInfo ptr_infos[4];
+ for (size_t i = 0; i < 4; ++i) {
+ CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0,
+ &requests[i]);
+ }
+
+ TestSender senders[4];
+ for (size_t i = 0; i < 4; ++i) {
+ senders[i].sender_thread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]),
+ base::Passed(&ptr_infos[i]), nullptr,
+ kMaxValue * (i + 1) / 4));
+ }
+
+ base::RunLoop run_loop;
+ TestReceiver receivers[2];
+ NotificationCounter counter(
+ 2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(&run_loop)));
+ for (size_t i = 0; i < 2; ++i) {
+ receivers[i].receiver_thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]),
+ base::Passed(&requests[2 * i]),
+ base::Passed(&requests[2 * i + 1]),
+ static_cast<size_t>(kMaxValue / 2),
+ base::Bind(&NotificationCounter::OnGotNotification,
+ base::Unretained(&counter))));
+ }
+
+ for (size_t i = 0; i < 4; ++i) {
+ senders[i].sender_thread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&TestSender::Send, base::Unretained(&senders[i]),
+ kMaxValue * i / 4 + 1));
+ }
+
+ run_loop.Run();
+
+ for (size_t i = 0; i < 4; ++i) {
+ base::RunLoop run_loop;
+ senders[i].sender_thread()->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])),
+ base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(&run_loop)));
+ run_loop.Run();
+ }
+
+ for (size_t i = 0; i < 2; ++i) {
+ base::RunLoop run_loop;
+ receivers[i].receiver_thread()->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])),
+ base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(&run_loop)));
+ run_loop.Run();
+ }
+
+ EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[0].values().size());
+ EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[1].values().size());
+
+ std::vector<int32_t> all_values;
+ all_values.insert(all_values.end(), receivers[0].values().begin(),
+ receivers[0].values().end());
+ all_values.insert(all_values.end(), receivers[1].values().begin(),
+ receivers[1].values().end());
+
+ std::sort(all_values.begin(), all_values.end());
+ for (size_t i = 0; i < all_values.size(); ++i)
+ ASSERT_EQ(static_cast<int32_t>(i + 1), all_values[i]);
+}
+
+TEST_F(AssociatedInterfaceTest, FIFO) {
+ // Set up four associated interfaces on a message pipe. Use the inteface
+ // pointers on four threads; run the interface implementations on two threads.
+ // Take turns to make calls using the four pointers. Test that FIFO-ness is
+ // preserved.
+
+ const int32_t kMaxValue = 100;
+ MessagePipe pipe;
+ scoped_refptr<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> requests[4];
+ IntegerSenderAssociatedPtrInfo ptr_infos[4];
+ for (size_t i = 0; i < 4; ++i) {
+ CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0,
+ &requests[i]);
+ }
+
+ TestSender senders[4];
+ for (size_t i = 0; i < 4; ++i) {
+ senders[i].sender_thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]),
+ base::Passed(&ptr_infos[i]),
+ base::Unretained(&senders[(i + 1) % 4]), kMaxValue));
+ }
+
+ base::RunLoop run_loop;
+ TestReceiver receivers[2];
+ NotificationCounter counter(
+ 2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(&run_loop)));
+ for (size_t i = 0; i < 2; ++i) {
+ receivers[i].receiver_thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]),
+ base::Passed(&requests[2 * i]),
+ base::Passed(&requests[2 * i + 1]),
+ static_cast<size_t>(kMaxValue / 2),
+ base::Bind(&NotificationCounter::OnGotNotification,
+ base::Unretained(&counter))));
+ }
+
+ senders[0].sender_thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestSender::Send, base::Unretained(&senders[0]), 1));
+
+ run_loop.Run();
+
+ for (size_t i = 0; i < 4; ++i) {
+ base::RunLoop run_loop;
+ senders[i].sender_thread()->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])),
+ base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(&run_loop)));
+ run_loop.Run();
+ }
+
+ for (size_t i = 0; i < 2; ++i) {
+ base::RunLoop run_loop;
+ receivers[i].receiver_thread()->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])),
+ base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
+ base::Unretained(this), base::Unretained(&run_loop)));
+ run_loop.Run();
+ }
+
+ EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[0].values().size());
+ EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[1].values().size());
+
+ for (size_t i = 0; i < 2; ++i) {
+ for (size_t j = 1; j < receivers[i].values().size(); ++j)
+ EXPECT_LT(receivers[i].values()[j - 1], receivers[i].values()[j]);
+ }
+}
+
+void CaptureInt32(int32_t* storage,
+ const base::Closure& closure,
+ int32_t value) {
+ *storage = value;
+ closure.Run();
+}
+
+void CaptureSenderPtrInfo(IntegerSenderAssociatedPtr* storage,
+ const base::Closure& closure,
+ IntegerSenderAssociatedPtrInfo info) {
+ storage->Bind(std::move(info));
+ closure.Run();
+}
+
+TEST_F(AssociatedInterfaceTest, PassAssociatedInterfaces) {
+ IntegerSenderConnectionPtr connection_ptr;
+ IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+
+ IntegerSenderAssociatedPtr sender0;
+ connection_ptr->GetSender(MakeRequest(&sender0));
+
+ int32_t echoed_value = 0;
+ base::RunLoop run_loop;
+ sender0->Echo(123, base::Bind(&CaptureInt32, &echoed_value,
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ EXPECT_EQ(123, echoed_value);
+
+ IntegerSenderAssociatedPtr sender1;
+ base::RunLoop run_loop2;
+ connection_ptr->AsyncGetSender(
+ base::Bind(&CaptureSenderPtrInfo, &sender1, run_loop2.QuitClosure()));
+ run_loop2.Run();
+ EXPECT_TRUE(sender1);
+
+ base::RunLoop run_loop3;
+ sender1->Echo(456, base::Bind(&CaptureInt32, &echoed_value,
+ run_loop3.QuitClosure()));
+ run_loop3.Run();
+ EXPECT_EQ(456, echoed_value);
+}
+
+TEST_F(AssociatedInterfaceTest, BindingWaitAndPauseWhenNoAssociatedInterfaces) {
+ IntegerSenderConnectionPtr connection_ptr;
+ IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+
+ IntegerSenderAssociatedPtr sender0;
+ connection_ptr->GetSender(MakeRequest(&sender0));
+
+ EXPECT_FALSE(connection.binding()->HasAssociatedInterfaces());
+ // There are no associated interfaces running on the pipe yet. It is okay to
+ // pause.
+ connection.binding()->PauseIncomingMethodCallProcessing();
+ connection.binding()->ResumeIncomingMethodCallProcessing();
+
+ // There are no associated interfaces running on the pipe yet. It is okay to
+ // wait.
+ EXPECT_TRUE(connection.binding()->WaitForIncomingMethodCall());
+
+ // The previous wait has dispatched the GetSender request message, therefore
+ // an associated interface has been set up on the pipe. It is not allowed to
+ // wait or pause.
+ EXPECT_TRUE(connection.binding()->HasAssociatedInterfaces());
+}
+
+class PingServiceImpl : public PingService {
+ public:
+ explicit PingServiceImpl(PingServiceAssociatedRequest request)
+ : binding_(this, std::move(request)) {}
+ ~PingServiceImpl() override {}
+
+ AssociatedBinding<PingService>& binding() { return binding_; }
+
+ void set_ping_handler(const base::Closure& handler) {
+ ping_handler_ = handler;
+ }
+
+ // PingService:
+ void Ping(const PingCallback& callback) override {
+ if (!ping_handler_.is_null())
+ ping_handler_.Run();
+ callback.Run();
+ }
+
+ private:
+ AssociatedBinding<PingService> binding_;
+ base::Closure ping_handler_;
+};
+
+class PingProviderImpl : public AssociatedPingProvider {
+ public:
+ explicit PingProviderImpl(AssociatedPingProviderRequest request)
+ : binding_(this, std::move(request)) {}
+ ~PingProviderImpl() override {}
+
+ // AssociatedPingProvider:
+ void GetPing(PingServiceAssociatedRequest request) override {
+ ping_services_.emplace_back(new PingServiceImpl(std::move(request)));
+
+ if (expected_bindings_count_ > 0 &&
+ ping_services_.size() == expected_bindings_count_ &&
+ !quit_waiting_.is_null()) {
+ expected_bindings_count_ = 0;
+ base::ResetAndReturn(&quit_waiting_).Run();
+ }
+ }
+
+ std::vector<std::unique_ptr<PingServiceImpl>>& ping_services() {
+ return ping_services_;
+ }
+
+ void WaitForBindings(size_t count) {
+ DCHECK(quit_waiting_.is_null());
+
+ expected_bindings_count_ = count;
+ base::RunLoop loop;
+ quit_waiting_ = loop.QuitClosure();
+ loop.Run();
+ }
+
+ private:
+ Binding<AssociatedPingProvider> binding_;
+ std::vector<std::unique_ptr<PingServiceImpl>> ping_services_;
+ size_t expected_bindings_count_ = 0;
+ base::Closure quit_waiting_;
+};
+
+class CallbackFilter : public MessageReceiver {
+ public:
+ explicit CallbackFilter(const base::Closure& callback)
+ : callback_(callback) {}
+ ~CallbackFilter() override {}
+
+ static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) {
+ return base::MakeUnique<CallbackFilter>(callback);
+ }
+
+ // MessageReceiver:
+ bool Accept(Message* message) override {
+ callback_.Run();
+ return true;
+ }
+
+ private:
+ const base::Closure callback_;
+};
+
+// Verifies that filters work as expected on associated bindings, i.e. that
+// they're notified in order, before dispatch; and that each associated
+// binding in a group operates with its own set of filters.
+TEST_F(AssociatedInterfaceTest, BindingWithFilters) {
+ AssociatedPingProviderPtr provider;
+ PingProviderImpl provider_impl(MakeRequest(&provider));
+
+ PingServiceAssociatedPtr ping_a, ping_b;
+ provider->GetPing(MakeRequest(&ping_a));
+ provider->GetPing(MakeRequest(&ping_b));
+ provider_impl.WaitForBindings(2);
+
+ ASSERT_EQ(2u, provider_impl.ping_services().size());
+ PingServiceImpl& ping_a_impl = *provider_impl.ping_services()[0];
+ PingServiceImpl& ping_b_impl = *provider_impl.ping_services()[1];
+
+ int a_status, b_status;
+ auto handler_helper = [] (int* a_status, int* b_status, int expected_a_status,
+ int new_a_status, int expected_b_status,
+ int new_b_status) {
+ EXPECT_EQ(expected_a_status, *a_status);
+ EXPECT_EQ(expected_b_status, *b_status);
+ *a_status = new_a_status;
+ *b_status = new_b_status;
+ };
+ auto create_handler = [&] (int expected_a_status, int new_a_status,
+ int expected_b_status, int new_b_status) {
+ return base::Bind(handler_helper, &a_status, &b_status, expected_a_status,
+ new_a_status, expected_b_status, new_b_status);
+ };
+
+ ping_a_impl.binding().AddFilter(
+ CallbackFilter::Wrap(create_handler(0, 1, 0, 0)));
+ ping_a_impl.binding().AddFilter(
+ CallbackFilter::Wrap(create_handler(1, 2, 0, 0)));
+ ping_a_impl.set_ping_handler(create_handler(2, 3, 0, 0));
+
+ ping_b_impl.binding().AddFilter(
+ CallbackFilter::Wrap(create_handler(3, 3, 0, 1)));
+ ping_b_impl.binding().AddFilter(
+ CallbackFilter::Wrap(create_handler(3, 3, 1, 2)));
+ ping_b_impl.set_ping_handler(create_handler(3, 3, 2, 3));
+
+ for (int i = 0; i < 10; ++i) {
+ a_status = 0;
+ b_status = 0;
+
+ {
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ EXPECT_EQ(3, a_status);
+ EXPECT_EQ(0, b_status);
+
+ {
+ base::RunLoop loop;
+ ping_b->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ EXPECT_EQ(3, a_status);
+ EXPECT_EQ(3, b_status);
+ }
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTesting) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl0(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> ptr0;
+ ptr0.Bind(std::move(ptr_info));
+ ptr0.set_connection_error_handler(base::Bind(&Fail));
+
+ bool ptr0_callback_run = false;
+ ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(
+ 123, &ptr0_callback_run, base::Bind(&base::DoNothing)));
+ ptr0.FlushForTesting();
+ EXPECT_TRUE(ptr0_callback_run);
+}
+
+void SetBool(bool* value) {
+ *value = true;
+}
+
+template <typename T>
+void SetBoolWithUnusedParameter(bool* value, T unused) {
+ *value = true;
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTestingWithClosedPeer) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ AssociatedInterfacePtr<IntegerSender> ptr0;
+ ptr0.Bind(std::move(ptr_info));
+ bool called = false;
+ ptr0.set_connection_error_handler(base::Bind(&SetBool, &called));
+ request = nullptr;
+
+ ptr0.FlushForTesting();
+ EXPECT_TRUE(called);
+ ptr0.FlushForTesting();
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedBindingFlushForTesting) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl0(std::move(request));
+ impl0.set_connection_error_handler(base::Bind(&Fail));
+ AssociatedInterfacePtr<IntegerSender> ptr0;
+ ptr0.Bind(std::move(ptr_info));
+
+ bool ptr0_callback_run = false;
+ ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(
+ 123, &ptr0_callback_run, base::Bind(&base::DoNothing)));
+ // Because the flush is sent from the binding, it only guarantees that the
+ // request has been received, not the response. The second flush waits for the
+ // response to be received.
+ impl0.binding()->FlushForTesting();
+ impl0.binding()->FlushForTesting();
+ EXPECT_TRUE(ptr0_callback_run);
+}
+
+TEST_F(AssociatedInterfaceTest,
+ AssociatedBindingFlushForTestingWithClosedPeer) {
+ scoped_refptr<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> request;
+ {
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0,
+ &request);
+ }
+
+ IntegerSenderImpl impl(std::move(request));
+ bool called = false;
+ impl.set_connection_error_handler(base::Bind(&SetBool, &called));
+ impl.binding()->FlushForTesting();
+ EXPECT_TRUE(called);
+ impl.binding()->FlushForTesting();
+}
+
+TEST_F(AssociatedInterfaceTest, BindingFlushForTesting) {
+ IntegerSenderConnectionPtr ptr;
+ IntegerSenderConnectionImpl impl(MakeRequest(&ptr));
+ bool called = false;
+ ptr->AsyncGetSender(base::Bind(
+ &SetBoolWithUnusedParameter<IntegerSenderAssociatedPtrInfo>, &called));
+ EXPECT_FALSE(called);
+ impl.binding()->set_connection_error_handler(base::Bind(&Fail));
+ // Because the flush is sent from the binding, it only guarantees that the
+ // request has been received, not the response. The second flush waits for the
+ // response to be received.
+ impl.binding()->FlushForTesting();
+ impl.binding()->FlushForTesting();
+ EXPECT_TRUE(called);
+}
+
+TEST_F(AssociatedInterfaceTest, BindingFlushForTestingWithClosedPeer) {
+ IntegerSenderConnectionPtr ptr;
+ IntegerSenderConnectionImpl impl(MakeRequest(&ptr));
+ bool called = false;
+ impl.binding()->set_connection_error_handler(base::Bind(&SetBool, &called));
+ ptr.reset();
+ EXPECT_FALSE(called);
+ impl.binding()->FlushForTesting();
+ EXPECT_TRUE(called);
+ impl.binding()->FlushForTesting();
+}
+
+TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTesting) {
+ IntegerSenderConnectionPtr ptr;
+ auto binding =
+ MakeStrongBinding(base::MakeUnique<IntegerSenderConnectionImpl>(
+ IntegerSenderConnectionRequest{}),
+ MakeRequest(&ptr));
+ bool called = false;
+ IntegerSenderAssociatedPtr sender_ptr;
+ ptr->GetSender(MakeRequest(&sender_ptr));
+ sender_ptr->Echo(1, base::Bind(&SetBoolWithUnusedParameter<int>, &called));
+ EXPECT_FALSE(called);
+ // Because the flush is sent from the binding, it only guarantees that the
+ // request has been received, not the response. The second flush waits for the
+ // response to be received.
+ ASSERT_TRUE(binding);
+ binding->FlushForTesting();
+ ASSERT_TRUE(binding);
+ binding->FlushForTesting();
+ EXPECT_TRUE(called);
+}
+
+TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTestingWithClosedPeer) {
+ IntegerSenderConnectionPtr ptr;
+ bool called = false;
+ auto binding =
+ MakeStrongBinding(base::MakeUnique<IntegerSenderConnectionImpl>(
+ IntegerSenderConnectionRequest{}),
+ MakeRequest(&ptr));
+ binding->set_connection_error_handler(base::Bind(&SetBool, &called));
+ ptr.reset();
+ EXPECT_FALSE(called);
+ ASSERT_TRUE(binding);
+ binding->FlushForTesting();
+ EXPECT_TRUE(called);
+ ASSERT_FALSE(binding);
+}
+
+TEST_F(AssociatedInterfaceTest, PtrFlushForTesting) {
+ IntegerSenderConnectionPtr ptr;
+ IntegerSenderConnectionImpl impl(MakeRequest(&ptr));
+ bool called = false;
+ ptr.set_connection_error_handler(base::Bind(&Fail));
+ ptr->AsyncGetSender(base::Bind(
+ &SetBoolWithUnusedParameter<IntegerSenderAssociatedPtrInfo>, &called));
+ EXPECT_FALSE(called);
+ ptr.FlushForTesting();
+ EXPECT_TRUE(called);
+}
+
+TEST_F(AssociatedInterfaceTest, PtrFlushForTestingWithClosedPeer) {
+ IntegerSenderConnectionPtr ptr;
+ MakeRequest(&ptr);
+ bool called = false;
+ ptr.set_connection_error_handler(base::Bind(&SetBool, &called));
+ EXPECT_FALSE(called);
+ ptr.FlushForTesting();
+ EXPECT_TRUE(called);
+ ptr.FlushForTesting();
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedBindingConnectionErrorWithReason) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> ptr;
+ ptr.Bind(std::move(ptr_info));
+
+ base::RunLoop run_loop;
+ impl.binding()->set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(123u, custom_reason);
+ EXPECT_EQ("farewell", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ ptr.ResetWithReason(123u, "farewell");
+
+ run_loop.Run();
+}
+
+TEST_F(AssociatedInterfaceTest,
+ PendingAssociatedBindingConnectionErrorWithReason) {
+ // Test that AssociatedBinding is notified with connection error when the
+ // interface hasn't associated with a message pipe and the peer is closed.
+
+ IntegerSenderAssociatedPtr ptr;
+ IntegerSenderImpl impl(MakeRequest(&ptr));
+
+ base::RunLoop run_loop;
+ impl.binding()->set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(123u, custom_reason);
+ EXPECT_EQ("farewell", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ ptr.ResetWithReason(123u, "farewell");
+
+ run_loop.Run();
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedPtrConnectionErrorWithReason) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> ptr;
+ ptr.Bind(std::move(ptr_info));
+
+ base::RunLoop run_loop;
+ ptr.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(456u, custom_reason);
+ EXPECT_EQ("farewell", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ impl.binding()->CloseWithReason(456u, "farewell");
+
+ run_loop.Run();
+}
+
+TEST_F(AssociatedInterfaceTest, PendingAssociatedPtrConnectionErrorWithReason) {
+ // Test that AssociatedInterfacePtr is notified with connection error when the
+ // interface hasn't associated with a message pipe and the peer is closed.
+
+ IntegerSenderAssociatedPtr ptr;
+ auto request = MakeRequest(&ptr);
+
+ base::RunLoop run_loop;
+ ptr.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(456u, custom_reason);
+ EXPECT_EQ("farewell", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ request.ResetWithReason(456u, "farewell");
+
+ run_loop.Run();
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedRequestResetWithReason) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ AssociatedInterfacePtr<IntegerSender> ptr;
+ ptr.Bind(std::move(ptr_info));
+
+ base::RunLoop run_loop;
+ ptr.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(789u, custom_reason);
+ EXPECT_EQ("long time no see", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ request.ResetWithReason(789u, "long time no see");
+
+ run_loop.Run();
+}
+
+TEST_F(AssociatedInterfaceTest, ThreadSafeAssociatedInterfacePtr) {
+ IntegerSenderConnectionPtr connection_ptr;
+ IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+
+ IntegerSenderAssociatedPtr sender;
+ connection_ptr->GetSender(MakeRequest(&sender));
+
+ scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr> thread_safe_sender =
+ ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender));
+
+ {
+ // Test the thread safe pointer can be used from the interface ptr thread.
+ int32_t echoed_value = 0;
+ base::RunLoop run_loop;
+ (*thread_safe_sender)
+ ->Echo(123, base::Bind(&CaptureInt32, &echoed_value,
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ EXPECT_EQ(123, echoed_value);
+ }
+
+ // Test the thread safe pointer can be used from another thread.
+ base::RunLoop run_loop;
+ base::Thread other_thread("service test thread");
+ other_thread.Start();
+
+ auto run_method = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ const scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr>&
+ thread_safe_sender) {
+ auto done_callback = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ base::PlatformThreadId thread_id, int32_t result) {
+ EXPECT_EQ(123, result);
+ // Validate the callback is invoked on the calling thread.
+ EXPECT_EQ(thread_id, base::PlatformThread::CurrentId());
+ // Notify the run_loop to quit.
+ main_task_runner->PostTask(FROM_HERE, quit_closure);
+ });
+ (*thread_safe_sender)
+ ->Echo(123,
+ base::Bind(done_callback, main_task_runner, quit_closure,
+ base::PlatformThread::CurrentId()));
+ },
+ base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
+ thread_safe_sender);
+ other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method);
+
+ // Block until the method callback is called on the background thread.
+ run_loop.Run();
+}
+
+struct ForwarderTestContext {
+ IntegerSenderConnectionPtr connection_ptr;
+ std::unique_ptr<IntegerSenderConnectionImpl> interface_impl;
+ IntegerSenderAssociatedRequest sender_request;
+};
+
+TEST_F(AssociatedInterfaceTest,
+ ThreadSafeAssociatedInterfacePtrWithTaskRunner) {
+ // Start the thread from where we'll bind the interface pointer.
+ base::Thread other_thread("service test thread");
+ other_thread.Start();
+ const scoped_refptr<base::SingleThreadTaskRunner>& other_thread_task_runner =
+ other_thread.message_loop()->task_runner();
+
+ ForwarderTestContext* context = new ForwarderTestContext();
+ IntegerSenderAssociatedPtrInfo sender_info;
+ base::WaitableEvent sender_info_bound_event(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ auto setup = [](base::WaitableEvent* sender_info_bound_event,
+ IntegerSenderAssociatedPtrInfo* sender_info,
+ ForwarderTestContext* context) {
+ context->interface_impl = base::MakeUnique<IntegerSenderConnectionImpl>(
+ MakeRequest(&context->connection_ptr));
+
+ auto sender_request = MakeRequest(sender_info);
+ context->connection_ptr->GetSender(std::move(sender_request));
+
+ // Unblock the main thread as soon as |sender_info| is set.
+ sender_info_bound_event->Signal();
+ };
+ other_thread_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(setup, &sender_info_bound_event, &sender_info, context));
+ sender_info_bound_event.Wait();
+
+ // Create a ThreadSafeAssociatedPtr that binds on the background thread and is
+ // associated with |connection_ptr| there.
+ scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr> thread_safe_ptr =
+ ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender_info),
+ other_thread_task_runner);
+
+ // Issue a call on the thread-safe ptr immediately. Note that this may happen
+ // before the interface is bound on the background thread, and that must be
+ // OK.
+ {
+ auto echo_callback =
+ base::Bind([](const base::Closure& quit_closure, int32_t result) {
+ EXPECT_EQ(123, result);
+ quit_closure.Run();
+ });
+ base::RunLoop run_loop;
+ (*thread_safe_ptr)
+ ->Echo(123, base::Bind(echo_callback, run_loop.QuitClosure()));
+
+ // Block until the method callback is called.
+ run_loop.Run();
+ }
+
+ other_thread_task_runner->DeleteSoon(FROM_HERE, context);
+
+ // Reset the pointer now so the InterfacePtr associated resources can be
+ // deleted before the background thread's message loop is invalidated.
+ thread_safe_ptr = nullptr;
+}
+
+class DiscardingAssociatedPingProviderProvider
+ : public AssociatedPingProviderProvider {
+ public:
+ void GetPingProvider(
+ AssociatedPingProviderAssociatedRequest request) override {}
+};
+
+TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) {
+ DiscardingAssociatedPingProviderProvider ping_provider_provider;
+ mojo::Binding<AssociatedPingProviderProvider> binding(
+ &ping_provider_provider);
+ auto provider_provider = binding.CreateInterfacePtrAndBind();
+ AssociatedPingProviderAssociatedPtr provider;
+ provider_provider->GetPingProvider(mojo::MakeRequest(&provider));
+ PingServiceAssociatedPtr ping;
+ provider->GetPing(mojo::MakeRequest(&ping));
+ base::RunLoop run_loop;
+ ping.set_connection_error_handler(run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+TEST_F(AssociatedInterfaceTest, GetIsolatedInterface) {
+ IntegerSenderAssociatedPtr sender;
+ GetIsolatedInterface(MakeRequest(&sender).PassHandle());
+ sender->Send(42);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
new file mode 100644
index 0000000000..569eb518c6
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
@@ -0,0 +1,395 @@
+// 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 <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class TestTaskRunner : public base::SingleThreadTaskRunner {
+ public:
+ TestTaskRunner()
+ : thread_id_(base::PlatformThread::CurrentRef()),
+ quit_called_(false),
+ task_ready_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+ base::OnceClosure task,
+ base::TimeDelta delay) override {
+ NOTREACHED();
+ return false;
+ }
+
+ bool PostDelayedTask(const tracked_objects::Location& from_here,
+ base::OnceClosure task,
+ base::TimeDelta delay) override {
+ {
+ base::AutoLock locker(lock_);
+ tasks_.push(std::move(task));
+ }
+ task_ready_.Signal();
+ return true;
+ }
+ bool RunsTasksOnCurrentThread() const override {
+ return base::PlatformThread::CurrentRef() == thread_id_;
+ }
+
+ // Only quits when Quit() is called.
+ void Run() {
+ DCHECK(RunsTasksOnCurrentThread());
+ quit_called_ = false;
+
+ while (true) {
+ {
+ base::AutoLock locker(lock_);
+ while (!tasks_.empty()) {
+ auto task = std::move(tasks_.front());
+ tasks_.pop();
+
+ {
+ base::AutoUnlock unlocker(lock_);
+ std::move(task).Run();
+ if (quit_called_)
+ return;
+ }
+ }
+ }
+ task_ready_.Wait();
+ }
+ }
+
+ void Quit() {
+ DCHECK(RunsTasksOnCurrentThread());
+ quit_called_ = true;
+ }
+
+ // Waits until one task is ready and runs it.
+ void RunOneTask() {
+ DCHECK(RunsTasksOnCurrentThread());
+
+ while (true) {
+ {
+ base::AutoLock locker(lock_);
+ if (!tasks_.empty()) {
+ auto task = std::move(tasks_.front());
+ tasks_.pop();
+
+ {
+ base::AutoUnlock unlocker(lock_);
+ std::move(task).Run();
+ return;
+ }
+ }
+ }
+ task_ready_.Wait();
+ }
+ }
+
+ private:
+ ~TestTaskRunner() override {}
+
+ const base::PlatformThreadRef thread_id_;
+ bool quit_called_;
+ base::WaitableEvent task_ready_;
+
+ // Protect |tasks_|.
+ base::Lock lock_;
+ std::queue<base::OnceClosure> tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
+};
+
+template <typename BindingType, typename RequestType>
+class IntegerSenderImpl : public IntegerSender {
+ public:
+ IntegerSenderImpl(RequestType request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner)
+ : binding_(this, std::move(request), std::move(runner)) {}
+
+ ~IntegerSenderImpl() override {}
+
+ using EchoHandler = base::Callback<void(int32_t, const EchoCallback&)>;
+
+ void set_echo_handler(const EchoHandler& handler) { echo_handler_ = handler; }
+
+ void Echo(int32_t value, const EchoCallback& callback) override {
+ if (echo_handler_.is_null())
+ callback.Run(value);
+ else
+ echo_handler_.Run(value, callback);
+ }
+ void Send(int32_t value) override { NOTREACHED(); }
+
+ BindingType* binding() { return &binding_; }
+
+ private:
+ BindingType binding_;
+ EchoHandler echo_handler_;
+};
+
+class IntegerSenderConnectionImpl : public IntegerSenderConnection {
+ public:
+ using SenderType = IntegerSenderImpl<AssociatedBinding<IntegerSender>,
+ IntegerSenderAssociatedRequest>;
+
+ explicit IntegerSenderConnectionImpl(
+ IntegerSenderConnectionRequest request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sender_runner)
+ : binding_(this, std::move(request), std::move(runner)),
+ sender_runner_(std::move(sender_runner)) {}
+
+ ~IntegerSenderConnectionImpl() override {}
+
+ void set_get_sender_notification(const base::Closure& notification) {
+ get_sender_notification_ = notification;
+ }
+ void GetSender(IntegerSenderAssociatedRequest sender) override {
+ sender_impl_.reset(new SenderType(std::move(sender), sender_runner_));
+ get_sender_notification_.Run();
+ }
+
+ void AsyncGetSender(const AsyncGetSenderCallback& callback) override {
+ NOTREACHED();
+ }
+
+ Binding<IntegerSenderConnection>* binding() { return &binding_; }
+
+ SenderType* sender_impl() { return sender_impl_.get(); }
+
+ private:
+ Binding<IntegerSenderConnection> binding_;
+ std::unique_ptr<SenderType> sender_impl_;
+ scoped_refptr<base::SingleThreadTaskRunner> sender_runner_;
+ base::Closure get_sender_notification_;
+};
+
+class BindTaskRunnerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ binding_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ ptr_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+
+ auto request = MakeRequest(&ptr_, ptr_task_runner_);
+ impl_.reset(new ImplType(std::move(request), binding_task_runner_));
+ }
+
+ base::MessageLoop loop_;
+ scoped_refptr<TestTaskRunner> binding_task_runner_;
+ scoped_refptr<TestTaskRunner> ptr_task_runner_;
+
+ IntegerSenderPtr ptr_;
+ using ImplType =
+ IntegerSenderImpl<Binding<IntegerSender>, IntegerSenderRequest>;
+ std::unique_ptr<ImplType> impl_;
+};
+
+class AssociatedBindTaskRunnerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ connection_binding_task_runner_ =
+ scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ connection_ptr_task_runner_ =
+ scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ sender_binding_task_runner_ =
+ scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ sender_ptr_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+
+ auto connection_request =
+ MakeRequest(&connection_ptr_, connection_ptr_task_runner_);
+ connection_impl_.reset(new IntegerSenderConnectionImpl(
+ std::move(connection_request), connection_binding_task_runner_,
+ sender_binding_task_runner_));
+
+ connection_impl_->set_get_sender_notification(
+ base::Bind(&AssociatedBindTaskRunnerTest::QuitTaskRunner,
+ base::Unretained(this)));
+
+ connection_ptr_->GetSender(
+ MakeRequest(&sender_ptr_, sender_ptr_task_runner_));
+ connection_binding_task_runner_->Run();
+ }
+
+ void QuitTaskRunner() {
+ connection_binding_task_runner_->Quit();
+ }
+
+ base::MessageLoop loop_;
+ scoped_refptr<TestTaskRunner> connection_binding_task_runner_;
+ scoped_refptr<TestTaskRunner> connection_ptr_task_runner_;
+ scoped_refptr<TestTaskRunner> sender_binding_task_runner_;
+ scoped_refptr<TestTaskRunner> sender_ptr_task_runner_;
+
+ IntegerSenderConnectionPtr connection_ptr_;
+ std::unique_ptr<IntegerSenderConnectionImpl> connection_impl_;
+ IntegerSenderAssociatedPtr sender_ptr_;
+};
+
+void DoSetFlagAndQuitTaskRunner(bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner) {
+ *flag = true;
+ if (task_runner)
+ task_runner->Quit();
+}
+
+void DoExpectValueSetFlagAndQuitTaskRunner(
+ int32_t expected_value,
+ bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner,
+ int32_t value) {
+ EXPECT_EQ(expected_value, value);
+ DoSetFlagAndQuitTaskRunner(flag, task_runner);
+}
+
+void DoExpectValueSetFlagForwardValueAndQuitTaskRunner(
+ int32_t expected_value,
+ bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner,
+ int32_t value,
+ const IntegerSender::EchoCallback& callback) {
+ EXPECT_EQ(expected_value, value);
+ *flag = true;
+ callback.Run(value);
+ task_runner->Quit();
+}
+
+base::Closure SetFlagAndQuitTaskRunner(
+ bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner) {
+ return base::Bind(&DoSetFlagAndQuitTaskRunner, flag, task_runner);
+}
+
+base::Callback<void(int32_t)> ExpectValueSetFlagAndQuitTaskRunner(
+ int32_t expected_value,
+ bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner) {
+ return base::Bind(&DoExpectValueSetFlagAndQuitTaskRunner, expected_value,
+ flag, task_runner);
+}
+
+TEST_F(BindTaskRunnerTest, MethodCall) {
+ bool echo_called = false;
+ impl_->set_echo_handler(
+ base::Bind(&DoExpectValueSetFlagForwardValueAndQuitTaskRunner,
+ 1024, &echo_called, binding_task_runner_));
+ bool echo_replied = false;
+ ptr_->Echo(1024, ExpectValueSetFlagAndQuitTaskRunner(1024, &echo_replied,
+ ptr_task_runner_));
+ binding_task_runner_->Run();
+ EXPECT_TRUE(echo_called);
+ ptr_task_runner_->Run();
+ EXPECT_TRUE(echo_replied);
+}
+
+TEST_F(BindTaskRunnerTest, BindingConnectionError) {
+ bool connection_error_called = false;
+ impl_->binding()->set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&connection_error_called, binding_task_runner_));
+ ptr_.reset();
+ binding_task_runner_->Run();
+ EXPECT_TRUE(connection_error_called);
+}
+
+TEST_F(BindTaskRunnerTest, PtrConnectionError) {
+ bool connection_error_called = false;
+ ptr_.set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&connection_error_called, ptr_task_runner_));
+ impl_->binding()->Close();
+ ptr_task_runner_->Run();
+ EXPECT_TRUE(connection_error_called);
+}
+
+void ExpectValueSetFlagAndForward(int32_t expected_value,
+ bool* flag,
+ int32_t value,
+ const IntegerSender::EchoCallback& callback) {
+ EXPECT_EQ(expected_value, value);
+ *flag = true;
+ callback.Run(value);
+}
+
+TEST_F(AssociatedBindTaskRunnerTest, MethodCall) {
+ bool echo_called = false;
+ connection_impl_->sender_impl()->set_echo_handler(
+ base::Bind(&ExpectValueSetFlagAndForward, 1024, &echo_called));
+
+ bool echo_replied = false;
+ sender_ptr_->Echo(
+ 1024, ExpectValueSetFlagAndQuitTaskRunner(1024, &echo_replied, nullptr));
+
+ // The Echo request first arrives at the master endpoint's task runner, and
+ // then is forwarded to the associated endpoint's task runner.
+ connection_binding_task_runner_->RunOneTask();
+ sender_binding_task_runner_->RunOneTask();
+ EXPECT_TRUE(echo_called);
+
+ // Similarly, the Echo response arrives at the master endpoint's task runner
+ // and then is forwarded to the associated endpoint's task runner.
+ connection_ptr_task_runner_->RunOneTask();
+ sender_ptr_task_runner_->RunOneTask();
+ EXPECT_TRUE(echo_replied);
+}
+
+TEST_F(AssociatedBindTaskRunnerTest, BindingConnectionError) {
+ bool sender_impl_error = false;
+ connection_impl_->sender_impl()->binding()->set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&sender_impl_error,
+ sender_binding_task_runner_));
+ bool connection_impl_error = false;
+ connection_impl_->binding()->set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&connection_impl_error,
+ connection_binding_task_runner_));
+ bool sender_ptr_error = false;
+ sender_ptr_.set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&sender_ptr_error, sender_ptr_task_runner_));
+ connection_ptr_.reset();
+ sender_ptr_task_runner_->Run();
+ EXPECT_TRUE(sender_ptr_error);
+ connection_binding_task_runner_->Run();
+ EXPECT_TRUE(connection_impl_error);
+ sender_binding_task_runner_->Run();
+ EXPECT_TRUE(sender_impl_error);
+}
+
+TEST_F(AssociatedBindTaskRunnerTest, PtrConnectionError) {
+ bool sender_impl_error = false;
+ connection_impl_->sender_impl()->binding()->set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&sender_impl_error,
+ sender_binding_task_runner_));
+ bool connection_ptr_error = false;
+ connection_ptr_.set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&connection_ptr_error,
+ connection_ptr_task_runner_));
+ bool sender_ptr_error = false;
+ sender_ptr_.set_connection_error_handler(
+ SetFlagAndQuitTaskRunner(&sender_ptr_error, sender_ptr_task_runner_));
+ connection_impl_->binding()->Close();
+ sender_binding_task_runner_->Run();
+ EXPECT_TRUE(sender_impl_error);
+ connection_ptr_task_runner_->Run();
+ EXPECT_TRUE(connection_ptr_error);
+ sender_ptr_task_runner_->Run();
+ EXPECT_TRUE(sender_ptr_error);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc
new file mode 100644
index 0000000000..43122ceb4f
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright 2015 The Chromium Authors. 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/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/gtest_util.h"
+#include "build/build_config.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// The tests in this file are designed to test the interaction between a
+// Callback and its associated Binding. If a Callback is deleted before
+// being used we DCHECK fail--unless the associated Binding has already
+// been closed or deleted. This contract must be explained to the Mojo
+// application developer. For example it is the developer's responsibility to
+// ensure that the Binding is destroyed before an unused Callback is destroyed.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+namespace mojo {
+namespace test {
+namespace {
+
+void SaveValue(int32_t* storage, const base::Closure& closure, int32_t value) {
+ *storage = value;
+ if (!closure.is_null())
+ closure.Run();
+}
+
+base::Callback<void(int32_t)> BindValueSaver(int32_t* last_value_seen,
+ const base::Closure& closure) {
+ return base::Bind(&SaveValue, last_value_seen, closure);
+}
+
+// An implementation of sample::Provider used on the server side.
+// It only implements one of the methods: EchoInt().
+// All it does is save the values and Callbacks it sees.
+class InterfaceImpl : public sample::Provider {
+ public:
+ InterfaceImpl()
+ : last_server_value_seen_(0),
+ callback_saved_(new EchoIntCallback) {}
+
+ ~InterfaceImpl() override {
+ if (callback_saved_) {
+ delete callback_saved_;
+ }
+ }
+
+ // Run's the callback previously saved from the last invocation
+ // of |EchoInt()|.
+ bool RunCallback() {
+ if (callback_saved_) {
+ callback_saved_->Run(last_server_value_seen_);
+ return true;
+ }
+ return false;
+ }
+
+ // Delete's the previously saved callback.
+ void DeleteCallback() {
+ delete callback_saved_;
+ callback_saved_ = nullptr;
+ }
+
+ // sample::Provider implementation
+
+ // Saves its two input values in member variables and does nothing else.
+ void EchoInt(int32_t x, const EchoIntCallback& callback) override {
+ last_server_value_seen_ = x;
+ *callback_saved_ = callback;
+ if (!closure_.is_null()) {
+ closure_.Run();
+ closure_.Reset();
+ }
+ }
+
+ void EchoString(const std::string& a,
+ const EchoStringCallback& callback) override {
+ CHECK(false) << "Not implemented.";
+ }
+
+ void EchoStrings(const std::string& a,
+ const std::string& b,
+ const EchoStringsCallback& callback) override {
+ CHECK(false) << "Not implemented.";
+ }
+
+ void EchoMessagePipeHandle(
+ ScopedMessagePipeHandle a,
+ const EchoMessagePipeHandleCallback& callback) override {
+ CHECK(false) << "Not implemented.";
+ }
+
+ void EchoEnum(sample::Enum a, const EchoEnumCallback& callback) override {
+ CHECK(false) << "Not implemented.";
+ }
+
+ void resetLastServerValueSeen() { last_server_value_seen_ = 0; }
+
+ int32_t last_server_value_seen() const { return last_server_value_seen_; }
+
+ void set_closure(const base::Closure& closure) { closure_ = closure; }
+
+ private:
+ int32_t last_server_value_seen_;
+ EchoIntCallback* callback_saved_;
+ base::Closure closure_;
+};
+
+class BindingCallbackTest : public testing::Test {
+ public:
+ BindingCallbackTest() {}
+ ~BindingCallbackTest() override {}
+
+ protected:
+ int32_t last_client_callback_value_seen_;
+ sample::ProviderPtr interface_ptr_;
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ private:
+ base::MessageLoop loop_;
+};
+
+// Tests that the InterfacePtr and the Binding can communicate with each
+// other normally.
+TEST_F(BindingCallbackTest, Basic) {
+ // Create the ServerImpl and the Binding.
+ InterfaceImpl server_impl;
+ Binding<sample::Provider> binding(&server_impl, MakeRequest(&interface_ptr_));
+
+ // Initialize the test values.
+ server_impl.resetLastServerValueSeen();
+ last_client_callback_value_seen_ = 0;
+
+ // Invoke the Echo method.
+ base::RunLoop run_loop, run_loop2;
+ server_impl.set_closure(run_loop.QuitClosure());
+ interface_ptr_->EchoInt(
+ 7,
+ BindValueSaver(&last_client_callback_value_seen_,
+ run_loop2.QuitClosure()));
+ run_loop.Run();
+
+ // Check that server saw the correct value, but the client has not yet.
+ EXPECT_EQ(7, server_impl.last_server_value_seen());
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ // Now run the Callback.
+ server_impl.RunCallback();
+ run_loop2.Run();
+
+ // Check that the client has now seen the correct value.
+ EXPECT_EQ(7, last_client_callback_value_seen_);
+
+ // Initialize the test values again.
+ server_impl.resetLastServerValueSeen();
+ last_client_callback_value_seen_ = 0;
+
+ // Invoke the Echo method again.
+ base::RunLoop run_loop3, run_loop4;
+ server_impl.set_closure(run_loop3.QuitClosure());
+ interface_ptr_->EchoInt(
+ 13,
+ BindValueSaver(&last_client_callback_value_seen_,
+ run_loop4.QuitClosure()));
+ run_loop3.Run();
+
+ // Check that server saw the correct value, but the client has not yet.
+ EXPECT_EQ(13, server_impl.last_server_value_seen());
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ // Now run the Callback again.
+ server_impl.RunCallback();
+ run_loop4.Run();
+
+ // Check that the client has now seen the correct value again.
+ EXPECT_EQ(13, last_client_callback_value_seen_);
+}
+
+// Tests that running the Callback after the Binding has been deleted
+// results in a clean failure.
+TEST_F(BindingCallbackTest, DeleteBindingThenRunCallback) {
+ // Create the ServerImpl.
+ InterfaceImpl server_impl;
+ base::RunLoop run_loop;
+ {
+ // Create the binding in an inner scope so it can be deleted first.
+ Binding<sample::Provider> binding(&server_impl,
+ MakeRequest(&interface_ptr_));
+ interface_ptr_.set_connection_error_handler(run_loop.QuitClosure());
+
+ // Initialize the test values.
+ server_impl.resetLastServerValueSeen();
+ last_client_callback_value_seen_ = 0;
+
+ // Invoke the Echo method.
+ base::RunLoop run_loop2;
+ server_impl.set_closure(run_loop2.QuitClosure());
+ interface_ptr_->EchoInt(
+ 7,
+ BindValueSaver(&last_client_callback_value_seen_, base::Closure()));
+ run_loop2.Run();
+ }
+ // The binding has now been destroyed and the pipe is closed.
+
+ // Check that server saw the correct value, but the client has not yet.
+ EXPECT_EQ(7, server_impl.last_server_value_seen());
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ // Now try to run the Callback. This should do nothing since the pipe
+ // is closed.
+ EXPECT_TRUE(server_impl.RunCallback());
+ PumpMessages();
+
+ // Check that the client has still not seen the correct value.
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ // Attempt to invoke the method again and confirm that an error was
+ // encountered.
+ interface_ptr_->EchoInt(
+ 13,
+ BindValueSaver(&last_client_callback_value_seen_, base::Closure()));
+ run_loop.Run();
+ EXPECT_TRUE(interface_ptr_.encountered_error());
+}
+
+// Tests that deleting a Callback without running it after the corresponding
+// binding has already been deleted does not result in a crash.
+TEST_F(BindingCallbackTest, DeleteBindingThenDeleteCallback) {
+ // Create the ServerImpl.
+ InterfaceImpl server_impl;
+ {
+ // Create the binding in an inner scope so it can be deleted first.
+ Binding<sample::Provider> binding(&server_impl,
+ MakeRequest(&interface_ptr_));
+
+ // Initialize the test values.
+ server_impl.resetLastServerValueSeen();
+ last_client_callback_value_seen_ = 0;
+
+ // Invoke the Echo method.
+ base::RunLoop run_loop;
+ server_impl.set_closure(run_loop.QuitClosure());
+ interface_ptr_->EchoInt(
+ 7,
+ BindValueSaver(&last_client_callback_value_seen_, base::Closure()));
+ run_loop.Run();
+ }
+ // The binding has now been destroyed and the pipe is closed.
+
+ // Check that server saw the correct value, but the client has not yet.
+ EXPECT_EQ(7, server_impl.last_server_value_seen());
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ // Delete the callback without running it. This should not
+ // cause a problem because the insfrastructure can detect that the
+ // binding has already been destroyed and the pipe is closed.
+ server_impl.DeleteCallback();
+}
+
+// Tests that closing a Binding allows us to delete a callback
+// without running it without encountering a crash.
+TEST_F(BindingCallbackTest, CloseBindingBeforeDeletingCallback) {
+ // Create the ServerImpl and the Binding.
+ InterfaceImpl server_impl;
+ Binding<sample::Provider> binding(&server_impl, MakeRequest(&interface_ptr_));
+
+ // Initialize the test values.
+ server_impl.resetLastServerValueSeen();
+ last_client_callback_value_seen_ = 0;
+
+ // Invoke the Echo method.
+ base::RunLoop run_loop;
+ server_impl.set_closure(run_loop.QuitClosure());
+ interface_ptr_->EchoInt(
+ 7,
+ BindValueSaver(&last_client_callback_value_seen_, base::Closure()));
+ run_loop.Run();
+
+ // Check that server saw the correct value, but the client has not yet.
+ EXPECT_EQ(7, server_impl.last_server_value_seen());
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ // Now close the Binding.
+ binding.Close();
+
+ // Delete the callback without running it. This should not
+ // cause a crash because the insfrastructure can detect that the
+ // binding has already been closed.
+ server_impl.DeleteCallback();
+
+ // Check that the client has still not seen the correct value.
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+}
+
+// Tests that deleting a Callback without using it before the
+// Binding has been destroyed or closed results in a DCHECK.
+TEST_F(BindingCallbackTest, DeleteCallbackBeforeBindingDeathTest) {
+ // Create the ServerImpl and the Binding.
+ InterfaceImpl server_impl;
+ Binding<sample::Provider> binding(&server_impl, MakeRequest(&interface_ptr_));
+
+ // Initialize the test values.
+ server_impl.resetLastServerValueSeen();
+ last_client_callback_value_seen_ = 0;
+
+ // Invoke the Echo method.
+ base::RunLoop run_loop;
+ server_impl.set_closure(run_loop.QuitClosure());
+ interface_ptr_->EchoInt(
+ 7,
+ BindValueSaver(&last_client_callback_value_seen_, base::Closure()));
+ run_loop.Run();
+
+ // Check that server saw the correct value, but the client has not yet.
+ EXPECT_EQ(7, server_impl.last_server_value_seen());
+ EXPECT_EQ(0, last_client_callback_value_seen_);
+
+ EXPECT_DCHECK_DEATH(server_impl.DeleteCallback());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/binding_set_unittest.cc b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc
new file mode 100644
index 0000000000..07acfbebe0
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc
@@ -0,0 +1,416 @@
+// 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 <memory>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/strong_binding_set.h"
+#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class BindingSetTest : public testing::Test {
+ public:
+ BindingSetTest() {}
+ ~BindingSetTest() override {}
+
+ base::MessageLoop& loop() { return loop_; }
+
+ private:
+ base::MessageLoop loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(BindingSetTest);
+};
+
+template <typename BindingSetType, typename ContextType>
+void ExpectContextHelper(BindingSetType* binding_set,
+ ContextType expected_context) {
+ EXPECT_EQ(expected_context, binding_set->dispatch_context());
+}
+
+template <typename BindingSetType, typename ContextType>
+base::Closure ExpectContext(BindingSetType* binding_set,
+ ContextType expected_context) {
+ return base::Bind(
+ &ExpectContextHelper<BindingSetType, ContextType>, binding_set,
+ expected_context);
+}
+
+base::Closure Sequence(const base::Closure& first,
+ const base::Closure& second) {
+ return base::Bind(
+ [] (const base::Closure& first, const base::Closure& second) {
+ first.Run();
+ second.Run();
+ }, first, second);
+}
+
+class PingImpl : public PingService {
+ public:
+ PingImpl() {}
+ ~PingImpl() override {}
+
+ void set_ping_handler(const base::Closure& handler) {
+ ping_handler_ = handler;
+ }
+
+ private:
+ // PingService:
+ void Ping(const PingCallback& callback) override {
+ if (!ping_handler_.is_null())
+ ping_handler_.Run();
+ callback.Run();
+ }
+
+ base::Closure ping_handler_;
+};
+
+TEST_F(BindingSetTest, BindingSetContext) {
+ PingImpl impl;
+
+ BindingSet<PingService, int> bindings;
+ PingServicePtr ping_a, ping_b;
+ bindings.AddBinding(&impl, MakeRequest(&ping_a), 1);
+ bindings.AddBinding(&impl, MakeRequest(&ping_b), 2);
+
+ {
+ impl.set_ping_handler(ExpectContext(&bindings, 1));
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ impl.set_ping_handler(ExpectContext(&bindings, 2));
+ base::RunLoop loop;
+ ping_b->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectContext(&bindings, 1), loop.QuitClosure()));
+ ping_a.reset();
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectContext(&bindings, 2), loop.QuitClosure()));
+ ping_b.reset();
+ loop.Run();
+ }
+
+ EXPECT_TRUE(bindings.empty());
+}
+
+TEST_F(BindingSetTest, BindingSetConnectionErrorWithReason) {
+ PingImpl impl;
+ PingServicePtr ptr;
+ BindingSet<PingService> bindings;
+ bindings.AddBinding(&impl, MakeRequest(&ptr));
+
+ base::RunLoop run_loop;
+ bindings.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(1024u, custom_reason);
+ EXPECT_EQ("bye", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ ptr.ResetWithReason(1024u, "bye");
+}
+
+class PingProviderImpl : public AssociatedPingProvider, public PingService {
+ public:
+ PingProviderImpl() {}
+ ~PingProviderImpl() override {}
+
+ void set_new_ping_context(int context) { new_ping_context_ = context; }
+
+ void set_new_ping_handler(const base::Closure& handler) {
+ new_ping_handler_ = handler;
+ }
+
+ void set_ping_handler(const base::Closure& handler) {
+ ping_handler_ = handler;
+ }
+
+ AssociatedBindingSet<PingService, int>& ping_bindings() {
+ return ping_bindings_;
+ }
+
+ private:
+ // AssociatedPingProvider:
+ void GetPing(PingServiceAssociatedRequest request) override {
+ ping_bindings_.AddBinding(this, std::move(request), new_ping_context_);
+ if (!new_ping_handler_.is_null())
+ new_ping_handler_.Run();
+ }
+
+ // PingService:
+ void Ping(const PingCallback& callback) override {
+ if (!ping_handler_.is_null())
+ ping_handler_.Run();
+ callback.Run();
+ }
+
+ AssociatedBindingSet<PingService, int> ping_bindings_;
+ int new_ping_context_ = -1;
+ base::Closure ping_handler_;
+ base::Closure new_ping_handler_;
+};
+
+TEST_F(BindingSetTest, AssociatedBindingSetContext) {
+ AssociatedPingProviderPtr provider;
+ PingProviderImpl impl;
+ Binding<AssociatedPingProvider> binding(&impl, MakeRequest(&provider));
+
+ PingServiceAssociatedPtr ping_a;
+ {
+ base::RunLoop loop;
+ impl.set_new_ping_context(1);
+ impl.set_new_ping_handler(loop.QuitClosure());
+ provider->GetPing(MakeRequest(&ping_a));
+ loop.Run();
+ }
+
+ PingServiceAssociatedPtr ping_b;
+ {
+ base::RunLoop loop;
+ impl.set_new_ping_context(2);
+ impl.set_new_ping_handler(loop.QuitClosure());
+ provider->GetPing(MakeRequest(&ping_b));
+ loop.Run();
+ }
+
+ {
+ impl.set_ping_handler(ExpectContext(&impl.ping_bindings(), 1));
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ impl.set_ping_handler(ExpectContext(&impl.ping_bindings(), 2));
+ base::RunLoop loop;
+ ping_b->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ impl.ping_bindings().set_connection_error_handler(
+ Sequence(ExpectContext(&impl.ping_bindings(), 1), loop.QuitClosure()));
+ ping_a.reset();
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ impl.ping_bindings().set_connection_error_handler(
+ Sequence(ExpectContext(&impl.ping_bindings(), 2), loop.QuitClosure()));
+ ping_b.reset();
+ loop.Run();
+ }
+
+ EXPECT_TRUE(impl.ping_bindings().empty());
+}
+
+TEST_F(BindingSetTest, MasterInterfaceBindingSetContext) {
+ AssociatedPingProviderPtr provider_a, provider_b;
+ PingProviderImpl impl;
+ BindingSet<AssociatedPingProvider, int> bindings;
+
+ bindings.AddBinding(&impl, MakeRequest(&provider_a), 1);
+ bindings.AddBinding(&impl, MakeRequest(&provider_b), 2);
+
+ {
+ PingServiceAssociatedPtr ping;
+ base::RunLoop loop;
+ impl.set_new_ping_handler(
+ Sequence(ExpectContext(&bindings, 1), loop.QuitClosure()));
+ provider_a->GetPing(MakeRequest(&ping));
+ loop.Run();
+ }
+
+ {
+ PingServiceAssociatedPtr ping;
+ base::RunLoop loop;
+ impl.set_new_ping_handler(
+ Sequence(ExpectContext(&bindings, 2), loop.QuitClosure()));
+ provider_b->GetPing(MakeRequest(&ping));
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectContext(&bindings, 1), loop.QuitClosure()));
+ provider_a.reset();
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectContext(&bindings, 2), loop.QuitClosure()));
+ provider_b.reset();
+ loop.Run();
+ }
+
+ EXPECT_TRUE(bindings.empty());
+}
+
+TEST_F(BindingSetTest, PreDispatchHandler) {
+ PingImpl impl;
+
+ BindingSet<PingService, int> bindings;
+ PingServicePtr ping_a, ping_b;
+ bindings.AddBinding(&impl, MakeRequest(&ping_a), 1);
+ bindings.AddBinding(&impl, MakeRequest(&ping_b), 2);
+
+ {
+ bindings.set_pre_dispatch_handler(base::Bind([] (const int& context) {
+ EXPECT_EQ(1, context);
+ }));
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ bindings.set_pre_dispatch_handler(base::Bind([] (const int& context) {
+ EXPECT_EQ(2, context);
+ }));
+ base::RunLoop loop;
+ ping_b->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_pre_dispatch_handler(
+ base::Bind([](base::RunLoop* loop, const int& context) {
+ EXPECT_EQ(1, context);
+ loop->Quit();
+ }, &loop));
+ ping_a.reset();
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_pre_dispatch_handler(
+ base::Bind([](base::RunLoop* loop, const int& context) {
+ EXPECT_EQ(2, context);
+ loop->Quit();
+ }, &loop));
+ ping_b.reset();
+ loop.Run();
+ }
+
+ EXPECT_TRUE(bindings.empty());
+}
+
+TEST_F(BindingSetTest, AssociatedBindingSetConnectionErrorWithReason) {
+ AssociatedPingProviderPtr master_ptr;
+ PingProviderImpl master_impl;
+ Binding<AssociatedPingProvider> master_binding(&master_impl, &master_ptr);
+
+ base::RunLoop run_loop;
+ master_impl.ping_bindings().set_connection_error_with_reason_handler(
+ base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(2048u, custom_reason);
+ EXPECT_EQ("bye", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ PingServiceAssociatedPtr ptr;
+ master_ptr->GetPing(MakeRequest(&ptr));
+
+ ptr.ResetWithReason(2048u, "bye");
+
+ run_loop.Run();
+}
+
+class PingInstanceCounter : public PingService {
+ public:
+ PingInstanceCounter() { ++instance_count; }
+ ~PingInstanceCounter() override { --instance_count; }
+
+ void Ping(const PingCallback& callback) override {}
+
+ static int instance_count;
+};
+int PingInstanceCounter::instance_count = 0;
+
+TEST_F(BindingSetTest, StrongBinding_Destructor) {
+ PingServicePtr ping_a, ping_b;
+ auto bindings = base::MakeUnique<StrongBindingSet<PingService>>();
+
+ bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ mojo::MakeRequest(&ping_a));
+ EXPECT_EQ(1, PingInstanceCounter::instance_count);
+
+ bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ mojo::MakeRequest(&ping_b));
+ EXPECT_EQ(2, PingInstanceCounter::instance_count);
+
+ bindings.reset();
+ EXPECT_EQ(0, PingInstanceCounter::instance_count);
+}
+
+TEST_F(BindingSetTest, StrongBinding_ConnectionError) {
+ PingServicePtr ping_a, ping_b;
+ StrongBindingSet<PingService> bindings;
+ bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ mojo::MakeRequest(&ping_a));
+ bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ mojo::MakeRequest(&ping_b));
+ EXPECT_EQ(2, PingInstanceCounter::instance_count);
+
+ ping_a.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, PingInstanceCounter::instance_count);
+
+ ping_b.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, PingInstanceCounter::instance_count);
+}
+
+TEST_F(BindingSetTest, StrongBinding_RemoveBinding) {
+ PingServicePtr ping_a, ping_b;
+ StrongBindingSet<PingService> bindings;
+ BindingId binding_id_a = bindings.AddBinding(
+ base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_a));
+ BindingId binding_id_b = bindings.AddBinding(
+ base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_b));
+ EXPECT_EQ(2, PingInstanceCounter::instance_count);
+
+ EXPECT_TRUE(bindings.RemoveBinding(binding_id_a));
+ EXPECT_EQ(1, PingInstanceCounter::instance_count);
+
+ EXPECT_TRUE(bindings.RemoveBinding(binding_id_b));
+ EXPECT_EQ(0, PingInstanceCounter::instance_count);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/binding_unittest.cc b/mojo/public/cpp/bindings/tests/binding_unittest.cc
new file mode 100644
index 0000000000..e76993bb68
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/binding_unittest.cc
@@ -0,0 +1,611 @@
+// Copyright 2015 The Chromium 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: This file tests both binding.h (mojo::Binding) and strong_binding.h
+// (mojo::StrongBinding).
+
+#include "mojo/public/cpp/bindings/binding.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class BindingTestBase : public testing::Test {
+ public:
+ BindingTestBase() {}
+ ~BindingTestBase() override {}
+
+ base::MessageLoop& loop() { return loop_; }
+
+ private:
+ base::MessageLoop loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(BindingTestBase);
+};
+
+class ServiceImpl : public sample::Service {
+ public:
+ explicit ServiceImpl(bool* was_deleted = nullptr)
+ : was_deleted_(was_deleted) {}
+ ~ServiceImpl() override {
+ if (was_deleted_)
+ *was_deleted_ = true;
+ }
+
+ private:
+ // sample::Service implementation
+ void Frobinate(sample::FooPtr foo,
+ BazOptions options,
+ sample::PortPtr port,
+ const FrobinateCallback& callback) override {
+ callback.Run(1);
+ }
+ void GetPort(InterfaceRequest<sample::Port> port) override {}
+
+ bool* const was_deleted_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
+};
+
+template <typename... Args>
+void DoSetFlagAndRunClosure(bool* flag,
+ const base::Closure& closure,
+ Args... args) {
+ *flag = true;
+ if (!closure.is_null())
+ closure.Run();
+}
+
+template <typename... Args>
+base::Callback<void(Args...)> SetFlagAndRunClosure(
+ bool* flag,
+ const base::Closure& callback = base::Closure()) {
+ return base::Bind(&DoSetFlagAndRunClosure<Args...>, flag, callback);
+}
+
+// BindingTest -----------------------------------------------------------------
+
+using BindingTest = BindingTestBase;
+
+TEST_F(BindingTest, Close) {
+ bool called = false;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ base::RunLoop run_loop;
+ ptr.set_connection_error_handler(
+ SetFlagAndRunClosure(&called, run_loop.QuitClosure()));
+ ServiceImpl impl;
+ Binding<sample::Service> binding(&impl, std::move(request));
+
+ binding.Close();
+ EXPECT_FALSE(called);
+ run_loop.Run();
+ EXPECT_TRUE(called);
+}
+
+// Tests that destroying a mojo::Binding closes the bound message pipe handle.
+TEST_F(BindingTest, DestroyClosesMessagePipe) {
+ bool encountered_error = false;
+ ServiceImpl impl;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ base::RunLoop run_loop;
+ ptr.set_connection_error_handler(
+ SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure()));
+ bool called = false;
+ base::RunLoop run_loop2;
+ {
+ Binding<sample::Service> binding(&impl, std::move(request));
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called,
+ run_loop2.QuitClosure()));
+ run_loop2.Run();
+ EXPECT_TRUE(called);
+ EXPECT_FALSE(encountered_error);
+ }
+ // Now that the Binding is out of scope we should detect an error on the other
+ // end of the pipe.
+ run_loop.Run();
+ EXPECT_TRUE(encountered_error);
+
+ // And calls should fail.
+ called = false;
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called,
+ run_loop2.QuitClosure()));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(called);
+}
+
+// Tests that the binding's connection error handler gets called when the other
+// end is closed.
+TEST_F(BindingTest, ConnectionError) {
+ bool called = false;
+ {
+ ServiceImpl impl;
+ sample::ServicePtr ptr;
+ Binding<sample::Service> binding(&impl, MakeRequest(&ptr));
+ base::RunLoop run_loop;
+ binding.set_connection_error_handler(
+ SetFlagAndRunClosure(&called, run_loop.QuitClosure()));
+ ptr.reset();
+ EXPECT_FALSE(called);
+ run_loop.Run();
+ EXPECT_TRUE(called);
+ // We want to make sure that it isn't called again during destruction.
+ called = false;
+ }
+ EXPECT_FALSE(called);
+}
+
+// Tests that calling Close doesn't result in the connection error handler being
+// called.
+TEST_F(BindingTest, CloseDoesntCallConnectionErrorHandler) {
+ ServiceImpl impl;
+ sample::ServicePtr ptr;
+ Binding<sample::Service> binding(&impl, MakeRequest(&ptr));
+ bool called = false;
+ binding.set_connection_error_handler(SetFlagAndRunClosure(&called));
+ binding.Close();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(called);
+
+ // We can also close the other end, and the error handler still won't be
+ // called.
+ ptr.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(called);
+}
+
+class ServiceImplWithBinding : public ServiceImpl {
+ public:
+ ServiceImplWithBinding(bool* was_deleted,
+ const base::Closure& closure,
+ InterfaceRequest<sample::Service> request)
+ : ServiceImpl(was_deleted),
+ binding_(this, std::move(request)),
+ closure_(closure) {
+ binding_.set_connection_error_handler(
+ base::Bind(&ServiceImplWithBinding::OnConnectionError,
+ base::Unretained(this)));
+ }
+
+ private:
+ ~ServiceImplWithBinding() override{
+ closure_.Run();
+ }
+
+ void OnConnectionError() { delete this; }
+
+ Binding<sample::Service> binding_;
+ base::Closure closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceImplWithBinding);
+};
+
+// Tests that the binding may be deleted in the connection error handler.
+TEST_F(BindingTest, SelfDeleteOnConnectionError) {
+ bool was_deleted = false;
+ sample::ServicePtr ptr;
+ // This should delete itself on connection error.
+ base::RunLoop run_loop;
+ new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(),
+ MakeRequest(&ptr));
+ ptr.reset();
+ EXPECT_FALSE(was_deleted);
+ run_loop.Run();
+ EXPECT_TRUE(was_deleted);
+}
+
+// Tests that explicitly calling Unbind followed by rebinding works.
+TEST_F(BindingTest, Unbind) {
+ ServiceImpl impl;
+ sample::ServicePtr ptr;
+ Binding<sample::Service> binding(&impl, MakeRequest(&ptr));
+
+ bool called = false;
+ base::RunLoop run_loop;
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called,
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ EXPECT_TRUE(called);
+
+ called = false;
+ auto request = binding.Unbind();
+ EXPECT_FALSE(binding.is_bound());
+ // All calls should fail when not bound...
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called,
+ run_loop.QuitClosure()));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(called);
+
+ called = false;
+ binding.Bind(std::move(request));
+ EXPECT_TRUE(binding.is_bound());
+ // ...and should succeed again when the rebound.
+ base::RunLoop run_loop2;
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called,
+ run_loop2.QuitClosure()));
+ run_loop2.Run();
+ EXPECT_TRUE(called);
+}
+
+class IntegerAccessorImpl : public sample::IntegerAccessor {
+ public:
+ IntegerAccessorImpl() {}
+ ~IntegerAccessorImpl() override {}
+
+ private:
+ // sample::IntegerAccessor implementation.
+ void GetInteger(const GetIntegerCallback& callback) override {
+ callback.Run(1, sample::Enum::VALUE);
+ }
+ void SetInteger(int64_t data, sample::Enum type) override {}
+
+ DISALLOW_COPY_AND_ASSIGN(IntegerAccessorImpl);
+};
+
+TEST_F(BindingTest, SetInterfacePtrVersion) {
+ IntegerAccessorImpl impl;
+ sample::IntegerAccessorPtr ptr;
+ Binding<sample::IntegerAccessor> binding(&impl, &ptr);
+ EXPECT_EQ(3u, ptr.version());
+}
+
+TEST_F(BindingTest, PauseResume) {
+ bool called = false;
+ base::RunLoop run_loop;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ ServiceImpl impl;
+ Binding<sample::Service> binding(&impl, std::move(request));
+ binding.PauseIncomingMethodCallProcessing();
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called,
+ run_loop.QuitClosure()));
+ EXPECT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ // Frobinate() should not be called as the binding is paused.
+ EXPECT_FALSE(called);
+
+ // Resume the binding, which should trigger processing.
+ binding.ResumeIncomingMethodCallProcessing();
+ run_loop.Run();
+ EXPECT_TRUE(called);
+}
+
+// Verifies the connection error handler is not run while a binding is paused.
+TEST_F(BindingTest, ErrorHandleNotRunWhilePaused) {
+ bool called = false;
+ base::RunLoop run_loop;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ ServiceImpl impl;
+ Binding<sample::Service> binding(&impl, std::move(request));
+ binding.set_connection_error_handler(
+ SetFlagAndRunClosure(&called, run_loop.QuitClosure()));
+ binding.PauseIncomingMethodCallProcessing();
+
+ ptr.reset();
+ base::RunLoop().RunUntilIdle();
+ // The connection error handle should not be called as the binding is paused.
+ EXPECT_FALSE(called);
+
+ // Resume the binding, which should trigger the error handler.
+ binding.ResumeIncomingMethodCallProcessing();
+ run_loop.Run();
+ EXPECT_TRUE(called);
+}
+
+class PingServiceImpl : public test::PingService {
+ public:
+ PingServiceImpl() {}
+ ~PingServiceImpl() override {}
+
+ // test::PingService:
+ void Ping(const PingCallback& callback) override {
+ if (!ping_handler_.is_null())
+ ping_handler_.Run();
+ callback.Run();
+ }
+
+ void set_ping_handler(const base::Closure& handler) {
+ ping_handler_ = handler;
+ }
+
+ private:
+ base::Closure ping_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(PingServiceImpl);
+};
+
+class CallbackFilter : public MessageReceiver {
+ public:
+ explicit CallbackFilter(const base::Closure& callback)
+ : callback_(callback) {}
+ ~CallbackFilter() override {}
+
+ static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) {
+ return base::MakeUnique<CallbackFilter>(callback);
+ }
+
+ // MessageReceiver:
+ bool Accept(Message* message) override {
+ callback_.Run();
+ return true;
+ }
+
+ private:
+ const base::Closure callback_;
+};
+
+// Verifies that message filters are notified in the order they were added and
+// are always notified before a message is dispatched.
+TEST_F(BindingTest, MessageFilter) {
+ test::PingServicePtr ptr;
+ PingServiceImpl impl;
+ mojo::Binding<test::PingService> binding(&impl, MakeRequest(&ptr));
+
+ int status = 0;
+ auto handler_helper = [] (int* status, int expected_status, int new_status) {
+ EXPECT_EQ(expected_status, *status);
+ *status = new_status;
+ };
+ auto create_handler = [&] (int expected_status, int new_status) {
+ return base::Bind(handler_helper, &status, expected_status, new_status);
+ };
+
+ binding.AddFilter(CallbackFilter::Wrap(create_handler(0, 1)));
+ binding.AddFilter(CallbackFilter::Wrap(create_handler(1, 2)));
+ impl.set_ping_handler(create_handler(2, 3));
+
+ for (int i = 0; i < 10; ++i) {
+ status = 0;
+ base::RunLoop loop;
+ ptr->Ping(loop.QuitClosure());
+ loop.Run();
+ EXPECT_EQ(3, status);
+ }
+}
+
+void Fail() {
+ FAIL() << "Unexpected connection error";
+}
+
+TEST_F(BindingTest, FlushForTesting) {
+ bool called = false;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ ServiceImpl impl;
+ Binding<sample::Service> binding(&impl, std::move(request));
+ binding.set_connection_error_handler(base::Bind(&Fail));
+
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called));
+ EXPECT_FALSE(called);
+ // Because the flush is sent from the binding, it only guarantees that the
+ // request has been received, not the response. The second flush waits for the
+ // response to be received.
+ binding.FlushForTesting();
+ binding.FlushForTesting();
+ EXPECT_TRUE(called);
+}
+
+TEST_F(BindingTest, FlushForTestingWithClosedPeer) {
+ bool called = false;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ ServiceImpl impl;
+ Binding<sample::Service> binding(&impl, std::move(request));
+ binding.set_connection_error_handler(SetFlagAndRunClosure(&called));
+ ptr.reset();
+
+ EXPECT_FALSE(called);
+ binding.FlushForTesting();
+ EXPECT_TRUE(called);
+ binding.FlushForTesting();
+}
+
+TEST_F(BindingTest, ConnectionErrorWithReason) {
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ ServiceImpl impl;
+ Binding<sample::Service> binding(&impl, std::move(request));
+
+ base::RunLoop run_loop;
+ binding.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(1234u, custom_reason);
+ EXPECT_EQ("hello", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ ptr.ResetWithReason(1234u, "hello");
+
+ run_loop.Run();
+}
+
+template <typename T>
+struct WeakPtrImplRefTraits {
+ using PointerType = base::WeakPtr<T>;
+
+ static bool IsNull(const base::WeakPtr<T>& ptr) { return !ptr; }
+ static T* GetRawPointer(base::WeakPtr<T>* ptr) { return ptr->get(); }
+};
+
+template <typename T>
+using WeakBinding = Binding<T, WeakPtrImplRefTraits<T>>;
+
+TEST_F(BindingTest, CustomImplPointerType) {
+ PingServiceImpl impl;
+ base::WeakPtrFactory<test::PingService> weak_factory(&impl);
+
+ test::PingServicePtr proxy;
+ WeakBinding<test::PingService> binding(weak_factory.GetWeakPtr(),
+ MakeRequest(&proxy));
+
+ {
+ // Ensure the binding is functioning.
+ base::RunLoop run_loop;
+ proxy->Ping(run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ {
+ // Attempt to dispatch another message after the WeakPtr is invalidated.
+ base::Closure assert_not_reached = base::Bind([] { NOTREACHED(); });
+ impl.set_ping_handler(assert_not_reached);
+ proxy->Ping(assert_not_reached);
+
+ // The binding will close its end of the pipe which will trigger a
+ // connection error on |proxy|.
+ base::RunLoop run_loop;
+ proxy.set_connection_error_handler(run_loop.QuitClosure());
+ weak_factory.InvalidateWeakPtrs();
+ run_loop.Run();
+ }
+}
+
+// StrongBindingTest -----------------------------------------------------------
+
+using StrongBindingTest = BindingTestBase;
+
+// Tests that destroying a mojo::StrongBinding closes the bound message pipe
+// handle but does *not* destroy the implementation object.
+TEST_F(StrongBindingTest, DestroyClosesMessagePipe) {
+ base::RunLoop run_loop;
+ bool encountered_error = false;
+ bool was_deleted = false;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ ptr.set_connection_error_handler(
+ SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure()));
+ bool called = false;
+ base::RunLoop run_loop2;
+
+ auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted),
+ std::move(request));
+ ptr->Frobinate(
+ nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called, run_loop2.QuitClosure()));
+ run_loop2.Run();
+ EXPECT_TRUE(called);
+ EXPECT_FALSE(encountered_error);
+ binding->Close();
+
+ // Now that the StrongBinding is closed we should detect an error on the other
+ // end of the pipe.
+ run_loop.Run();
+ EXPECT_TRUE(encountered_error);
+
+ // Destroying the StrongBinding also destroys the impl.
+ ASSERT_TRUE(was_deleted);
+}
+
+// Tests the typical case, where the implementation object owns the
+// StrongBinding (and should be destroyed on connection error).
+TEST_F(StrongBindingTest, ConnectionErrorDestroysImpl) {
+ sample::ServicePtr ptr;
+ bool was_deleted = false;
+ // Will delete itself.
+ base::RunLoop run_loop;
+ new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(),
+ MakeRequest(&ptr));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(was_deleted);
+
+ ptr.reset();
+ EXPECT_FALSE(was_deleted);
+ run_loop.Run();
+ EXPECT_TRUE(was_deleted);
+}
+
+TEST_F(StrongBindingTest, FlushForTesting) {
+ bool called = false;
+ bool was_deleted = false;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted),
+ std::move(request));
+ binding->set_connection_error_handler(base::Bind(&Fail));
+
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&called));
+ EXPECT_FALSE(called);
+ // Because the flush is sent from the binding, it only guarantees that the
+ // request has been received, not the response. The second flush waits for the
+ // response to be received.
+ ASSERT_TRUE(binding);
+ binding->FlushForTesting();
+ ASSERT_TRUE(binding);
+ binding->FlushForTesting();
+ EXPECT_TRUE(called);
+ EXPECT_FALSE(was_deleted);
+ ptr.reset();
+ ASSERT_TRUE(binding);
+ binding->set_connection_error_handler(base::Closure());
+ binding->FlushForTesting();
+ EXPECT_TRUE(was_deleted);
+}
+
+TEST_F(StrongBindingTest, FlushForTestingWithClosedPeer) {
+ bool called = false;
+ bool was_deleted = false;
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted),
+ std::move(request));
+ binding->set_connection_error_handler(SetFlagAndRunClosure(&called));
+ ptr.reset();
+
+ EXPECT_FALSE(called);
+ EXPECT_FALSE(was_deleted);
+ ASSERT_TRUE(binding);
+ binding->FlushForTesting();
+ EXPECT_TRUE(called);
+ EXPECT_TRUE(was_deleted);
+ ASSERT_FALSE(binding);
+}
+
+TEST_F(StrongBindingTest, ConnectionErrorWithReason) {
+ sample::ServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ auto binding =
+ MakeStrongBinding(base::MakeUnique<ServiceImpl>(), std::move(request));
+ base::RunLoop run_loop;
+ binding->set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(5678u, custom_reason);
+ EXPECT_EQ("hello", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ ptr.ResetWithReason(5678u, "hello");
+
+ run_loop.Run();
+}
+
+} // namespace
+} // mojo
diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
new file mode 100644
index 0000000000..65b3c8c1d4
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
@@ -0,0 +1,286 @@
+// Copyright 2015 The Chromium Authors. 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 "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const double kMojoTicksPerSecond = 1000000.0;
+
+double MojoTicksToSeconds(MojoTimeTicks ticks) {
+ return ticks / kMojoTicksPerSecond;
+}
+
+class PingServiceImpl : public test::PingService {
+ public:
+ PingServiceImpl() {}
+ ~PingServiceImpl() override {}
+
+ // |PingService| methods:
+ void Ping(const PingCallback& callback) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PingServiceImpl);
+};
+
+void PingServiceImpl::Ping(const PingCallback& callback) {
+ callback.Run();
+}
+
+class PingPongTest {
+ public:
+ explicit PingPongTest(test::PingServicePtr service);
+
+ void Run(unsigned int iterations);
+
+ private:
+ void OnPingDone();
+
+ test::PingServicePtr service_;
+ unsigned int iterations_to_run_;
+ unsigned int current_iterations_;
+
+ base::Closure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(PingPongTest);
+};
+
+PingPongTest::PingPongTest(test::PingServicePtr service)
+ : service_(std::move(service)) {}
+
+void PingPongTest::Run(unsigned int iterations) {
+ iterations_to_run_ = iterations;
+ current_iterations_ = 0;
+
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitClosure();
+ service_->Ping(base::Bind(&PingPongTest::OnPingDone, base::Unretained(this)));
+ run_loop.Run();
+}
+
+void PingPongTest::OnPingDone() {
+ current_iterations_++;
+ if (current_iterations_ >= iterations_to_run_) {
+ quit_closure_.Run();
+ return;
+ }
+
+ service_->Ping(base::Bind(&PingPongTest::OnPingDone, base::Unretained(this)));
+}
+
+struct BoundPingService {
+ BoundPingService() : binding(&impl) { binding.Bind(MakeRequest(&service)); }
+
+ PingServiceImpl impl;
+ test::PingServicePtr service;
+ Binding<test::PingService> binding;
+};
+
+class MojoBindingsPerftest : public testing::Test {
+ public:
+ MojoBindingsPerftest() {}
+
+ protected:
+ base::MessageLoop loop_;
+};
+
+TEST_F(MojoBindingsPerftest, InProcessPingPong) {
+ test::PingServicePtr service;
+ PingServiceImpl impl;
+ Binding<test::PingService> binding(&impl, MakeRequest(&service));
+ PingPongTest test(std::move(service));
+
+ {
+ const unsigned int kIterations = 100000;
+ const MojoTimeTicks start_time = MojoGetTimeTicksNow();
+ test.Run(kIterations);
+ const MojoTimeTicks end_time = MojoGetTimeTicksNow();
+ test::LogPerfResult(
+ "InProcessPingPong", "0_Inactive",
+ kIterations / MojoTicksToSeconds(end_time - start_time),
+ "pings/second");
+ }
+
+ {
+ const size_t kNumInactiveServices = 1000;
+ BoundPingService* inactive_services =
+ new BoundPingService[kNumInactiveServices];
+
+ const unsigned int kIterations = 10000;
+ const MojoTimeTicks start_time = MojoGetTimeTicksNow();
+ test.Run(kIterations);
+ const MojoTimeTicks end_time = MojoGetTimeTicksNow();
+ test::LogPerfResult(
+ "InProcessPingPong", "1000_Inactive",
+ kIterations / MojoTicksToSeconds(end_time - start_time),
+ "pings/second");
+
+ delete[] inactive_services;
+ }
+}
+
+class PingPongPaddle : public MessageReceiverWithResponderStatus {
+ public:
+ PingPongPaddle(MessageReceiver* sender) : sender_(sender) {}
+
+ void set_sender(MessageReceiver* sender) { sender_ = sender; }
+
+ bool Accept(Message* message) override {
+ uint32_t count = message->header()->name;
+ if (!quit_closure_.is_null()) {
+ count++;
+ if (count >= expected_count_) {
+ end_time_ = base::TimeTicks::Now();
+ quit_closure_.Run();
+ return true;
+ }
+ }
+
+ internal::MessageBuilder builder(count, 0, 8, 0);
+ bool result = sender_->Accept(builder.message());
+ DCHECK(result);
+ return true;
+ }
+
+ bool AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) override {
+ NOTREACHED();
+ return true;
+ }
+
+ base::TimeDelta Serve(uint32_t expected_count) {
+ base::RunLoop run_loop;
+
+ expected_count_ = expected_count;
+ quit_closure_ = run_loop.QuitClosure();
+
+ start_time_ = base::TimeTicks::Now();
+ internal::MessageBuilder builder(0, 0, 8, 0);
+ bool result = sender_->Accept(builder.message());
+ DCHECK(result);
+
+ run_loop.Run();
+
+ return end_time_ - start_time_;
+ }
+
+ private:
+ base::TimeTicks start_time_;
+ base::TimeTicks end_time_;
+ uint32_t expected_count_ = 0;
+ MessageReceiver* sender_;
+ base::Closure quit_closure_;
+};
+
+TEST_F(MojoBindingsPerftest, MultiplexRouterPingPong) {
+ MessagePipe pipe;
+ scoped_refptr<internal::MultiplexRouter> router0(
+ new internal::MultiplexRouter(std::move(pipe.handle0),
+ internal::MultiplexRouter::SINGLE_INTERFACE,
+ true, base::ThreadTaskRunnerHandle::Get()));
+ scoped_refptr<internal::MultiplexRouter> router1(
+ new internal::MultiplexRouter(
+ std::move(pipe.handle1), internal::MultiplexRouter::SINGLE_INTERFACE,
+ false, base::ThreadTaskRunnerHandle::Get()));
+
+ PingPongPaddle paddle0(nullptr);
+ PingPongPaddle paddle1(nullptr);
+
+ InterfaceEndpointClient client0(
+ router0->CreateLocalEndpointHandle(kMasterInterfaceId), &paddle0, nullptr,
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+ InterfaceEndpointClient client1(
+ router1->CreateLocalEndpointHandle(kMasterInterfaceId), &paddle1, nullptr,
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+
+ paddle0.set_sender(&client0);
+ paddle1.set_sender(&client1);
+
+ static const uint32_t kWarmUpIterations = 1000;
+ static const uint32_t kTestIterations = 1000000;
+
+ paddle0.Serve(kWarmUpIterations);
+
+ base::TimeDelta duration = paddle0.Serve(kTestIterations);
+
+ test::LogPerfResult("MultiplexRouterPingPong", nullptr,
+ kTestIterations / duration.InSecondsF(), "pings/second");
+}
+
+class CounterReceiver : public MessageReceiverWithResponderStatus {
+ public:
+ bool Accept(Message* message) override {
+ counter_++;
+ return true;
+ }
+
+ bool AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) override {
+ NOTREACHED();
+ return true;
+ }
+
+ uint32_t counter() const { return counter_; }
+
+ void Reset() { counter_ = 0; }
+
+ private:
+ uint32_t counter_ = 0;
+};
+
+TEST_F(MojoBindingsPerftest, MultiplexRouterDispatchCost) {
+ MessagePipe pipe;
+ scoped_refptr<internal::MultiplexRouter> router(new internal::MultiplexRouter(
+ std::move(pipe.handle0), internal::MultiplexRouter::SINGLE_INTERFACE,
+ true, base::ThreadTaskRunnerHandle::Get()));
+ CounterReceiver receiver;
+ InterfaceEndpointClient client(
+ router->CreateLocalEndpointHandle(kMasterInterfaceId), &receiver, nullptr,
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+
+ static const uint32_t kIterations[] = {1000, 3000000};
+
+ for (size_t i = 0; i < 2; ++i) {
+ receiver.Reset();
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ for (size_t j = 0; j < kIterations[i]; ++j) {
+ internal::MessageBuilder builder(0, 0, 8, 0);
+ bool result =
+ router->SimulateReceivingMessageForTesting(builder.message());
+ DCHECK(result);
+ }
+
+ base::TimeTicks end_time = base::TimeTicks::Now();
+ base::TimeDelta duration = end_time - start_time;
+ CHECK_EQ(kIterations[i], receiver.counter());
+
+ if (i == 1) {
+ test::LogPerfResult("MultiplexRouterDispatchCost", nullptr,
+ kIterations[i] / duration.InSecondsF(),
+ "times/second");
+ }
+ }
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/blink_typemaps.gni b/mojo/public/cpp/bindings/tests/blink_typemaps.gni
new file mode 100644
index 0000000000..b71dcf8d46
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/blink_typemaps.gni
@@ -0,0 +1,8 @@
+# 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.
+
+typemaps = [
+ "//mojo/public/cpp/bindings/tests/rect_blink.typemap",
+ "//mojo/public/cpp/bindings/tests/test_native_types_blink.typemap",
+]
diff --git a/mojo/public/cpp/bindings/tests/buffer_unittest.cc b/mojo/public/cpp/bindings/tests/buffer_unittest.cc
new file mode 100644
index 0000000000..d75bdd0785
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/buffer_unittest.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 <stddef.h>
+
+#include <limits>
+
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+bool IsZero(void* p_buf, size_t size) {
+ char* buf = reinterpret_cast<char*>(p_buf);
+ for (size_t i = 0; i < size; ++i) {
+ if (buf[i] != 0)
+ return false;
+ }
+ return true;
+}
+
+// Tests that FixedBuffer allocates memory aligned to 8 byte boundaries.
+TEST(FixedBufferTest, Alignment) {
+ internal::FixedBufferForTesting buf(internal::Align(10) * 2);
+ ASSERT_EQ(buf.size(), 16u * 2);
+
+ void* a = buf.Allocate(10);
+ ASSERT_TRUE(a);
+ EXPECT_TRUE(IsZero(a, 10));
+ EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(a) % 8);
+
+ void* b = buf.Allocate(10);
+ ASSERT_TRUE(b);
+ EXPECT_TRUE(IsZero(b, 10));
+ EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(b) % 8);
+
+ // Any more allocations would result in an assert, but we can't test that.
+}
+
+// Tests that FixedBufferForTesting::Leak passes ownership to the caller.
+TEST(FixedBufferTest, Leak) {
+ void* ptr = nullptr;
+ void* buf_ptr = nullptr;
+ {
+ internal::FixedBufferForTesting buf(8);
+ ASSERT_EQ(8u, buf.size());
+
+ ptr = buf.Allocate(8);
+ ASSERT_TRUE(ptr);
+ buf_ptr = buf.Leak();
+
+ // The buffer should point to the first element allocated.
+ // TODO(mpcomplete): Is this a reasonable expectation?
+ EXPECT_EQ(ptr, buf_ptr);
+
+ // The FixedBufferForTesting should be empty now.
+ EXPECT_EQ(0u, buf.size());
+ EXPECT_FALSE(buf.Leak());
+ }
+
+ // Since we called Leak, ptr is still writable after FixedBufferForTesting
+ // went out of scope.
+ memset(ptr, 1, 8);
+ free(buf_ptr);
+}
+
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+TEST(FixedBufferTest, TooBig) {
+ internal::FixedBufferForTesting buf(24);
+
+ // A little bit too large.
+ EXPECT_EQ(reinterpret_cast<void*>(0), buf.Allocate(32));
+
+ // Move the cursor forward.
+ EXPECT_NE(reinterpret_cast<void*>(0), buf.Allocate(16));
+
+ // A lot too large.
+ EXPECT_EQ(reinterpret_cast<void*>(0),
+ buf.Allocate(std::numeric_limits<size_t>::max() - 1024u));
+
+ // A lot too large, leading to possible integer overflow.
+ EXPECT_EQ(reinterpret_cast<void*>(0),
+ buf.Allocate(std::numeric_limits<size_t>::max() - 8u));
+}
+#endif
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/chromium_typemaps.gni b/mojo/public/cpp/bindings/tests/chromium_typemaps.gni
new file mode 100644
index 0000000000..1da7cbfa3e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/chromium_typemaps.gni
@@ -0,0 +1,9 @@
+# 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.
+
+typemaps = [
+ "//mojo/public/cpp/bindings/tests/rect_chromium.typemap",
+ "//mojo/public/cpp/bindings/tests/struct_with_traits.typemap",
+ "//mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap",
+]
diff --git a/mojo/public/cpp/bindings/tests/connector_unittest.cc b/mojo/public/cpp/bindings/tests/connector_unittest.cc
new file mode 100644
index 0000000000..74ecb7a9ee
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/connector_unittest.cc
@@ -0,0 +1,599 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/connector.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/tests/message_queue.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class MessageAccumulator : public MessageReceiver {
+ public:
+ MessageAccumulator() {}
+ explicit MessageAccumulator(const base::Closure& closure)
+ : closure_(closure) {}
+
+ bool Accept(Message* message) override {
+ queue_.Push(message);
+ if (!closure_.is_null())
+ base::ResetAndReturn(&closure_).Run();
+ return true;
+ }
+
+ bool IsEmpty() const { return queue_.IsEmpty(); }
+
+ void Pop(Message* message) { queue_.Pop(message); }
+
+ void set_closure(const base::Closure& closure) { closure_ = closure; }
+
+ size_t size() const { return queue_.size(); }
+
+ private:
+ MessageQueue queue_;
+ base::Closure closure_;
+};
+
+class ConnectorDeletingMessageAccumulator : public MessageAccumulator {
+ public:
+ ConnectorDeletingMessageAccumulator(Connector** connector)
+ : connector_(connector) {}
+
+ bool Accept(Message* message) override {
+ delete *connector_;
+ *connector_ = nullptr;
+ return MessageAccumulator::Accept(message);
+ }
+
+ private:
+ Connector** connector_;
+};
+
+class ReentrantMessageAccumulator : public MessageAccumulator {
+ public:
+ ReentrantMessageAccumulator(Connector* connector)
+ : connector_(connector), number_of_calls_(0) {}
+
+ bool Accept(Message* message) override {
+ if (!MessageAccumulator::Accept(message))
+ return false;
+ number_of_calls_++;
+ if (number_of_calls_ == 1) {
+ return connector_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+ }
+ return true;
+ }
+
+ int number_of_calls() { return number_of_calls_; }
+
+ private:
+ Connector* connector_;
+ int number_of_calls_;
+};
+
+class ConnectorTest : public testing::Test {
+ public:
+ ConnectorTest() {}
+
+ void SetUp() override {
+ CreateMessagePipe(nullptr, &handle0_, &handle1_);
+ }
+
+ void TearDown() override {}
+
+ void AllocMessage(const char* text, Message* message) {
+ size_t payload_size = strlen(text) + 1; // Plus null terminator.
+ internal::MessageBuilder builder(1, 0, payload_size, 0);
+ memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
+
+ *message = std::move(*builder.message());
+ }
+
+ protected:
+ ScopedMessagePipeHandle handle0_;
+ ScopedMessagePipeHandle handle1_;
+
+ private:
+ base::MessageLoop loop_;
+};
+
+TEST_F(ConnectorTest, Basic) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ base::RunLoop run_loop;
+ MessageAccumulator accumulator(run_loop.QuitClosure());
+ connector1.set_incoming_receiver(&accumulator);
+
+ run_loop.Run();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, Basic_Synchronous) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, Basic_EarlyIncomingReceiver) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ base::RunLoop run_loop;
+ MessageAccumulator accumulator(run_loop.QuitClosure());
+ connector1.set_incoming_receiver(&accumulator);
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ run_loop.Run();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, Basic_TwoMessages) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char* kText[] = {"hello", "world"};
+
+ for (size_t i = 0; i < arraysize(kText); ++i) {
+ Message message;
+ AllocMessage(kText[i], &message);
+
+ connector0.Accept(&message);
+ }
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ for (size_t i = 0; i < arraysize(kText); ++i) {
+ if (accumulator.IsEmpty()) {
+ base::RunLoop run_loop;
+ accumulator.set_closure(run_loop.QuitClosure());
+ run_loop.Run();
+ }
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText[i]),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+ }
+}
+
+TEST_F(ConnectorTest, Basic_TwoMessages_Synchronous) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char* kText[] = {"hello", "world"};
+
+ for (size_t i = 0; i < arraysize(kText); ++i) {
+ Message message;
+ AllocMessage(kText[i], &message);
+
+ connector0.Accept(&message);
+ }
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText[0]),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+
+ ASSERT_TRUE(accumulator.IsEmpty());
+}
+
+TEST_F(ConnectorTest, WriteToClosedPipe) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ // Close the other end of the pipe.
+ handle1_.reset();
+
+ // Not observed yet because we haven't spun the message loop yet.
+ EXPECT_FALSE(connector0.encountered_error());
+
+ // Write failures are not reported.
+ bool ok = connector0.Accept(&message);
+ EXPECT_TRUE(ok);
+
+ // Still not observed.
+ EXPECT_FALSE(connector0.encountered_error());
+
+ // Spin the message loop, and then we should start observing the closed pipe.
+ base::RunLoop run_loop;
+ connector0.set_connection_error_handler(run_loop.QuitClosure());
+ run_loop.Run();
+
+ EXPECT_TRUE(connector0.encountered_error());
+}
+
+TEST_F(ConnectorTest, MessageWithHandles) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ Message message1;
+ AllocMessage(kText, &message1);
+
+ MessagePipe pipe;
+ message1.mutable_handles()->push_back(pipe.handle0.release());
+
+ connector0.Accept(&message1);
+
+ // The message should have been transferred, releasing the handles.
+ EXPECT_TRUE(message1.handles()->empty());
+
+ base::RunLoop run_loop;
+ MessageAccumulator accumulator(run_loop.QuitClosure());
+ connector1.set_incoming_receiver(&accumulator);
+
+ run_loop.Run();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+ ASSERT_EQ(1U, message_received.handles()->size());
+
+ // Now send a message to the transferred handle and confirm it's sent through
+ // to the orginal pipe.
+ // TODO(vtl): Do we need a better way of "downcasting" the handle types?
+ ScopedMessagePipeHandle smph;
+ smph.reset(MessagePipeHandle(message_received.handles()->front().value()));
+ message_received.mutable_handles()->front() = Handle();
+ // |smph| now owns this handle.
+
+ Connector connector_received(std::move(smph), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector_original(std::move(pipe.handle1),
+ Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ Message message2;
+ AllocMessage(kText, &message2);
+
+ connector_received.Accept(&message2);
+ base::RunLoop run_loop2;
+ MessageAccumulator accumulator2(run_loop2.QuitClosure());
+ connector_original.set_incoming_receiver(&accumulator2);
+ run_loop2.Run();
+
+ ASSERT_FALSE(accumulator2.IsEmpty());
+
+ accumulator2.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, WaitForIncomingMessageWithError) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ // Close the other end of the pipe.
+ handle1_.reset();
+ ASSERT_FALSE(connector0.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE));
+}
+
+TEST_F(ConnectorTest, WaitForIncomingMessageWithDeletion) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector* connector1 =
+ new Connector(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ ConnectorDeletingMessageAccumulator accumulator(&connector1);
+ connector1->set_incoming_receiver(&accumulator);
+
+ connector1->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+
+ ASSERT_FALSE(connector1);
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, WaitForIncomingMessageWithReentrancy) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char* kText[] = {"hello", "world"};
+
+ for (size_t i = 0; i < arraysize(kText); ++i) {
+ Message message;
+ AllocMessage(kText[i], &message);
+
+ connector0.Accept(&message);
+ }
+
+ ReentrantMessageAccumulator accumulator(&connector1);
+ connector1.set_incoming_receiver(&accumulator);
+
+ for (size_t i = 0; i < arraysize(kText); ++i) {
+ if (accumulator.IsEmpty()) {
+ base::RunLoop run_loop;
+ accumulator.set_closure(run_loop.QuitClosure());
+ run_loop.Run();
+ }
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText[i]),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+ }
+
+ ASSERT_EQ(2, accumulator.number_of_calls());
+}
+
+void ForwardErrorHandler(bool* called, const base::Closure& callback) {
+ *called = true;
+ callback.Run();
+}
+
+TEST_F(ConnectorTest, RaiseError) {
+ base::RunLoop run_loop, run_loop2;
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ bool error_handler_called0 = false;
+ connector0.set_connection_error_handler(
+ base::Bind(&ForwardErrorHandler, &error_handler_called0,
+ run_loop.QuitClosure()));
+
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ bool error_handler_called1 = false;
+ connector1.set_connection_error_handler(
+ base::Bind(&ForwardErrorHandler, &error_handler_called1,
+ run_loop2.QuitClosure()));
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+ connector0.RaiseError();
+
+ base::RunLoop run_loop3;
+ MessageAccumulator accumulator(run_loop3.QuitClosure());
+ connector1.set_incoming_receiver(&accumulator);
+
+ run_loop3.Run();
+
+ // Messages sent prior to RaiseError() still arrive at the other end.
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+
+ run_loop.Run();
+ run_loop2.Run();
+
+ // Connection error handler is called at both sides.
+ EXPECT_TRUE(error_handler_called0);
+ EXPECT_TRUE(error_handler_called1);
+
+ // The error flag is set at both sides.
+ EXPECT_TRUE(connector0.encountered_error());
+ EXPECT_TRUE(connector1.encountered_error());
+
+ // The message pipe handle is valid at both sides.
+ EXPECT_TRUE(connector0.is_valid());
+ EXPECT_TRUE(connector1.is_valid());
+}
+
+void PauseConnectorAndRunClosure(Connector* connector,
+ const base::Closure& closure) {
+ connector->PauseIncomingMethodCallProcessing();
+ closure.Run();
+}
+
+TEST_F(ConnectorTest, PauseWithQueuedMessages) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ // Queue up two messages.
+ Message message;
+ AllocMessage(kText, &message);
+ connector0.Accept(&message);
+ AllocMessage(kText, &message);
+ connector0.Accept(&message);
+
+ base::RunLoop run_loop;
+ // Configure the accumulator such that it pauses after the first message is
+ // received.
+ MessageAccumulator accumulator(
+ base::Bind(&PauseConnectorAndRunClosure, &connector1,
+ run_loop.QuitClosure()));
+ connector1.set_incoming_receiver(&accumulator);
+
+ run_loop.Run();
+
+ // As we paused after the first message we should only have gotten one
+ // message.
+ ASSERT_EQ(1u, accumulator.size());
+}
+
+void AccumulateWithNestedLoop(MessageAccumulator* accumulator,
+ const base::Closure& closure) {
+ base::RunLoop nested_run_loop;
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ accumulator->set_closure(nested_run_loop.QuitClosure());
+ nested_run_loop.Run();
+ closure.Run();
+}
+
+TEST_F(ConnectorTest, ProcessWhenNested) {
+ Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+
+ const char kText[] = "hello world";
+
+ // Queue up two messages.
+ Message message;
+ AllocMessage(kText, &message);
+ connector0.Accept(&message);
+ AllocMessage(kText, &message);
+ connector0.Accept(&message);
+
+ base::RunLoop run_loop;
+ MessageAccumulator accumulator;
+ // When the accumulator gets the first message it spins a nested message
+ // loop. The loop is quit when another message is received.
+ accumulator.set_closure(base::Bind(&AccumulateWithNestedLoop, &accumulator,
+ run_loop.QuitClosure()));
+ connector1.set_incoming_receiver(&accumulator);
+
+ run_loop.Run();
+
+ ASSERT_EQ(2u, accumulator.size());
+}
+
+TEST_F(ConnectorTest, DestroyOnDifferentThreadAfterClose) {
+ std::unique_ptr<Connector> connector(
+ new Connector(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get()));
+
+ connector->CloseMessagePipe();
+
+ base::Thread another_thread("ThreadForDestroyingConnector");
+ another_thread.Start();
+
+ base::RunLoop run_loop;
+ another_thread.task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(
+ [](std::unique_ptr<Connector> connector) { connector.reset(); },
+ base::Passed(std::move(connector))),
+ run_loop.QuitClosure());
+
+ run_loop.Run();
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/constant_unittest.cc b/mojo/public/cpp/bindings/tests/constant_unittest.cc
new file mode 100644
index 0000000000..caa6464cf4
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/constant_unittest.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 <cmath>
+
+#include "base/strings/string_piece.h"
+#include "mojo/public/interfaces/bindings/tests/test_constants.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+
+TEST(ConstantTest, GlobalConstants) {
+ // Compile-time constants.
+ static_assert(kBoolValue == true, "");
+ static_assert(kInt8Value == -2, "");
+ static_assert(kUint8Value == 128U, "");
+ static_assert(kInt16Value == -233, "");
+ static_assert(kUint16Value == 44204U, "");
+ static_assert(kInt32Value == -44204, "");
+ static_assert(kUint32Value == 4294967295U, "");
+ static_assert(kInt64Value == -9223372036854775807, "");
+ static_assert(kUint64Value == 9999999999999999999ULL, "");
+ static_assert(kDoubleValue == 3.14159, "");
+ static_assert(kFloatValue == 2.71828f, "");
+
+ EXPECT_EQ(base::StringPiece(kStringValue), "test string contents");
+ EXPECT_TRUE(std::isnan(kDoubleNaN));
+ EXPECT_TRUE(std::isinf(kDoubleInfinity));
+ EXPECT_TRUE(std::isinf(kDoubleNegativeInfinity));
+ EXPECT_NE(kDoubleInfinity, kDoubleNegativeInfinity);
+ EXPECT_TRUE(std::isnan(kFloatNaN));
+ EXPECT_TRUE(std::isinf(kFloatInfinity));
+ EXPECT_TRUE(std::isinf(kFloatNegativeInfinity));
+ EXPECT_NE(kFloatInfinity, kFloatNegativeInfinity);
+}
+
+TEST(ConstantTest, StructConstants) {
+ // Compile-time constants.
+ static_assert(StructWithConstants::kInt8Value == 5U, "");
+ static_assert(StructWithConstants::kFloatValue == 765.432f, "");
+
+ EXPECT_EQ(base::StringPiece(StructWithConstants::kStringValue),
+ "struct test string contents");
+}
+
+TEST(ConstantTest, InterfaceConstants) {
+ // Compile-time constants.
+ static_assert(InterfaceWithConstants::kUint32Value == 20100722, "");
+ static_assert(InterfaceWithConstants::kDoubleValue == 12.34567, "");
+
+ EXPECT_EQ(base::StringPiece(InterfaceWithConstants::kStringValue),
+ "interface test string contents");
+ EXPECT_EQ(base::StringPiece(InterfaceWithConstants::Name_),
+ "mojo::test::InterfaceWithConstants");
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/container_test_util.cc b/mojo/public/cpp/bindings/tests/container_test_util.cc
new file mode 100644
index 0000000000..a53d351e0c
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/container_test_util.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 <stddef.h>
+
+#include "mojo/public/cpp/bindings/tests/container_test_util.h"
+
+namespace mojo {
+
+size_t CopyableType::num_instances_ = 0;
+size_t MoveOnlyType::num_instances_ = 0;
+
+CopyableType::CopyableType() : copied_(false), ptr_(this) {
+ num_instances_++;
+}
+
+CopyableType::CopyableType(const CopyableType& other)
+ : copied_(true), ptr_(other.ptr()) {
+ num_instances_++;
+}
+
+CopyableType& CopyableType::operator=(const CopyableType& other) {
+ copied_ = true;
+ ptr_ = other.ptr();
+ return *this;
+}
+
+CopyableType::~CopyableType() {
+ num_instances_--;
+}
+
+MoveOnlyType::MoveOnlyType() : moved_(false), ptr_(this) {
+ num_instances_++;
+}
+
+MoveOnlyType::MoveOnlyType(MoveOnlyType&& other)
+ : moved_(true), ptr_(other.ptr()) {
+ num_instances_++;
+}
+
+MoveOnlyType& MoveOnlyType::operator=(MoveOnlyType&& other) {
+ moved_ = true;
+ ptr_ = other.ptr();
+ return *this;
+}
+
+MoveOnlyType::~MoveOnlyType() {
+ num_instances_--;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/container_test_util.h b/mojo/public/cpp/bindings/tests/container_test_util.h
new file mode 100644
index 0000000000..f709c1561e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/container_test_util.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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+
+namespace mojo {
+
+class CopyableType {
+ public:
+ CopyableType();
+ CopyableType(const CopyableType& other);
+ CopyableType& operator=(const CopyableType& other);
+ ~CopyableType();
+
+ bool copied() const { return copied_; }
+ static size_t num_instances() { return num_instances_; }
+ CopyableType* ptr() const { return ptr_; }
+ void ResetCopied() { copied_ = false; }
+
+ private:
+ bool copied_;
+ static size_t num_instances_;
+ CopyableType* ptr_;
+};
+
+class MoveOnlyType {
+ public:
+ typedef MoveOnlyType Data_;
+ MoveOnlyType();
+ MoveOnlyType(MoveOnlyType&& other);
+ MoveOnlyType& operator=(MoveOnlyType&& other);
+ ~MoveOnlyType();
+
+ bool moved() const { return moved_; }
+ static size_t num_instances() { return num_instances_; }
+ MoveOnlyType* ptr() const { return ptr_; }
+ void ResetMoved() { moved_ = false; }
+
+ private:
+ bool moved_;
+ static size_t num_instances_;
+ MoveOnlyType* ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(MoveOnlyType);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_
diff --git a/mojo/public/cpp/bindings/tests/data_view_unittest.cc b/mojo/public/cpp/bindings/tests/data_view_unittest.cc
new file mode 100644
index 0000000000..0ebfda5d12
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/data_view_unittest.cc
@@ -0,0 +1,303 @@
+// 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 <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/interfaces/bindings/tests/test_data_view.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace data_view {
+namespace {
+
+class DataViewTest : public testing::Test {
+ private:
+ base::MessageLoop message_loop_;
+};
+
+struct DataViewHolder {
+ std::unique_ptr<TestStructDataView> data_view;
+ std::unique_ptr<mojo::internal::FixedBufferForTesting> buf;
+ mojo::internal::SerializationContext context;
+};
+
+std::unique_ptr<DataViewHolder> SerializeTestStruct(TestStructPtr input) {
+ std::unique_ptr<DataViewHolder> result(new DataViewHolder);
+
+ size_t size = mojo::internal::PrepareToSerialize<TestStructDataView>(
+ input, &result->context);
+
+ result->buf.reset(new mojo::internal::FixedBufferForTesting(size));
+ internal::TestStruct_Data* data = nullptr;
+ mojo::internal::Serialize<TestStructDataView>(input, result->buf.get(), &data,
+ &result->context);
+
+ result->data_view.reset(new TestStructDataView(data, &result->context));
+ return result;
+}
+
+class TestInterfaceImpl : public TestInterface {
+ public:
+ explicit TestInterfaceImpl(TestInterfaceRequest request)
+ : binding_(this, std::move(request)) {}
+ ~TestInterfaceImpl() override {}
+
+ // TestInterface implementation:
+ void Echo(int32_t value, const EchoCallback& callback) override {
+ callback.Run(value);
+ }
+
+ private:
+ Binding<TestInterface> binding_;
+};
+
+} // namespace
+
+TEST_F(DataViewTest, String) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_string = "hello";
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ StringDataView string_data_view;
+ data_view.GetFStringDataView(&string_data_view);
+
+ ASSERT_FALSE(string_data_view.is_null());
+ EXPECT_EQ(std::string("hello"),
+ std::string(string_data_view.storage(), string_data_view.size()));
+}
+
+TEST_F(DataViewTest, NestedStruct) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_struct = NestedStruct::New();
+ obj->f_struct->f_int32 = 42;
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ NestedStructDataView struct_data_view;
+ data_view.GetFStructDataView(&struct_data_view);
+
+ ASSERT_FALSE(struct_data_view.is_null());
+ EXPECT_EQ(42, struct_data_view.f_int32());
+}
+
+TEST_F(DataViewTest, NativeStruct) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_native_struct = NativeStruct::New();
+ obj->f_native_struct->data = std::vector<uint8_t>({3, 2, 1});
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ NativeStructDataView struct_data_view;
+ data_view.GetFNativeStructDataView(&struct_data_view);
+
+ ASSERT_FALSE(struct_data_view.is_null());
+ ASSERT_EQ(3u, struct_data_view.size());
+ EXPECT_EQ(3, struct_data_view[0]);
+ EXPECT_EQ(2, struct_data_view[1]);
+ EXPECT_EQ(1, struct_data_view[2]);
+ EXPECT_EQ(3, *struct_data_view.data());
+}
+
+TEST_F(DataViewTest, BoolArray) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_bool_array = {true, false};
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<bool> array_data_view;
+ data_view.GetFBoolArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(2u, array_data_view.size());
+ EXPECT_TRUE(array_data_view[0]);
+ EXPECT_FALSE(array_data_view[1]);
+}
+
+TEST_F(DataViewTest, IntegerArray) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_int32_array = {1024, 128};
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<int32_t> array_data_view;
+ data_view.GetFInt32ArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(2u, array_data_view.size());
+ EXPECT_EQ(1024, array_data_view[0]);
+ EXPECT_EQ(128, array_data_view[1]);
+ EXPECT_EQ(1024, *array_data_view.data());
+}
+
+TEST_F(DataViewTest, EnumArray) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_enum_array = {TestEnum::VALUE_1, TestEnum::VALUE_0};
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<TestEnum> array_data_view;
+ data_view.GetFEnumArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(2u, array_data_view.size());
+ EXPECT_EQ(TestEnum::VALUE_1, array_data_view[0]);
+ EXPECT_EQ(TestEnum::VALUE_0, array_data_view[1]);
+ EXPECT_EQ(TestEnum::VALUE_0, *(array_data_view.data() + 1));
+
+ TestEnum output;
+ ASSERT_TRUE(array_data_view.Read(0, &output));
+ EXPECT_EQ(TestEnum::VALUE_1, output);
+}
+
+TEST_F(DataViewTest, InterfaceArray) {
+ TestInterfacePtr ptr;
+ TestInterfaceImpl impl(MakeRequest(&ptr));
+
+ TestStructPtr obj(TestStruct::New());
+ obj->f_interface_array.push_back(std::move(ptr));
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<TestInterfacePtrDataView> array_data_view;
+ data_view.GetFInterfaceArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(1u, array_data_view.size());
+
+ TestInterfacePtr ptr2 = array_data_view.Take<TestInterfacePtr>(0);
+ ASSERT_TRUE(ptr2);
+ int32_t result = 0;
+ ASSERT_TRUE(ptr2->Echo(42, &result));
+ EXPECT_EQ(42, result);
+}
+
+TEST_F(DataViewTest, NestedArray) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_nested_array = {{3, 4}, {2}};
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<ArrayDataView<int32_t>> array_data_view;
+ data_view.GetFNestedArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(2u, array_data_view.size());
+
+ ArrayDataView<int32_t> nested_array_data_view;
+ array_data_view.GetDataView(0, &nested_array_data_view);
+ ASSERT_FALSE(nested_array_data_view.is_null());
+ ASSERT_EQ(2u, nested_array_data_view.size());
+ EXPECT_EQ(4, nested_array_data_view[1]);
+
+ std::vector<int32_t> vec;
+ ASSERT_TRUE(array_data_view.Read(1, &vec));
+ ASSERT_EQ(1u, vec.size());
+ EXPECT_EQ(2, vec[0]);
+}
+
+TEST_F(DataViewTest, StructArray) {
+ NestedStructPtr nested_struct(NestedStruct::New());
+ nested_struct->f_int32 = 42;
+
+ TestStructPtr obj(TestStruct::New());
+ obj->f_struct_array.push_back(std::move(nested_struct));
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<NestedStructDataView> array_data_view;
+ data_view.GetFStructArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(1u, array_data_view.size());
+
+ NestedStructDataView struct_data_view;
+ array_data_view.GetDataView(0, &struct_data_view);
+ ASSERT_FALSE(struct_data_view.is_null());
+ EXPECT_EQ(42, struct_data_view.f_int32());
+
+ NestedStructPtr nested_struct2;
+ ASSERT_TRUE(array_data_view.Read(0, &nested_struct2));
+ ASSERT_TRUE(nested_struct2);
+ EXPECT_EQ(42, nested_struct2->f_int32);
+}
+
+TEST_F(DataViewTest, Map) {
+ TestStructPtr obj(TestStruct::New());
+ obj->f_map["1"] = 1;
+ obj->f_map["2"] = 2;
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ MapDataView<StringDataView, int32_t> map_data_view;
+ data_view.GetFMapDataView(&map_data_view);
+
+ ASSERT_FALSE(map_data_view.is_null());
+ ASSERT_EQ(2u, map_data_view.size());
+
+ ASSERT_FALSE(map_data_view.keys().is_null());
+ ASSERT_EQ(2u, map_data_view.keys().size());
+
+ ASSERT_FALSE(map_data_view.values().is_null());
+ ASSERT_EQ(2u, map_data_view.values().size());
+
+ std::vector<std::string> keys;
+ ASSERT_TRUE(map_data_view.ReadKeys(&keys));
+ std::vector<int32_t> values;
+ ASSERT_TRUE(map_data_view.ReadValues(&values));
+
+ std::unordered_map<std::string, int32_t> map;
+ for (size_t i = 0; i < 2; ++i)
+ map[keys[i]] = values[i];
+
+ EXPECT_EQ(1, map["1"]);
+ EXPECT_EQ(2, map["2"]);
+}
+
+TEST_F(DataViewTest, UnionArray) {
+ TestUnionPtr union_ptr(TestUnion::New());
+ union_ptr->set_f_int32(1024);
+
+ TestStructPtr obj(TestStruct::New());
+ obj->f_union_array.push_back(std::move(union_ptr));
+
+ auto data_view_holder = SerializeTestStruct(std::move(obj));
+ auto& data_view = *data_view_holder->data_view;
+
+ ArrayDataView<TestUnionDataView> array_data_view;
+ data_view.GetFUnionArrayDataView(&array_data_view);
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(1u, array_data_view.size());
+
+ TestUnionDataView union_data_view;
+ array_data_view.GetDataView(0, &union_data_view);
+ ASSERT_FALSE(union_data_view.is_null());
+
+ TestUnionPtr union_ptr2;
+ ASSERT_TRUE(array_data_view.Read(0, &union_ptr2));
+ ASSERT_TRUE(union_ptr2->is_f_int32());
+ EXPECT_EQ(1024, union_ptr2->get_f_int32());
+}
+
+} // namespace data_view
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/e2e_perftest.cc b/mojo/public/cpp/bindings/tests/e2e_perftest.cc
new file mode 100644
index 0000000000..bc69e0f727
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/e2e_perftest.cc
@@ -0,0 +1,204 @@
+// 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 <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/perf_time_logger.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/test/mojo_test_base.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class EchoServiceImpl : public test::EchoService {
+ public:
+ explicit EchoServiceImpl(const base::Closure& quit_closure);
+ ~EchoServiceImpl() override;
+
+ // |EchoService| methods:
+ void Echo(const std::string& test_data,
+ const EchoCallback& callback) override;
+
+ private:
+ const base::Closure quit_closure_;
+};
+
+EchoServiceImpl::EchoServiceImpl(const base::Closure& quit_closure)
+ : quit_closure_(quit_closure) {}
+
+EchoServiceImpl::~EchoServiceImpl() {
+ quit_closure_.Run();
+}
+
+void EchoServiceImpl::Echo(const std::string& test_data,
+ const EchoCallback& callback) {
+ callback.Run(test_data);
+}
+
+class PingPongTest {
+ public:
+ explicit PingPongTest(test::EchoServicePtr service);
+
+ void RunTest(int iterations, int batch_size, int message_size);
+
+ private:
+ void DoPing();
+ void OnPingDone(const std::string& reply);
+
+ test::EchoServicePtr service_;
+ const base::Callback<void(const std::string&)> ping_done_callback_;
+
+ int iterations_;
+ int batch_size_;
+ std::string message_;
+
+ int current_iterations_;
+ int calls_outstanding_;
+
+ base::Closure quit_closure_;
+};
+
+PingPongTest::PingPongTest(test::EchoServicePtr service)
+ : service_(std::move(service)),
+ ping_done_callback_(
+ base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))) {}
+
+void PingPongTest::RunTest(int iterations, int batch_size, int message_size) {
+ iterations_ = iterations;
+ batch_size_ = batch_size;
+ message_ = std::string(message_size, 'a');
+ current_iterations_ = 0;
+ calls_outstanding_ = 0;
+
+ base::MessageLoop::current()->SetNestableTasksAllowed(true);
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitClosure();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&PingPongTest::DoPing, base::Unretained(this)));
+ run_loop.Run();
+}
+
+void PingPongTest::DoPing() {
+ DCHECK_EQ(0, calls_outstanding_);
+ current_iterations_++;
+ if (current_iterations_ > iterations_) {
+ quit_closure_.Run();
+ return;
+ }
+
+ calls_outstanding_ = batch_size_;
+ for (int i = 0; i < batch_size_; i++) {
+ service_->Echo(message_, ping_done_callback_);
+ }
+}
+
+void PingPongTest::OnPingDone(const std::string& reply) {
+ DCHECK_GT(calls_outstanding_, 0);
+ calls_outstanding_--;
+
+ if (!calls_outstanding_)
+ DoPing();
+}
+
+class MojoE2EPerftest : public edk::test::MojoTestBase {
+ public:
+ void RunTestOnTaskRunner(base::TaskRunner* runner,
+ MojoHandle client_mp,
+ const std::string& test_name) {
+ if (runner == base::ThreadTaskRunnerHandle::Get().get()) {
+ RunTests(client_mp, test_name);
+ } else {
+ base::RunLoop run_loop;
+ runner->PostTaskAndReply(
+ FROM_HERE, base::Bind(&MojoE2EPerftest::RunTests,
+ base::Unretained(this), client_mp, test_name),
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+
+ private:
+ void RunTests(MojoHandle client_mp, const std::string& test_name) {
+ const int kMessages = 10000;
+ const int kBatchSizes[] = {1, 10, 100};
+ const int kMessageSizes[] = {8, 64, 512, 4096, 65536};
+
+ test::EchoServicePtr service;
+ service.Bind(InterfacePtrInfo<test::EchoService>(
+ ScopedMessagePipeHandle(MessagePipeHandle(client_mp)),
+ service.version()));
+ PingPongTest test(std::move(service));
+
+ for (int batch_size : kBatchSizes) {
+ for (int message_size : kMessageSizes) {
+ int num_messages = kMessages;
+ if (message_size == 65536)
+ num_messages /= 10;
+ std::string sub_test_name = base::StringPrintf(
+ "%s/%dx%d/%dbytes", test_name.c_str(), num_messages / batch_size,
+ batch_size, message_size);
+ base::PerfTimeLogger timer(sub_test_name.c_str());
+ test.RunTest(num_messages / batch_size, batch_size, message_size);
+ }
+ }
+ }
+};
+
+void CreateAndRunService(InterfaceRequest<test::EchoService> request,
+ const base::Closure& cb) {
+ MakeStrongBinding(base::MakeUnique<EchoServiceImpl>(cb), std::move(request));
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingService, MojoE2EPerftest, mp) {
+ MojoHandle service_mp;
+ EXPECT_EQ("hello", ReadMessageWithHandles(mp, &service_mp, 1));
+
+ InterfaceRequest<test::EchoService> request;
+ request.Bind(ScopedMessagePipeHandle(MessagePipeHandle(service_mp)));
+ base::RunLoop run_loop;
+ edk::GetIOTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&CreateAndRunService, base::Passed(&request),
+ base::Bind(base::IgnoreResult(&base::TaskRunner::PostTask),
+ message_loop_.task_runner(), FROM_HERE,
+ run_loop.QuitClosure())));
+ run_loop.Run();
+}
+
+TEST_F(MojoE2EPerftest, MultiProcessEchoMainThread) {
+ RUN_CHILD_ON_PIPE(PingService, mp)
+ MojoHandle client_mp, service_mp;
+ CreateMessagePipe(&client_mp, &service_mp);
+ WriteMessageWithHandles(mp, "hello", &service_mp, 1);
+ RunTestOnTaskRunner(message_loop_.task_runner().get(), client_mp,
+ "MultiProcessEchoMainThread");
+ END_CHILD()
+}
+
+TEST_F(MojoE2EPerftest, MultiProcessEchoIoThread) {
+ RUN_CHILD_ON_PIPE(PingService, mp)
+ MojoHandle client_mp, service_mp;
+ CreateMessagePipe(&client_mp, &service_mp);
+ WriteMessageWithHandles(mp, "hello", &service_mp, 1);
+ RunTestOnTaskRunner(edk::GetIOTaskRunner().get(), client_mp,
+ "MultiProcessEchoIoThread");
+ END_CHILD()
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/equals_unittest.cc b/mojo/public/cpp/bindings/tests/equals_unittest.cc
new file mode 100644
index 0000000000..6483baf8f0
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/equals_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 <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+
+namespace {
+
+RectPtr CreateRect() {
+ return Rect::New(1, 2, 3, 4);
+}
+
+using EqualsTest = testing::Test;
+
+} // namespace
+
+TEST_F(EqualsTest, NullStruct) {
+ RectPtr r1;
+ RectPtr r2;
+ EXPECT_TRUE(r1.Equals(r2));
+ EXPECT_TRUE(r2.Equals(r1));
+
+ r1 = CreateRect();
+ EXPECT_FALSE(r1.Equals(r2));
+ EXPECT_FALSE(r2.Equals(r1));
+}
+
+TEST_F(EqualsTest, Struct) {
+ RectPtr r1(CreateRect());
+ RectPtr r2(r1.Clone());
+ EXPECT_TRUE(r1.Equals(r2));
+ r2->y = 1;
+ EXPECT_FALSE(r1.Equals(r2));
+ r2.reset();
+ EXPECT_FALSE(r1.Equals(r2));
+}
+
+TEST_F(EqualsTest, StructNested) {
+ RectPairPtr p1(RectPair::New(CreateRect(), CreateRect()));
+ RectPairPtr p2(p1.Clone());
+ EXPECT_TRUE(p1.Equals(p2));
+ p2->second->width = 0;
+ EXPECT_FALSE(p1.Equals(p2));
+ p2->second.reset();
+ EXPECT_FALSE(p1.Equals(p2));
+}
+
+TEST_F(EqualsTest, Array) {
+ std::vector<RectPtr> rects;
+ rects.push_back(CreateRect());
+ NamedRegionPtr n1(NamedRegion::New(std::string("n1"), std::move(rects)));
+ NamedRegionPtr n2(n1.Clone());
+ EXPECT_TRUE(n1.Equals(n2));
+
+ n2->rects = base::nullopt;
+ EXPECT_FALSE(n1.Equals(n2));
+ n2->rects.emplace();
+ EXPECT_FALSE(n1.Equals(n2));
+
+ n2->rects->push_back(CreateRect());
+ n2->rects->push_back(CreateRect());
+ EXPECT_FALSE(n1.Equals(n2));
+
+ n2->rects->resize(1);
+ (*n2->rects)[0]->width = 0;
+ EXPECT_FALSE(n1.Equals(n2));
+
+ (*n2->rects)[0] = CreateRect();
+ EXPECT_TRUE(n1.Equals(n2));
+}
+
+TEST_F(EqualsTest, InterfacePtr) {
+ base::MessageLoop message_loop;
+
+ SomeInterfacePtr inf1;
+ SomeInterfacePtr inf2;
+
+ EXPECT_TRUE(inf1.Equals(inf1));
+ EXPECT_TRUE(inf1.Equals(inf2));
+
+ auto inf1_request = MakeRequest(&inf1);
+ ALLOW_UNUSED_LOCAL(inf1_request);
+
+ EXPECT_TRUE(inf1.Equals(inf1));
+ EXPECT_FALSE(inf1.Equals(inf2));
+
+ auto inf2_request = MakeRequest(&inf2);
+ ALLOW_UNUSED_LOCAL(inf2_request);
+
+ EXPECT_FALSE(inf1.Equals(inf2));
+}
+
+TEST_F(EqualsTest, InterfaceRequest) {
+ base::MessageLoop message_loop;
+
+ InterfaceRequest<SomeInterface> req1;
+ InterfaceRequest<SomeInterface> req2;
+
+ EXPECT_TRUE(req1.Equals(req1));
+ EXPECT_TRUE(req1.Equals(req2));
+
+ SomeInterfacePtr inf1;
+ req1 = MakeRequest(&inf1);
+
+ EXPECT_TRUE(req1.Equals(req1));
+ EXPECT_FALSE(req1.Equals(req2));
+
+ SomeInterfacePtr inf2;
+ req2 = MakeRequest(&inf2);
+
+ EXPECT_FALSE(req1.Equals(req2));
+}
+
+} // test
+} // mojo
diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
new file mode 100644
index 0000000000..ef977af935
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
@@ -0,0 +1,356 @@
+// Copyright 2013 The Chromium Authors. 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/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+const char kText1[] = "hello";
+const char kText2[] = "world";
+
+void RecordString(std::string* storage,
+ const base::Closure& closure,
+ const std::string& str) {
+ *storage = str;
+ closure.Run();
+}
+
+base::Callback<void(const std::string&)> MakeStringRecorder(
+ std::string* storage,
+ const base::Closure& closure) {
+ return base::Bind(&RecordString, storage, closure);
+}
+
+class ImportedInterfaceImpl : public imported::ImportedInterface {
+ public:
+ ImportedInterfaceImpl(
+ InterfaceRequest<imported::ImportedInterface> request,
+ const base::Closure& closure)
+ : binding_(this, std::move(request)), closure_(closure) {}
+
+ void DoSomething() override {
+ do_something_count_++;
+ closure_.Run();
+ }
+
+ static int do_something_count() { return do_something_count_; }
+
+ private:
+ static int do_something_count_;
+ Binding<ImportedInterface> binding_;
+ base::Closure closure_;
+};
+int ImportedInterfaceImpl::do_something_count_ = 0;
+
+class SampleNamedObjectImpl : public sample::NamedObject {
+ public:
+ SampleNamedObjectImpl() {}
+
+ void SetName(const std::string& name) override { name_ = name; }
+
+ void GetName(const GetNameCallback& callback) override {
+ callback.Run(name_);
+ }
+
+ private:
+ std::string name_;
+};
+
+class SampleFactoryImpl : public sample::Factory {
+ public:
+ explicit SampleFactoryImpl(InterfaceRequest<sample::Factory> request)
+ : binding_(this, std::move(request)) {}
+
+ void DoStuff(sample::RequestPtr request,
+ ScopedMessagePipeHandle pipe,
+ const DoStuffCallback& callback) override {
+ std::string text1;
+ if (pipe.is_valid())
+ EXPECT_TRUE(ReadTextMessage(pipe.get(), &text1));
+
+ std::string text2;
+ if (request->pipe.is_valid()) {
+ EXPECT_TRUE(ReadTextMessage(request->pipe.get(), &text2));
+
+ // Ensure that simply accessing request->pipe does not close it.
+ EXPECT_TRUE(request->pipe.is_valid());
+ }
+
+ ScopedMessagePipeHandle pipe0;
+ if (!text2.empty()) {
+ CreateMessagePipe(nullptr, &pipe0, &pipe1_);
+ EXPECT_TRUE(WriteTextMessage(pipe1_.get(), text2));
+ }
+
+ sample::ResponsePtr response(sample::Response::New(2, std::move(pipe0)));
+ callback.Run(std::move(response), text1);
+
+ if (request->obj)
+ request->obj->DoSomething();
+ }
+
+ void DoStuff2(ScopedDataPipeConsumerHandle pipe,
+ const DoStuff2Callback& callback) override {
+ // Read the data from the pipe, writing the response (as a string) to
+ // DidStuff2().
+ ASSERT_TRUE(pipe.is_valid());
+ uint32_t data_size = 0;
+
+ MojoHandleSignalsState state;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
+ ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadDataRaw(
+ pipe.get(), nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY));
+ ASSERT_NE(0, static_cast<int>(data_size));
+ char data[64];
+ ASSERT_LT(static_cast<int>(data_size), 64);
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ ReadDataRaw(
+ pipe.get(), data, &data_size, MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+
+ callback.Run(data);
+ }
+
+ void CreateNamedObject(
+ InterfaceRequest<sample::NamedObject> object_request) override {
+ EXPECT_TRUE(object_request.is_pending());
+ MakeStrongBinding(base::MakeUnique<SampleNamedObjectImpl>(),
+ std::move(object_request));
+ }
+
+ // These aren't called or implemented, but exist here to test that the
+ // methods are generated with the correct argument types for imported
+ // interfaces.
+ void RequestImportedInterface(
+ InterfaceRequest<imported::ImportedInterface> imported,
+ const RequestImportedInterfaceCallback& callback) override {}
+ void TakeImportedInterface(
+ imported::ImportedInterfacePtr imported,
+ const TakeImportedInterfaceCallback& callback) override {}
+
+ private:
+ ScopedMessagePipeHandle pipe1_;
+ Binding<sample::Factory> binding_;
+};
+
+class HandlePassingTest : public testing::Test {
+ public:
+ HandlePassingTest() {}
+
+ void TearDown() override { PumpMessages(); }
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ private:
+ base::MessageLoop loop_;
+};
+
+void DoStuff(bool* got_response,
+ std::string* got_text_reply,
+ const base::Closure& closure,
+ sample::ResponsePtr response,
+ const std::string& text_reply) {
+ *got_text_reply = text_reply;
+
+ if (response->pipe.is_valid()) {
+ std::string text2;
+ EXPECT_TRUE(ReadTextMessage(response->pipe.get(), &text2));
+
+ // Ensure that simply accessing response.pipe does not close it.
+ EXPECT_TRUE(response->pipe.is_valid());
+
+ EXPECT_EQ(std::string(kText2), text2);
+
+ // Do some more tests of handle passing:
+ ScopedMessagePipeHandle p = std::move(response->pipe);
+ EXPECT_TRUE(p.is_valid());
+ EXPECT_FALSE(response->pipe.is_valid());
+ }
+
+ *got_response = true;
+ closure.Run();
+}
+
+void DoStuff2(bool* got_response,
+ std::string* got_text_reply,
+ const base::Closure& closure,
+ const std::string& text_reply) {
+ *got_response = true;
+ *got_text_reply = text_reply;
+ closure.Run();
+}
+
+TEST_F(HandlePassingTest, Basic) {
+ sample::FactoryPtr factory;
+ SampleFactoryImpl factory_impl(MakeRequest(&factory));
+
+ MessagePipe pipe0;
+ EXPECT_TRUE(WriteTextMessage(pipe0.handle1.get(), kText1));
+
+ MessagePipe pipe1;
+ EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2));
+
+ imported::ImportedInterfacePtr imported;
+ base::RunLoop run_loop;
+ ImportedInterfaceImpl imported_impl(MakeRequest(&imported),
+ run_loop.QuitClosure());
+
+ sample::RequestPtr request(sample::Request::New(
+ 1, std::move(pipe1.handle0), base::nullopt, std::move(imported)));
+ bool got_response = false;
+ std::string got_text_reply;
+ base::RunLoop run_loop2;
+ factory->DoStuff(std::move(request), std::move(pipe0.handle0),
+ base::Bind(&DoStuff, &got_response, &got_text_reply,
+ run_loop2.QuitClosure()));
+
+ EXPECT_FALSE(got_response);
+ int count_before = ImportedInterfaceImpl::do_something_count();
+
+ run_loop.Run();
+ run_loop2.Run();
+
+ EXPECT_TRUE(got_response);
+ EXPECT_EQ(kText1, got_text_reply);
+ EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before);
+}
+
+TEST_F(HandlePassingTest, PassInvalid) {
+ sample::FactoryPtr factory;
+ SampleFactoryImpl factory_impl(MakeRequest(&factory));
+
+ sample::RequestPtr request(
+ sample::Request::New(1, ScopedMessagePipeHandle(), base::nullopt,
+ imported::ImportedInterfacePtr()));
+ bool got_response = false;
+ std::string got_text_reply;
+ base::RunLoop run_loop;
+ factory->DoStuff(std::move(request), ScopedMessagePipeHandle(),
+ base::Bind(&DoStuff, &got_response, &got_text_reply,
+ run_loop.QuitClosure()));
+
+ EXPECT_FALSE(got_response);
+
+ run_loop.Run();
+
+ EXPECT_TRUE(got_response);
+}
+
+// Verifies DataPipeConsumer can be passed and read from.
+TEST_F(HandlePassingTest, DataPipe) {
+ sample::FactoryPtr factory;
+ SampleFactoryImpl factory_impl(MakeRequest(&factory));
+
+ // Writes a string to a data pipe and passes the data pipe (consumer) to the
+ // factory.
+ ScopedDataPipeProducerHandle producer_handle;
+ ScopedDataPipeConsumerHandle consumer_handle;
+ MojoCreateDataPipeOptions options = {sizeof(MojoCreateDataPipeOptions),
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ 1,
+ 1024};
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateDataPipe(&options, &producer_handle, &consumer_handle));
+ std::string expected_text_reply = "got it";
+ // +1 for \0.
+ uint32_t data_size = static_cast<uint32_t>(expected_text_reply.size() + 1);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ WriteDataRaw(producer_handle.get(),
+ expected_text_reply.c_str(),
+ &data_size,
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+
+ bool got_response = false;
+ std::string got_text_reply;
+ base::RunLoop run_loop;
+ factory->DoStuff2(std::move(consumer_handle),
+ base::Bind(&DoStuff2, &got_response, &got_text_reply,
+ run_loop.QuitClosure()));
+
+ EXPECT_FALSE(got_response);
+
+ run_loop.Run();
+
+ EXPECT_TRUE(got_response);
+ EXPECT_EQ(expected_text_reply, got_text_reply);
+}
+
+TEST_F(HandlePassingTest, PipesAreClosed) {
+ sample::FactoryPtr factory;
+ SampleFactoryImpl factory_impl(MakeRequest(&factory));
+
+ MessagePipe extra_pipe;
+
+ MojoHandle handle0_value = extra_pipe.handle0.get().value();
+ MojoHandle handle1_value = extra_pipe.handle1.get().value();
+
+ {
+ std::vector<ScopedMessagePipeHandle> pipes(2);
+ pipes[0] = std::move(extra_pipe.handle0);
+ pipes[1] = std::move(extra_pipe.handle1);
+
+ sample::RequestPtr request(sample::Request::New());
+ request->more_pipes = std::move(pipes);
+
+ factory->DoStuff(std::move(request), ScopedMessagePipeHandle(),
+ sample::Factory::DoStuffCallback());
+ }
+
+ // We expect the pipes to have been closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle0_value));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle1_value));
+}
+
+TEST_F(HandlePassingTest, CreateNamedObject) {
+ sample::FactoryPtr factory;
+ SampleFactoryImpl factory_impl(MakeRequest(&factory));
+
+ sample::NamedObjectPtr object1;
+ EXPECT_FALSE(object1);
+
+ InterfaceRequest<sample::NamedObject> object1_request(&object1);
+ EXPECT_TRUE(object1_request.is_pending());
+ factory->CreateNamedObject(std::move(object1_request));
+ EXPECT_FALSE(object1_request.is_pending()); // We've passed the request.
+
+ ASSERT_TRUE(object1);
+ object1->SetName("object1");
+
+ sample::NamedObjectPtr object2;
+ factory->CreateNamedObject(MakeRequest(&object2));
+ object2->SetName("object2");
+
+ base::RunLoop run_loop, run_loop2;
+ std::string name1;
+ object1->GetName(MakeStringRecorder(&name1, run_loop.QuitClosure()));
+
+ std::string name2;
+ object2->GetName(MakeStringRecorder(&name2, run_loop2.QuitClosure()));
+
+ run_loop.Run();
+ run_loop2.Run();
+
+ EXPECT_EQ(std::string("object1"), name1);
+ EXPECT_EQ(std::string("object2"), name2);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/hash_unittest.cc b/mojo/public/cpp/bindings/tests/hash_unittest.cc
new file mode 100644
index 0000000000..9ce1f5bc7b
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/hash_unittest.cc
@@ -0,0 +1,35 @@
+// 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 "mojo/public/cpp/bindings/lib/hash_util.h"
+
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using HashTest = testing::Test;
+
+TEST_F(HashTest, NestedStruct) {
+ // Just check that this template instantiation compiles.
+ ASSERT_EQ(
+ ::mojo::internal::Hash(::mojo::internal::kHashSeed,
+ SimpleNestedStruct::New(ContainsOther::New(1))),
+ ::mojo::internal::Hash(::mojo::internal::kHashSeed,
+ SimpleNestedStruct::New(ContainsOther::New(1))));
+}
+
+TEST_F(HashTest, UnmappedNativeStruct) {
+ // Just check that this template instantiation compiles.
+ ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed,
+ UnmappedNativeStruct::New()),
+ ::mojo::internal::Hash(::mojo::internal::kHashSeed,
+ UnmappedNativeStruct::New()));
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
new file mode 100644
index 0000000000..431a844250
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
@@ -0,0 +1,937 @@
+// Copyright 2013 The Chromium Authors. 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/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/scoping.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+typedef base::Callback<void(double)> CalcCallback;
+
+class MathCalculatorImpl : public math::Calculator {
+ public:
+ explicit MathCalculatorImpl(InterfaceRequest<math::Calculator> request)
+ : total_(0.0), binding_(this, std::move(request)) {}
+ ~MathCalculatorImpl() override {}
+
+ void Clear(const CalcCallback& callback) override {
+ total_ = 0.0;
+ callback.Run(total_);
+ }
+
+ void Add(double value, const CalcCallback& callback) override {
+ total_ += value;
+ callback.Run(total_);
+ }
+
+ void Multiply(double value, const CalcCallback& callback) override {
+ total_ *= value;
+ callback.Run(total_);
+ }
+
+ Binding<math::Calculator>* binding() { return &binding_; }
+
+ private:
+ double total_;
+ Binding<math::Calculator> binding_;
+};
+
+class MathCalculatorUI {
+ public:
+ explicit MathCalculatorUI(math::CalculatorPtr calculator)
+ : calculator_(std::move(calculator)),
+ output_(0.0) {}
+
+ bool encountered_error() const { return calculator_.encountered_error(); }
+ void set_connection_error_handler(const base::Closure& closure) {
+ calculator_.set_connection_error_handler(closure);
+ }
+
+ void Add(double value, const base::Closure& closure) {
+ calculator_->Add(
+ value,
+ base::Bind(&MathCalculatorUI::Output, base::Unretained(this), closure));
+ }
+
+ void Multiply(double value, const base::Closure& closure) {
+ calculator_->Multiply(
+ value,
+ base::Bind(&MathCalculatorUI::Output, base::Unretained(this), closure));
+ }
+
+ double GetOutput() const { return output_; }
+
+ math::CalculatorPtr& GetInterfacePtr() { return calculator_; }
+
+ private:
+ void Output(const base::Closure& closure, double output) {
+ output_ = output;
+ if (!closure.is_null())
+ closure.Run();
+ }
+
+ math::CalculatorPtr calculator_;
+ double output_;
+ base::Closure closure_;
+};
+
+class SelfDestructingMathCalculatorUI {
+ public:
+ explicit SelfDestructingMathCalculatorUI(math::CalculatorPtr calculator)
+ : calculator_(std::move(calculator)), nesting_level_(0) {
+ ++num_instances_;
+ }
+
+ void BeginTest(bool nested, const base::Closure& closure) {
+ nesting_level_ = nested ? 2 : 1;
+ calculator_->Add(
+ 1.0,
+ base::Bind(&SelfDestructingMathCalculatorUI::Output,
+ base::Unretained(this), closure));
+ }
+
+ static int num_instances() { return num_instances_; }
+
+ void Output(const base::Closure& closure, double value) {
+ if (--nesting_level_ > 0) {
+ // Add some more and wait for re-entrant call to Output!
+ calculator_->Add(
+ 1.0,
+ base::Bind(&SelfDestructingMathCalculatorUI::Output,
+ base::Unretained(this), closure));
+ } else {
+ closure.Run();
+ delete this;
+ }
+ }
+
+ private:
+ ~SelfDestructingMathCalculatorUI() { --num_instances_; }
+
+ math::CalculatorPtr calculator_;
+ int nesting_level_;
+ static int num_instances_;
+};
+
+// static
+int SelfDestructingMathCalculatorUI::num_instances_ = 0;
+
+class ReentrantServiceImpl : public sample::Service {
+ public:
+ ~ReentrantServiceImpl() override {}
+
+ explicit ReentrantServiceImpl(InterfaceRequest<sample::Service> request)
+ : call_depth_(0),
+ max_call_depth_(0),
+ binding_(this, std::move(request)) {}
+
+ int max_call_depth() { return max_call_depth_; }
+
+ void Frobinate(sample::FooPtr foo,
+ sample::Service::BazOptions baz,
+ sample::PortPtr port,
+ const sample::Service::FrobinateCallback& callback) override {
+ max_call_depth_ = std::max(++call_depth_, max_call_depth_);
+ if (call_depth_ == 1) {
+ EXPECT_TRUE(binding_.WaitForIncomingMethodCall());
+ }
+ call_depth_--;
+ callback.Run(5);
+ }
+
+ void GetPort(mojo::InterfaceRequest<sample::Port> port) override {}
+
+ private:
+ int call_depth_;
+ int max_call_depth_;
+ Binding<sample::Service> binding_;
+};
+
+class IntegerAccessorImpl : public sample::IntegerAccessor {
+ public:
+ IntegerAccessorImpl() : integer_(0) {}
+ ~IntegerAccessorImpl() override {}
+
+ int64_t integer() const { return integer_; }
+
+ void set_closure(const base::Closure& closure) { closure_ = closure; }
+
+ private:
+ // sample::IntegerAccessor implementation.
+ void GetInteger(const GetIntegerCallback& callback) override {
+ callback.Run(integer_, sample::Enum::VALUE);
+ }
+ void SetInteger(int64_t data, sample::Enum type) override {
+ integer_ = data;
+ if (!closure_.is_null()) {
+ closure_.Run();
+ closure_.Reset();
+ }
+ }
+
+ int64_t integer_;
+ base::Closure closure_;
+};
+
+class InterfacePtrTest : public testing::Test {
+ public:
+ InterfacePtrTest() {}
+ ~InterfacePtrTest() override { base::RunLoop().RunUntilIdle(); }
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ private:
+ base::MessageLoop loop_;
+};
+
+void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
+ *flag = true;
+ closure.Run();
+}
+
+void IgnoreValueAndRunClosure(const base::Closure& closure, int32_t value) {
+ closure.Run();
+}
+
+void ExpectValueAndRunClosure(uint32_t expected_value,
+ const base::Closure& closure,
+ uint32_t value) {
+ EXPECT_EQ(expected_value, value);
+ closure.Run();
+}
+
+TEST_F(InterfacePtrTest, IsBound) {
+ math::CalculatorPtr calc;
+ EXPECT_FALSE(calc.is_bound());
+ MathCalculatorImpl calc_impl(MakeRequest(&calc));
+ EXPECT_TRUE(calc.is_bound());
+}
+
+TEST_F(InterfacePtrTest, EndToEnd) {
+ math::CalculatorPtr calc;
+ MathCalculatorImpl calc_impl(MakeRequest(&calc));
+
+ // Suppose this is instantiated in a process that has pipe1_.
+ MathCalculatorUI calculator_ui(std::move(calc));
+
+ base::RunLoop run_loop, run_loop2;
+ calculator_ui.Add(2.0, run_loop.QuitClosure());
+ calculator_ui.Multiply(5.0, run_loop2.QuitClosure());
+ run_loop.Run();
+ run_loop2.Run();
+
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+}
+
+TEST_F(InterfacePtrTest, EndToEnd_Synchronous) {
+ math::CalculatorPtr calc;
+ MathCalculatorImpl calc_impl(MakeRequest(&calc));
+
+ // Suppose this is instantiated in a process that has pipe1_.
+ MathCalculatorUI calculator_ui(std::move(calc));
+
+ EXPECT_EQ(0.0, calculator_ui.GetOutput());
+
+ base::RunLoop run_loop;
+ calculator_ui.Add(2.0, run_loop.QuitClosure());
+ EXPECT_EQ(0.0, calculator_ui.GetOutput());
+ calc_impl.binding()->WaitForIncomingMethodCall();
+ run_loop.Run();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+
+ base::RunLoop run_loop2;
+ calculator_ui.Multiply(5.0, run_loop2.QuitClosure());
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+ calc_impl.binding()->WaitForIncomingMethodCall();
+ run_loop2.Run();
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+}
+
+TEST_F(InterfacePtrTest, Movable) {
+ math::CalculatorPtr a;
+ math::CalculatorPtr b;
+ MathCalculatorImpl calc_impl(MakeRequest(&b));
+
+ EXPECT_TRUE(!a);
+ EXPECT_FALSE(!b);
+
+ a = std::move(b);
+
+ EXPECT_FALSE(!a);
+ EXPECT_TRUE(!b);
+}
+
+TEST_F(InterfacePtrTest, Resettable) {
+ math::CalculatorPtr a;
+
+ EXPECT_TRUE(!a);
+
+ MessagePipe pipe;
+
+ // Save this so we can test it later.
+ Handle handle = pipe.handle0.get();
+
+ a = MakeProxy(
+ InterfacePtrInfo<math::Calculator>(std::move(pipe.handle0), 0u));
+
+ EXPECT_FALSE(!a);
+
+ a.reset();
+
+ EXPECT_TRUE(!a);
+ EXPECT_FALSE(a.internal_state()->is_bound());
+
+ // Test that handle was closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle));
+}
+
+TEST_F(InterfacePtrTest, BindInvalidHandle) {
+ math::CalculatorPtr ptr;
+ EXPECT_FALSE(ptr.get());
+ EXPECT_FALSE(ptr);
+
+ ptr.Bind(InterfacePtrInfo<math::Calculator>());
+ EXPECT_FALSE(ptr.get());
+ EXPECT_FALSE(ptr);
+}
+
+TEST_F(InterfacePtrTest, EncounteredError) {
+ math::CalculatorPtr proxy;
+ MathCalculatorImpl calc_impl(MakeRequest(&proxy));
+
+ MathCalculatorUI calculator_ui(std::move(proxy));
+
+ base::RunLoop run_loop;
+ calculator_ui.Add(2.0, run_loop.QuitClosure());
+ run_loop.Run();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ calculator_ui.Multiply(5.0, base::Closure());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ // Close the server.
+ calc_impl.binding()->Close();
+
+ // The state change isn't picked up locally yet.
+ base::RunLoop run_loop2;
+ calculator_ui.set_connection_error_handler(run_loop2.QuitClosure());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ run_loop2.Run();
+
+ // OK, now we see the error.
+ EXPECT_TRUE(calculator_ui.encountered_error());
+}
+
+TEST_F(InterfacePtrTest, EncounteredErrorCallback) {
+ math::CalculatorPtr proxy;
+ MathCalculatorImpl calc_impl(MakeRequest(&proxy));
+
+ bool encountered_error = false;
+ base::RunLoop run_loop;
+ proxy.set_connection_error_handler(
+ base::Bind(&SetFlagAndRunClosure, &encountered_error,
+ run_loop.QuitClosure()));
+
+ MathCalculatorUI calculator_ui(std::move(proxy));
+
+ base::RunLoop run_loop2;
+ calculator_ui.Add(2.0, run_loop2.QuitClosure());
+ run_loop2.Run();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ calculator_ui.Multiply(5.0, base::Closure());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ // Close the server.
+ calc_impl.binding()->Close();
+
+ // The state change isn't picked up locally yet.
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ run_loop.Run();
+
+ // OK, now we see the error.
+ EXPECT_TRUE(calculator_ui.encountered_error());
+
+ // We should have also been able to observe the error through the error
+ // handler.
+ EXPECT_TRUE(encountered_error);
+}
+
+TEST_F(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) {
+ math::CalculatorPtr proxy;
+ MathCalculatorImpl calc_impl(MakeRequest(&proxy));
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances());
+
+ SelfDestructingMathCalculatorUI* impl =
+ new SelfDestructingMathCalculatorUI(std::move(proxy));
+ base::RunLoop run_loop;
+ impl->BeginTest(false, run_loop.QuitClosure());
+ run_loop.Run();
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances());
+}
+
+TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) {
+ math::CalculatorPtr proxy;
+ MathCalculatorImpl calc_impl(MakeRequest(&proxy));
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances());
+
+ SelfDestructingMathCalculatorUI* impl =
+ new SelfDestructingMathCalculatorUI(std::move(proxy));
+ base::RunLoop run_loop;
+ impl->BeginTest(true, run_loop.QuitClosure());
+ run_loop.Run();
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances());
+}
+
+TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) {
+ sample::ServicePtr proxy;
+ ReentrantServiceImpl impl(MakeRequest(&proxy));
+
+ base::RunLoop run_loop, run_loop2;
+ proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ base::Bind(&IgnoreValueAndRunClosure,
+ run_loop.QuitClosure()));
+ proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ base::Bind(&IgnoreValueAndRunClosure,
+ run_loop2.QuitClosure()));
+
+ run_loop.Run();
+ run_loop2.Run();
+
+ EXPECT_EQ(2, impl.max_call_depth());
+}
+
+TEST_F(InterfacePtrTest, QueryVersion) {
+ IntegerAccessorImpl impl;
+ sample::IntegerAccessorPtr ptr;
+ Binding<sample::IntegerAccessor> binding(&impl, MakeRequest(&ptr));
+
+ EXPECT_EQ(0u, ptr.version());
+
+ base::RunLoop run_loop;
+ ptr.QueryVersion(base::Bind(&ExpectValueAndRunClosure, 3u,
+ run_loop.QuitClosure()));
+ run_loop.Run();
+
+ EXPECT_EQ(3u, ptr.version());
+}
+
+TEST_F(InterfacePtrTest, RequireVersion) {
+ IntegerAccessorImpl impl;
+ sample::IntegerAccessorPtr ptr;
+ Binding<sample::IntegerAccessor> binding(&impl, MakeRequest(&ptr));
+
+ EXPECT_EQ(0u, ptr.version());
+
+ ptr.RequireVersion(1u);
+ EXPECT_EQ(1u, ptr.version());
+ base::RunLoop run_loop;
+ impl.set_closure(run_loop.QuitClosure());
+ ptr->SetInteger(123, sample::Enum::VALUE);
+ run_loop.Run();
+ EXPECT_FALSE(ptr.encountered_error());
+ EXPECT_EQ(123, impl.integer());
+
+ ptr.RequireVersion(3u);
+ EXPECT_EQ(3u, ptr.version());
+ base::RunLoop run_loop2;
+ impl.set_closure(run_loop2.QuitClosure());
+ ptr->SetInteger(456, sample::Enum::VALUE);
+ run_loop2.Run();
+ EXPECT_FALSE(ptr.encountered_error());
+ EXPECT_EQ(456, impl.integer());
+
+ // Require a version that is not supported by the impl side.
+ ptr.RequireVersion(4u);
+ // This value is set to the input of RequireVersion() synchronously.
+ EXPECT_EQ(4u, ptr.version());
+ base::RunLoop run_loop3;
+ ptr.set_connection_error_handler(run_loop3.QuitClosure());
+ ptr->SetInteger(789, sample::Enum::VALUE);
+ run_loop3.Run();
+ EXPECT_TRUE(ptr.encountered_error());
+ // The call to SetInteger() after RequireVersion(4u) is ignored.
+ EXPECT_EQ(456, impl.integer());
+}
+
+class StrongMathCalculatorImpl : public math::Calculator {
+ public:
+ StrongMathCalculatorImpl(bool* destroyed) : destroyed_(destroyed) {}
+ ~StrongMathCalculatorImpl() override { *destroyed_ = true; }
+
+ // math::Calculator implementation.
+ void Clear(const CalcCallback& callback) override { callback.Run(total_); }
+
+ void Add(double value, const CalcCallback& callback) override {
+ total_ += value;
+ callback.Run(total_);
+ }
+
+ void Multiply(double value, const CalcCallback& callback) override {
+ total_ *= value;
+ callback.Run(total_);
+ }
+
+ private:
+ double total_ = 0.0;
+ bool* destroyed_;
+};
+
+TEST(StrongConnectorTest, Math) {
+ base::MessageLoop loop;
+
+ bool error_received = false;
+ bool destroyed = false;
+ math::CalculatorPtr calc;
+ base::RunLoop run_loop;
+
+ auto binding =
+ MakeStrongBinding(base::MakeUnique<StrongMathCalculatorImpl>(&destroyed),
+ MakeRequest(&calc));
+ binding->set_connection_error_handler(base::Bind(
+ &SetFlagAndRunClosure, &error_received, run_loop.QuitClosure()));
+
+ {
+ // Suppose this is instantiated in a process that has the other end of the
+ // message pipe.
+ MathCalculatorUI calculator_ui(std::move(calc));
+
+ base::RunLoop run_loop, run_loop2;
+ calculator_ui.Add(2.0, run_loop.QuitClosure());
+ calculator_ui.Multiply(5.0, run_loop2.QuitClosure());
+ run_loop.Run();
+ run_loop2.Run();
+
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+ EXPECT_FALSE(error_received);
+ EXPECT_FALSE(destroyed);
+ }
+ // Destroying calculator_ui should close the pipe and generate an error on the
+ // other
+ // end which will destroy the instance since it is strongly bound.
+
+ run_loop.Run();
+ EXPECT_TRUE(error_received);
+ EXPECT_TRUE(destroyed);
+}
+
+class WeakMathCalculatorImpl : public math::Calculator {
+ public:
+ WeakMathCalculatorImpl(ScopedMessagePipeHandle handle,
+ bool* error_received,
+ bool* destroyed,
+ const base::Closure& closure)
+ : error_received_(error_received),
+ destroyed_(destroyed),
+ closure_(closure),
+ binding_(this, std::move(handle)) {
+ binding_.set_connection_error_handler(
+ base::Bind(&SetFlagAndRunClosure, error_received_, closure_));
+ }
+ ~WeakMathCalculatorImpl() override { *destroyed_ = true; }
+
+ void Clear(const CalcCallback& callback) override { callback.Run(total_); }
+
+ void Add(double value, const CalcCallback& callback) override {
+ total_ += value;
+ callback.Run(total_);
+ }
+
+ void Multiply(double value, const CalcCallback& callback) override {
+ total_ *= value;
+ callback.Run(total_);
+ }
+
+ private:
+ double total_ = 0.0;
+ bool* error_received_;
+ bool* destroyed_;
+ base::Closure closure_;
+
+ Binding<math::Calculator> binding_;
+};
+
+TEST(WeakConnectorTest, Math) {
+ base::MessageLoop loop;
+
+ bool error_received = false;
+ bool destroyed = false;
+ MessagePipe pipe;
+ base::RunLoop run_loop;
+ WeakMathCalculatorImpl impl(std::move(pipe.handle0), &error_received,
+ &destroyed, run_loop.QuitClosure());
+
+ math::CalculatorPtr calc;
+ calc.Bind(InterfacePtrInfo<math::Calculator>(std::move(pipe.handle1), 0u));
+
+ {
+ // Suppose this is instantiated in a process that has the other end of the
+ // message pipe.
+ MathCalculatorUI calculator_ui(std::move(calc));
+
+ base::RunLoop run_loop, run_loop2;
+ calculator_ui.Add(2.0, run_loop.QuitClosure());
+ calculator_ui.Multiply(5.0, run_loop2.QuitClosure());
+ run_loop.Run();
+ run_loop2.Run();
+
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+ EXPECT_FALSE(error_received);
+ EXPECT_FALSE(destroyed);
+ // Destroying calculator_ui should close the pipe and generate an error on
+ // the other
+ // end which will destroy the instance since it is strongly bound.
+ }
+
+ run_loop.Run();
+ EXPECT_TRUE(error_received);
+ EXPECT_FALSE(destroyed);
+}
+
+class CImpl : public C {
+ public:
+ CImpl(bool* d_called, const base::Closure& closure)
+ : d_called_(d_called), closure_(closure) {}
+ ~CImpl() override {}
+
+ private:
+ void D() override {
+ *d_called_ = true;
+ closure_.Run();
+ }
+
+ bool* d_called_;
+ base::Closure closure_;
+};
+
+class BImpl : public B {
+ public:
+ BImpl(bool* d_called, const base::Closure& closure)
+ : d_called_(d_called), closure_(closure) {}
+ ~BImpl() override {}
+
+ private:
+ void GetC(InterfaceRequest<C> c) override {
+ MakeStrongBinding(base::MakeUnique<CImpl>(d_called_, closure_),
+ std::move(c));
+ }
+
+ bool* d_called_;
+ base::Closure closure_;
+};
+
+class AImpl : public A {
+ public:
+ AImpl(InterfaceRequest<A> request, const base::Closure& closure)
+ : d_called_(false), binding_(this, std::move(request)),
+ closure_(closure) {}
+ ~AImpl() override {}
+
+ bool d_called() const { return d_called_; }
+
+ private:
+ void GetB(InterfaceRequest<B> b) override {
+ MakeStrongBinding(base::MakeUnique<BImpl>(&d_called_, closure_),
+ std::move(b));
+ }
+
+ bool d_called_;
+ Binding<A> binding_;
+ base::Closure closure_;
+};
+
+TEST_F(InterfacePtrTest, Scoping) {
+ APtr a;
+ base::RunLoop run_loop;
+ AImpl a_impl(MakeRequest(&a), run_loop.QuitClosure());
+
+ EXPECT_FALSE(a_impl.d_called());
+
+ {
+ BPtr b;
+ a->GetB(MakeRequest(&b));
+ CPtr c;
+ b->GetC(MakeRequest(&c));
+ c->D();
+ }
+
+ // While B & C have fallen out of scope, the pipes will remain until they are
+ // flushed.
+ EXPECT_FALSE(a_impl.d_called());
+ run_loop.Run();
+ EXPECT_TRUE(a_impl.d_called());
+}
+
+class PingTestImpl : public sample::PingTest {
+ public:
+ explicit PingTestImpl(InterfaceRequest<sample::PingTest> request)
+ : binding_(this, std::move(request)) {}
+ ~PingTestImpl() override {}
+
+ private:
+ // sample::PingTest:
+ void Ping(const PingCallback& callback) override { callback.Run(); }
+
+ Binding<sample::PingTest> binding_;
+};
+
+// Tests that FuseProxy does what it's supposed to do.
+TEST_F(InterfacePtrTest, Fusion) {
+ sample::PingTestPtr proxy;
+ PingTestImpl impl(MakeRequest(&proxy));
+
+ // Create another PingTest pipe.
+ sample::PingTestPtr ptr;
+ sample::PingTestRequest request(&ptr);
+
+ // Fuse the new pipe to the one hanging off |impl|.
+ EXPECT_TRUE(FuseInterface(std::move(request), proxy.PassInterface()));
+
+ // Ping!
+ bool called = false;
+ base::RunLoop loop;
+ ptr->Ping(base::Bind(&SetFlagAndRunClosure, &called, loop.QuitClosure()));
+ loop.Run();
+ EXPECT_TRUE(called);
+}
+
+void Fail() {
+ FAIL() << "Unexpected connection error";
+}
+
+TEST_F(InterfacePtrTest, FlushForTesting) {
+ math::CalculatorPtr calc;
+ MathCalculatorImpl calc_impl(MakeRequest(&calc));
+ calc.set_connection_error_handler(base::Bind(&Fail));
+
+ MathCalculatorUI calculator_ui(std::move(calc));
+
+ calculator_ui.Add(2.0, base::Bind(&base::DoNothing));
+ calculator_ui.GetInterfacePtr().FlushForTesting();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+
+ calculator_ui.Multiply(5.0, base::Bind(&base::DoNothing));
+ calculator_ui.GetInterfacePtr().FlushForTesting();
+
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+}
+
+void SetBool(bool* value) {
+ *value = true;
+}
+
+TEST_F(InterfacePtrTest, FlushForTestingWithClosedPeer) {
+ math::CalculatorPtr calc;
+ MakeRequest(&calc);
+ bool called = false;
+ calc.set_connection_error_handler(base::Bind(&SetBool, &called));
+ calc.FlushForTesting();
+ EXPECT_TRUE(called);
+ calc.FlushForTesting();
+}
+
+TEST_F(InterfacePtrTest, ConnectionErrorWithReason) {
+ math::CalculatorPtr calc;
+ MathCalculatorImpl calc_impl(MakeRequest(&calc));
+
+ base::RunLoop run_loop;
+ calc.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(42u, custom_reason);
+ EXPECT_EQ("hey", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ calc_impl.binding()->CloseWithReason(42u, "hey");
+
+ run_loop.Run();
+}
+
+TEST_F(InterfacePtrTest, InterfaceRequestResetWithReason) {
+ math::CalculatorPtr calc;
+ auto request = MakeRequest(&calc);
+
+ base::RunLoop run_loop;
+ calc.set_connection_error_with_reason_handler(base::Bind(
+ [](const base::Closure& quit_closure, uint32_t custom_reason,
+ const std::string& description) {
+ EXPECT_EQ(88u, custom_reason);
+ EXPECT_EQ("greetings", description);
+ quit_closure.Run();
+ },
+ run_loop.QuitClosure()));
+
+ request.ResetWithReason(88u, "greetings");
+
+ run_loop.Run();
+}
+
+TEST_F(InterfacePtrTest, CallbackIsPassedInterfacePtr) {
+ sample::PingTestPtr ptr;
+ sample::PingTestRequest request(&ptr);
+
+ base::RunLoop run_loop;
+
+ // Make a call with the proxy's lifetime bound to the response callback.
+ sample::PingTest* raw_proxy = ptr.get();
+ ptr.set_connection_error_handler(run_loop.QuitClosure());
+ raw_proxy->Ping(
+ base::Bind([](sample::PingTestPtr ptr) {}, base::Passed(&ptr)));
+
+ // Trigger an error on |ptr|. This will ultimately lead to the proxy's
+ // response callbacks being destroyed, which will in turn lead to the proxy
+ // being destroyed. This should not crash.
+ request.PassMessagePipe();
+ run_loop.Run();
+}
+
+TEST_F(InterfacePtrTest, ConnectionErrorHandlerOwnsInterfacePtr) {
+ sample::PingTestPtr* ptr = new sample::PingTestPtr;
+ sample::PingTestRequest request(ptr);
+
+ base::RunLoop run_loop;
+
+ // Make a call with |ptr|'s lifetime bound to the connection error handler
+ // callback.
+ ptr->set_connection_error_handler(base::Bind(
+ [](const base::Closure& quit, sample::PingTestPtr* ptr) {
+ ptr->reset();
+ quit.Run();
+ },
+ run_loop.QuitClosure(), base::Owned(ptr)));
+
+ // Trigger an error on |ptr|. In the error handler |ptr| is reset. This
+ // shouldn't immediately destroy the callback (and |ptr| that it owns), before
+ // the callback is completed.
+ request.PassMessagePipe();
+ run_loop.Run();
+}
+
+TEST_F(InterfacePtrTest, ThreadSafeInterfacePointer) {
+ math::CalculatorPtr ptr;
+ MathCalculatorImpl calc_impl(MakeRequest(&ptr));
+ scoped_refptr<math::ThreadSafeCalculatorPtr> thread_safe_ptr =
+ math::ThreadSafeCalculatorPtr::Create(std::move(ptr));
+
+ base::RunLoop run_loop;
+
+ // Create and start the thread from where we'll call the interface pointer.
+ base::Thread other_thread("service test thread");
+ other_thread.Start();
+
+ auto run_method = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ const scoped_refptr<math::ThreadSafeCalculatorPtr>& thread_safe_ptr) {
+ auto calc_callback = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ base::PlatformThreadId thread_id,
+ double result) {
+ EXPECT_EQ(123, result);
+ // Validate the callback is invoked on the calling thread.
+ EXPECT_EQ(thread_id, base::PlatformThread::CurrentId());
+ // Notify the run_loop to quit.
+ main_task_runner->PostTask(FROM_HERE, quit_closure);
+ });
+ (*thread_safe_ptr)->Add(
+ 123, base::Bind(calc_callback, main_task_runner, quit_closure,
+ base::PlatformThread::CurrentId()));
+ },
+ base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
+ thread_safe_ptr);
+ other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method);
+
+ // Block until the method callback is called on the background thread.
+ run_loop.Run();
+}
+
+TEST_F(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) {
+ // Create and start the thread from where we'll bind the interface pointer.
+ base::Thread other_thread("service test thread");
+ other_thread.Start();
+ const scoped_refptr<base::SingleThreadTaskRunner>& other_thread_task_runner =
+ other_thread.message_loop()->task_runner();
+
+ math::CalculatorPtr ptr;
+ math::CalculatorRequest request(&ptr);
+
+ // Create a ThreadSafeInterfacePtr that we'll bind from a different thread.
+ scoped_refptr<math::ThreadSafeCalculatorPtr> thread_safe_ptr =
+ math::ThreadSafeCalculatorPtr::Create(ptr.PassInterface(),
+ other_thread_task_runner);
+ ASSERT_TRUE(thread_safe_ptr);
+
+ MathCalculatorImpl* math_calc_impl = nullptr;
+ {
+ base::RunLoop run_loop;
+ auto run_method = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ const scoped_refptr<math::ThreadSafeCalculatorPtr>& thread_safe_ptr,
+ math::CalculatorRequest request,
+ MathCalculatorImpl** math_calc_impl) {
+ math::CalculatorPtr ptr;
+ // In real life, the implementation would have a legitimate owner.
+ *math_calc_impl = new MathCalculatorImpl(std::move(request));
+ main_task_runner->PostTask(FROM_HERE, quit_closure);
+ },
+ base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
+ thread_safe_ptr, base::Passed(&request), &math_calc_impl);
+ other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method);
+ run_loop.Run();
+ }
+
+ {
+ // The interface ptr is bound, we can call methods on it.
+ auto calc_callback =
+ base::Bind([](const base::Closure& quit_closure, double result) {
+ EXPECT_EQ(123, result);
+ quit_closure.Run();
+ });
+ base::RunLoop run_loop;
+ (*thread_safe_ptr)
+ ->Add(123, base::Bind(calc_callback, run_loop.QuitClosure()));
+ // Block until the method callback is called.
+ run_loop.Run();
+ }
+
+ other_thread_task_runner->DeleteSoon(FROM_HERE, math_calc_impl);
+
+ // Reset the pointer now so the InterfacePtr associated resources can be
+ // deleted before the background thread's message loop is invalidated.
+ thread_safe_ptr = nullptr;
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/map_unittest.cc b/mojo/public/cpp/bindings/tests/map_unittest.cc
new file mode 100644
index 0000000000..8d630a5862
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/map_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2017 The Chromium Authors. 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 <unordered_map>
+#include <utility>
+
+#include "mojo/public/cpp/bindings/tests/rect_chromium.h"
+#include "mojo/public/interfaces/bindings/tests/rect.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+TEST(MapTest, StructKey) {
+ std::unordered_map<RectPtr, int32_t> map;
+ map.insert(std::make_pair(Rect::New(1, 2, 3, 4), 123));
+
+ RectPtr key = Rect::New(1, 2, 3, 4);
+ ASSERT_NE(map.end(), map.find(key));
+ ASSERT_EQ(123, map.find(key)->second);
+
+ map.erase(key);
+ ASSERT_EQ(0u, map.size());
+}
+
+TEST(MapTest, TypemappedStructKey) {
+ std::unordered_map<ContainsHashablePtr, int32_t> map;
+ map.insert(
+ std::make_pair(ContainsHashable::New(RectChromium(1, 2, 3, 4)), 123));
+
+ ContainsHashablePtr key = ContainsHashable::New(RectChromium(1, 2, 3, 4));
+ ASSERT_NE(map.end(), map.find(key));
+ ASSERT_EQ(123, map.find(key)->second);
+
+ map.erase(key);
+ ASSERT_EQ(0u, map.size());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/message_queue.cc b/mojo/public/cpp/bindings/tests/message_queue.cc
new file mode 100644
index 0000000000..32ed76328c
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/message_queue.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 "mojo/public/cpp/bindings/tests/message_queue.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace test {
+
+MessageQueue::MessageQueue() {
+}
+
+MessageQueue::~MessageQueue() {
+}
+
+bool MessageQueue::IsEmpty() const {
+ return queue_.empty();
+}
+
+void MessageQueue::Push(Message* message) {
+ queue_.emplace(std::move(*message));
+}
+
+void MessageQueue::Pop(Message* message) {
+ DCHECK(!queue_.empty());
+ *message = std::move(queue_.front());
+ Pop();
+}
+
+void MessageQueue::Pop() {
+ DCHECK(!queue_.empty());
+ queue_.pop();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/message_queue.h b/mojo/public/cpp/bindings/tests/message_queue.h
new file mode 100644
index 0000000000..8f13f7ab6d
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/message_queue.h
@@ -0,0 +1,44 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_
+
+#include <queue>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace test {
+
+// A queue for Message objects.
+class MessageQueue {
+ public:
+ MessageQueue();
+ ~MessageQueue();
+
+ bool IsEmpty() const;
+
+ // This method copies the message data and steals ownership of its handles.
+ void Push(Message* message);
+
+ // Removes the next message from the queue, copying its data and transferring
+ // ownership of its handles to the given |message|.
+ void Pop(Message* message);
+
+ size_t size() const { return queue_.size(); }
+
+ private:
+ void Pop();
+
+ std::queue<Message> queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageQueue);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_
diff --git a/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h b/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h
new file mode 100644
index 0000000000..b3bbe27421
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h
@@ -0,0 +1,29 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(MOJO_TEST_BLINK_IMPLEMENTATION)
+#define MOJO_TEST_BLINK_EXPORT __declspec(dllexport)
+#else
+#define MOJO_TEST_BLINK_EXPORT __declspec(dllimport)
+#endif // defined(MOJO_TEST_BLINK_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(MOJO_TEST_BLINK_IMPLEMENTATION)
+#define MOJO_TEST_BLINK_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_TEST_BLINK_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define MOJO_TEST_BLINK_EXPORT
+#endif
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_
diff --git a/mojo/public/cpp/bindings/tests/mojo_test_export.h b/mojo/public/cpp/bindings/tests/mojo_test_export.h
new file mode 100644
index 0000000000..a48a1ba8ae
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/mojo_test_export.h
@@ -0,0 +1,29 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(MOJO_TEST_IMPLEMENTATION)
+#define MOJO_TEST_EXPORT __declspec(dllexport)
+#else
+#define MOJO_TEST_EXPORT __declspec(dllimport)
+#endif // defined(MOJO_TEST_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(MOJO_TEST_IMPLEMENTATION)
+#define MOJO_TEST_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_TEST_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define MOJO_TEST_EXPORT
+#endif
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_
diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
new file mode 100644
index 0000000000..89509283c4
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/bindings/tests/message_queue.h"
+#include "mojo/public/cpp/bindings/tests/router_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using mojo::internal::MultiplexRouter;
+
+class MultiplexRouterTest : public testing::Test {
+ public:
+ MultiplexRouterTest() {}
+
+ void SetUp() override {
+ MessagePipe pipe;
+ router0_ = new MultiplexRouter(std::move(pipe.handle0),
+ MultiplexRouter::MULTI_INTERFACE, false,
+ base::ThreadTaskRunnerHandle::Get());
+ router1_ = new MultiplexRouter(std::move(pipe.handle1),
+ MultiplexRouter::MULTI_INTERFACE, true,
+ base::ThreadTaskRunnerHandle::Get());
+ ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0_,
+ &endpoint1_);
+ auto id = router0_->AssociateInterface(std::move(endpoint1_));
+ endpoint1_ = router1_->CreateLocalEndpointHandle(id);
+ }
+
+ void TearDown() override {}
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ protected:
+ scoped_refptr<MultiplexRouter> router0_;
+ scoped_refptr<MultiplexRouter> router1_;
+ ScopedInterfaceEndpointHandle endpoint0_;
+ ScopedInterfaceEndpointHandle endpoint1_;
+
+ private:
+ base::MessageLoop loop_;
+};
+
+TEST_F(MultiplexRouterTest, BasicRequestResponse) {
+ InterfaceEndpointClient client0(std::move(endpoint0_), nullptr,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+ ResponseGenerator generator;
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ base::RunLoop run_loop;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&message_queue,
+ run_loop.QuitClosure()));
+
+ run_loop.Run();
+
+ EXPECT_FALSE(message_queue.IsEmpty());
+
+ Message response;
+ message_queue.Pop(&response);
+
+ EXPECT_EQ(std::string("hello world!"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+
+ // Send a second message on the pipe.
+ Message request2;
+ AllocRequestMessage(1, "hello again", &request2);
+
+ base::RunLoop run_loop2;
+ client0.AcceptWithResponder(
+ &request2, base::MakeUnique<MessageAccumulator>(&message_queue,
+ run_loop2.QuitClosure()));
+
+ run_loop2.Run();
+
+ EXPECT_FALSE(message_queue.IsEmpty());
+
+ message_queue.Pop(&response);
+
+ EXPECT_EQ(std::string("hello again world!"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+}
+
+TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
+ InterfaceEndpointClient client0(std::move(endpoint0_), nullptr,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+ ResponseGenerator generator;
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&message_queue));
+
+ router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+ router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+
+ EXPECT_FALSE(message_queue.IsEmpty());
+
+ Message response;
+ message_queue.Pop(&response);
+
+ EXPECT_EQ(std::string("hello world!"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+
+ // Send a second message on the pipe.
+ Message request2;
+ AllocRequestMessage(1, "hello again", &request2);
+
+ client0.AcceptWithResponder(
+ &request2, base::MakeUnique<MessageAccumulator>(&message_queue));
+
+ router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+ router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
+
+ EXPECT_FALSE(message_queue.IsEmpty());
+
+ message_queue.Pop(&response);
+
+ EXPECT_EQ(std::string("hello again world!"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+}
+
+// Tests MultiplexRouter using the LazyResponseGenerator. The responses will not
+// be sent until after the requests have been accepted.
+TEST_F(MultiplexRouterTest, LazyResponses) {
+ InterfaceEndpointClient client0(
+ std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()),
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+ base::RunLoop run_loop;
+ LazyResponseGenerator generator(run_loop.QuitClosure());
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::WrapUnique(new PassThroughFilter()),
+ false, base::ThreadTaskRunnerHandle::Get(),
+ 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ base::RunLoop run_loop2;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&message_queue,
+ run_loop2.QuitClosure()));
+ run_loop.Run();
+
+ // The request has been received but the response has not been sent yet.
+ EXPECT_TRUE(message_queue.IsEmpty());
+
+ // Send the response.
+ EXPECT_TRUE(generator.responder_is_valid());
+ generator.CompleteWithResponse();
+ run_loop2.Run();
+
+ // Check the response.
+ EXPECT_FALSE(message_queue.IsEmpty());
+ Message response;
+ message_queue.Pop(&response);
+ EXPECT_EQ(std::string("hello world!"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+
+ // Send a second message on the pipe.
+ base::RunLoop run_loop3;
+ generator.set_closure(run_loop3.QuitClosure());
+ Message request2;
+ AllocRequestMessage(1, "hello again", &request2);
+
+ base::RunLoop run_loop4;
+ client0.AcceptWithResponder(
+ &request2, base::MakeUnique<MessageAccumulator>(&message_queue,
+ run_loop4.QuitClosure()));
+ run_loop3.Run();
+
+ // The request has been received but the response has not been sent yet.
+ EXPECT_TRUE(message_queue.IsEmpty());
+
+ // Send the second response.
+ EXPECT_TRUE(generator.responder_is_valid());
+ generator.CompleteWithResponse();
+ run_loop4.Run();
+
+ // Check the second response.
+ EXPECT_FALSE(message_queue.IsEmpty());
+ message_queue.Pop(&response);
+ EXPECT_EQ(std::string("hello again world!"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+}
+
+void ForwardErrorHandler(bool* called, const base::Closure& callback) {
+ *called = true;
+ callback.Run();
+}
+
+// Tests that if the receiving application destroys the responder_ without
+// sending a response, then we trigger connection error at both sides. Moreover,
+// both sides still appear to have a valid message pipe handle bound.
+TEST_F(MultiplexRouterTest, MissingResponses) {
+ base::RunLoop run_loop0, run_loop1;
+ InterfaceEndpointClient client0(
+ std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()),
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+ bool error_handler_called0 = false;
+ client0.set_connection_error_handler(
+ base::Bind(&ForwardErrorHandler, &error_handler_called0,
+ run_loop0.QuitClosure()));
+
+ base::RunLoop run_loop3;
+ LazyResponseGenerator generator(run_loop3.QuitClosure());
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::WrapUnique(new PassThroughFilter()),
+ false, base::ThreadTaskRunnerHandle::Get(),
+ 0u);
+ bool error_handler_called1 = false;
+ client1.set_connection_error_handler(
+ base::Bind(&ForwardErrorHandler, &error_handler_called1,
+ run_loop1.QuitClosure()));
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&message_queue));
+ run_loop3.Run();
+
+ // The request has been received but no response has been sent.
+ EXPECT_TRUE(message_queue.IsEmpty());
+
+ // Destroy the responder MessagerReceiver but don't send any response.
+ generator.CompleteWithoutResponse();
+ run_loop0.Run();
+ run_loop1.Run();
+
+ // Check that no response was received.
+ EXPECT_TRUE(message_queue.IsEmpty());
+
+ // Connection error handler is called at both sides.
+ EXPECT_TRUE(error_handler_called0);
+ EXPECT_TRUE(error_handler_called1);
+
+ // The error flag is set at both sides.
+ EXPECT_TRUE(client0.encountered_error());
+ EXPECT_TRUE(client1.encountered_error());
+
+ // The message pipe handle is valid at both sides.
+ EXPECT_TRUE(router0_->is_valid());
+ EXPECT_TRUE(router1_->is_valid());
+}
+
+TEST_F(MultiplexRouterTest, LateResponse) {
+ // Test that things won't blow up if we try to send a message to a
+ // MessageReceiver, which was given to us via AcceptWithResponder,
+ // after the router has gone away.
+
+ base::RunLoop run_loop;
+ LazyResponseGenerator generator(run_loop.QuitClosure());
+ {
+ InterfaceEndpointClient client0(
+ std::move(endpoint0_), nullptr, base::MakeUnique<PassThroughFilter>(),
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::MakeUnique<PassThroughFilter>(),
+ false, base::ThreadTaskRunnerHandle::Get(),
+ 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&message_queue));
+
+ run_loop.Run();
+
+ EXPECT_TRUE(generator.has_responder());
+ }
+
+ EXPECT_FALSE(generator.responder_is_valid());
+ generator.CompleteWithResponse(); // This should end up doing nothing.
+}
+
+// TODO(yzshen): add more tests.
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/pickle_unittest.cc b/mojo/public/cpp/bindings/tests/pickle_unittest.cc
new file mode 100644
index 0000000000..a5947ce9ed
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/pickle_unittest.cc
@@ -0,0 +1,403 @@
+// Copyright 2015 The Chromium Authors. 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/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/tests/pickled_types_blink.h"
+#include "mojo/public/cpp/bindings/tests/pickled_types_chromium.h"
+#include "mojo/public/cpp/bindings/tests/variant_test_util.h"
+#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h"
+#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+template <typename T>
+void DoExpectResult(int foo, int bar, const base::Closure& callback, T actual) {
+ EXPECT_EQ(foo, actual.foo());
+ EXPECT_EQ(bar, actual.bar());
+ callback.Run();
+}
+
+template <typename T>
+base::Callback<void(T)> ExpectResult(const T& t,
+ const base::Closure& callback) {
+ return base::Bind(&DoExpectResult<T>, t.foo(), t.bar(), callback);
+}
+
+template <typename T>
+void DoFail(const std::string& reason, T) {
+ EXPECT_TRUE(false) << reason;
+}
+
+template <typename T>
+base::Callback<void(T)> Fail(const std::string& reason) {
+ return base::Bind(&DoFail<T>, reason);
+}
+
+template <typename T>
+void DoExpectEnumResult(T expected, const base::Closure& callback, T actual) {
+ EXPECT_EQ(expected, actual);
+ callback.Run();
+}
+
+template <typename T>
+base::Callback<void(T)> ExpectEnumResult(T t, const base::Closure& callback) {
+ return base::Bind(&DoExpectEnumResult<T>, t, callback);
+}
+
+template <typename T>
+void DoEnumFail(const std::string& reason, T) {
+ EXPECT_TRUE(false) << reason;
+}
+
+template <typename T>
+base::Callback<void(T)> EnumFail(const std::string& reason) {
+ return base::Bind(&DoEnumFail<T>, reason);
+}
+
+template <typename T>
+void ExpectError(InterfacePtr<T>* proxy, const base::Closure& callback) {
+ proxy->set_connection_error_handler(callback);
+}
+
+template <typename Func, typename Arg>
+void RunSimpleLambda(Func func, Arg arg) { func(std::move(arg)); }
+
+template <typename Arg, typename Func>
+base::Callback<void(Arg)> BindSimpleLambda(Func func) {
+ return base::Bind(&RunSimpleLambda<Func, Arg>, func);
+}
+
+// This implements the generated Chromium variant of PicklePasser.
+class ChromiumPicklePasserImpl : public PicklePasser {
+ public:
+ ChromiumPicklePasserImpl() {}
+
+ // mojo::test::PicklePasser:
+ void PassPickledStruct(PickledStructChromium pickle,
+ const PassPickledStructCallback& callback) override {
+ callback.Run(std::move(pickle));
+ }
+
+ void PassPickledEnum(PickledEnumChromium pickle,
+ const PassPickledEnumCallback& callback) override {
+ callback.Run(pickle);
+ }
+
+ void PassPickleContainer(
+ PickleContainerPtr container,
+ const PassPickleContainerCallback& callback) override {
+ callback.Run(std::move(container));
+ }
+
+ void PassPickles(std::vector<PickledStructChromium> pickles,
+ const PassPicklesCallback& callback) override {
+ callback.Run(std::move(pickles));
+ }
+
+ void PassPickleArrays(
+ std::vector<std::vector<PickledStructChromium>> pickle_arrays,
+ const PassPickleArraysCallback& callback) override {
+ callback.Run(std::move(pickle_arrays));
+ }
+};
+
+// This implements the generated Blink variant of PicklePasser.
+class BlinkPicklePasserImpl : public blink::PicklePasser {
+ public:
+ BlinkPicklePasserImpl() {}
+
+ // mojo::test::blink::PicklePasser:
+ void PassPickledStruct(PickledStructBlink pickle,
+ const PassPickledStructCallback& callback) override {
+ callback.Run(std::move(pickle));
+ }
+
+ void PassPickledEnum(PickledEnumBlink pickle,
+ const PassPickledEnumCallback& callback) override {
+ callback.Run(pickle);
+ }
+
+ void PassPickleContainer(
+ blink::PickleContainerPtr container,
+ const PassPickleContainerCallback& callback) override {
+ callback.Run(std::move(container));
+ }
+
+ void PassPickles(WTF::Vector<PickledStructBlink> pickles,
+ const PassPicklesCallback& callback) override {
+ callback.Run(std::move(pickles));
+ }
+
+ void PassPickleArrays(
+ WTF::Vector<WTF::Vector<PickledStructBlink>> pickle_arrays,
+ const PassPickleArraysCallback& callback) override {
+ callback.Run(std::move(pickle_arrays));
+ }
+};
+
+// A test which runs both Chromium and Blink implementations of the
+// PicklePasser service.
+class PickleTest : public testing::Test {
+ public:
+ PickleTest() {}
+
+ template <typename ProxyType = PicklePasser>
+ InterfacePtr<ProxyType> ConnectToChromiumService() {
+ InterfacePtr<ProxyType> proxy;
+ InterfaceRequest<ProxyType> request(&proxy);
+ chromium_bindings_.AddBinding(
+ &chromium_service_,
+ ConvertInterfaceRequest<PicklePasser>(std::move(request)));
+ return proxy;
+ }
+
+ template <typename ProxyType = blink::PicklePasser>
+ InterfacePtr<ProxyType> ConnectToBlinkService() {
+ InterfacePtr<ProxyType> proxy;
+ InterfaceRequest<ProxyType> request(&proxy);
+ blink_bindings_.AddBinding(
+ &blink_service_,
+ ConvertInterfaceRequest<blink::PicklePasser>(std::move(request)));
+ return proxy;
+ }
+
+ private:
+ base::MessageLoop loop_;
+ ChromiumPicklePasserImpl chromium_service_;
+ BindingSet<PicklePasser> chromium_bindings_;
+ BlinkPicklePasserImpl blink_service_;
+ BindingSet<blink::PicklePasser> blink_bindings_;
+};
+
+} // namespace
+
+TEST_F(PickleTest, ChromiumProxyToChromiumService) {
+ auto chromium_proxy = ConnectToChromiumService();
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledStruct(
+ PickledStructChromium(1, 2),
+ ExpectResult(PickledStructChromium(1, 2), loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledStruct(
+ PickledStructChromium(4, 5),
+ ExpectResult(PickledStructChromium(4, 5), loop.QuitClosure()));
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledEnum(
+ PickledEnumChromium::VALUE_1,
+ ExpectEnumResult(PickledEnumChromium::VALUE_1, loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(PickleTest, ChromiumProxyToBlinkService) {
+ auto chromium_proxy = ConnectToBlinkService<PicklePasser>();
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledStruct(
+ PickledStructChromium(1, 2),
+ ExpectResult(PickledStructChromium(1, 2), loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledStruct(
+ PickledStructChromium(4, 5),
+ ExpectResult(PickledStructChromium(4, 5), loop.QuitClosure()));
+ loop.Run();
+ }
+ // The Blink service should drop our connection because the
+ // PickledStructBlink ParamTraits deserializer rejects negative values.
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledStruct(
+ PickledStructChromium(-1, -1),
+ Fail<PickledStructChromium>("Blink service should reject this."));
+ ExpectError(&chromium_proxy, loop.QuitClosure());
+ loop.Run();
+ }
+
+ chromium_proxy = ConnectToBlinkService<PicklePasser>();
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledEnum(
+ PickledEnumChromium::VALUE_0,
+ ExpectEnumResult(PickledEnumChromium::VALUE_0, loop.QuitClosure()));
+ loop.Run();
+ }
+
+ // The Blink service should drop our connection because the
+ // PickledEnumBlink ParamTraits deserializer rejects this value.
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassPickledEnum(
+ PickledEnumChromium::VALUE_2,
+ EnumFail<PickledEnumChromium>("Blink service should reject this."));
+ ExpectError(&chromium_proxy, loop.QuitClosure());
+ loop.Run();
+ }
+}
+
+TEST_F(PickleTest, BlinkProxyToBlinkService) {
+ auto blink_proxy = ConnectToBlinkService();
+ {
+ base::RunLoop loop;
+ blink_proxy->PassPickledStruct(
+ PickledStructBlink(1, 1),
+ ExpectResult(PickledStructBlink(1, 1), loop.QuitClosure()));
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ blink_proxy->PassPickledEnum(
+ PickledEnumBlink::VALUE_0,
+ ExpectEnumResult(PickledEnumBlink::VALUE_0, loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(PickleTest, BlinkProxyToChromiumService) {
+ auto blink_proxy = ConnectToChromiumService<blink::PicklePasser>();
+ {
+ base::RunLoop loop;
+ blink_proxy->PassPickledStruct(
+ PickledStructBlink(1, 1),
+ ExpectResult(PickledStructBlink(1, 1), loop.QuitClosure()));
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ blink_proxy->PassPickledEnum(
+ PickledEnumBlink::VALUE_1,
+ ExpectEnumResult(PickledEnumBlink::VALUE_1, loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(PickleTest, PickleArray) {
+ auto proxy = ConnectToChromiumService();
+ auto pickles = std::vector<PickledStructChromium>(2);
+ pickles[0].set_foo(1);
+ pickles[0].set_bar(2);
+ pickles[0].set_baz(100);
+ pickles[1].set_foo(3);
+ pickles[1].set_bar(4);
+ pickles[1].set_baz(100);
+ {
+ base::RunLoop run_loop;
+ // Verify that the array of pickled structs can be serialized and
+ // deserialized intact. This ensures that the ParamTraits are actually used
+ // rather than doing a byte-for-byte copy of the element data, beacuse the
+ // |baz| field should never be serialized.
+ proxy->PassPickles(std::move(pickles),
+ BindSimpleLambda<std::vector<PickledStructChromium>>(
+ [&](std::vector<PickledStructChromium> passed) {
+ ASSERT_EQ(2u, passed.size());
+ EXPECT_EQ(1, passed[0].foo());
+ EXPECT_EQ(2, passed[0].bar());
+ EXPECT_EQ(0, passed[0].baz());
+ EXPECT_EQ(3, passed[1].foo());
+ EXPECT_EQ(4, passed[1].bar());
+ EXPECT_EQ(0, passed[1].baz());
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ }
+}
+
+TEST_F(PickleTest, PickleArrayArray) {
+ auto proxy = ConnectToChromiumService();
+ auto pickle_arrays = std::vector<std::vector<PickledStructChromium>>(2);
+ for (size_t i = 0; i < 2; ++i)
+ pickle_arrays[i] = std::vector<PickledStructChromium>(2);
+
+ pickle_arrays[0][0].set_foo(1);
+ pickle_arrays[0][0].set_bar(2);
+ pickle_arrays[0][0].set_baz(100);
+ pickle_arrays[0][1].set_foo(3);
+ pickle_arrays[0][1].set_bar(4);
+ pickle_arrays[0][1].set_baz(100);
+ pickle_arrays[1][0].set_foo(5);
+ pickle_arrays[1][0].set_bar(6);
+ pickle_arrays[1][0].set_baz(100);
+ pickle_arrays[1][1].set_foo(7);
+ pickle_arrays[1][1].set_bar(8);
+ pickle_arrays[1][1].set_baz(100);
+ {
+ base::RunLoop run_loop;
+ // Verify that the array-of-arrays serializes and deserializes properly.
+ proxy->PassPickleArrays(
+ std::move(pickle_arrays),
+ BindSimpleLambda<std::vector<std::vector<PickledStructChromium>>>(
+ [&](std::vector<std::vector<PickledStructChromium>> passed) {
+ ASSERT_EQ(2u, passed.size());
+ ASSERT_EQ(2u, passed[0].size());
+ ASSERT_EQ(2u, passed[1].size());
+ EXPECT_EQ(1, passed[0][0].foo());
+ EXPECT_EQ(2, passed[0][0].bar());
+ EXPECT_EQ(0, passed[0][0].baz());
+ EXPECT_EQ(3, passed[0][1].foo());
+ EXPECT_EQ(4, passed[0][1].bar());
+ EXPECT_EQ(0, passed[0][1].baz());
+ EXPECT_EQ(5, passed[1][0].foo());
+ EXPECT_EQ(6, passed[1][0].bar());
+ EXPECT_EQ(0, passed[1][0].baz());
+ EXPECT_EQ(7, passed[1][1].foo());
+ EXPECT_EQ(8, passed[1][1].bar());
+ EXPECT_EQ(0, passed[1][1].baz());
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ }
+}
+
+TEST_F(PickleTest, PickleContainer) {
+ auto proxy = ConnectToChromiumService();
+ PickleContainerPtr pickle_container = PickleContainer::New();
+ pickle_container->f_struct.set_foo(42);
+ pickle_container->f_struct.set_bar(43);
+ pickle_container->f_struct.set_baz(44);
+ pickle_container->f_enum = PickledEnumChromium::VALUE_1;
+ EXPECT_TRUE(pickle_container.Equals(pickle_container));
+ EXPECT_FALSE(pickle_container.Equals(PickleContainer::New()));
+ {
+ base::RunLoop run_loop;
+ proxy->PassPickleContainer(std::move(pickle_container),
+ BindSimpleLambda<PickleContainerPtr>(
+ [&](PickleContainerPtr passed) {
+ ASSERT_FALSE(passed.is_null());
+ EXPECT_EQ(42, passed->f_struct.foo());
+ EXPECT_EQ(43, passed->f_struct.bar());
+ EXPECT_EQ(0, passed->f_struct.baz());
+ EXPECT_EQ(PickledEnumChromium::VALUE_1,
+ passed->f_enum);
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ }
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.cc b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc
new file mode 100644
index 0000000000..7e556507bb
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/tests/pickled_types_blink.h"
+
+#include "base/logging.h"
+#include "base/pickle.h"
+
+namespace mojo {
+namespace test {
+
+PickledStructBlink::PickledStructBlink() {}
+
+PickledStructBlink::PickledStructBlink(int foo, int bar)
+ : foo_(foo), bar_(bar) {
+ DCHECK_GE(foo_, 0);
+ DCHECK_GE(bar_, 0);
+}
+
+PickledStructBlink::~PickledStructBlink() {}
+
+} // namespace test
+} // namespace mojo
+
+namespace IPC {
+
+void ParamTraits<mojo::test::PickledStructBlink>::GetSize(
+ base::PickleSizer* sizer,
+ const param_type& p) {
+ sizer->AddInt();
+ sizer->AddInt();
+}
+
+void ParamTraits<mojo::test::PickledStructBlink>::Write(base::Pickle* m,
+ const param_type& p) {
+ m->WriteInt(p.foo());
+ m->WriteInt(p.bar());
+}
+
+bool ParamTraits<mojo::test::PickledStructBlink>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ int foo, bar;
+ if (!iter->ReadInt(&foo) || !iter->ReadInt(&bar) || foo < 0 || bar < 0)
+ return false;
+
+ p->set_foo(foo);
+ p->set_bar(bar);
+ return true;
+}
+
+#include "ipc/param_traits_size_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
+ mojo::test::PickledEnumBlink::VALUE_1)
+#include "ipc/param_traits_write_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
+ mojo::test::PickledEnumBlink::VALUE_1)
+#include "ipc/param_traits_read_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
+ mojo::test::PickledEnumBlink::VALUE_1)
+#include "ipc/param_traits_log_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
+ mojo::test::PickledEnumBlink::VALUE_1)
+
+} // namespace IPC
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.h b/mojo/public/cpp/bindings/tests/pickled_types_blink.h
new file mode 100644
index 0000000000..37e9e70578
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_param_traits.h"
+
+namespace base {
+class Pickle;
+class PickleIterator;
+class PickleSizer;
+}
+
+namespace mojo {
+namespace test {
+
+// Implementation of types with IPC::ParamTraits for consumers in Blink.
+
+enum class PickledEnumBlink { VALUE_0, VALUE_1 };
+
+// To make things slightly more interesting, this variation of the type doesn't
+// support negative values. It'll DCHECK if you try to construct it with any,
+// and it will fail deserialization if negative values are decoded.
+class PickledStructBlink {
+ public:
+ PickledStructBlink();
+ PickledStructBlink(int foo, int bar);
+ PickledStructBlink(PickledStructBlink&& other) = default;
+ ~PickledStructBlink();
+
+ PickledStructBlink& operator=(PickledStructBlink&& other) = default;
+
+ int foo() const { return foo_; }
+ void set_foo(int foo) {
+ DCHECK_GE(foo, 0);
+ foo_ = foo;
+ }
+
+ int bar() const { return bar_; }
+ void set_bar(int bar) {
+ DCHECK_GE(bar, 0);
+ bar_ = bar;
+ }
+
+ // The |baz| field should never be serialized.
+ int baz() const { return baz_; }
+ void set_baz(int baz) { baz_ = baz; }
+
+ private:
+ int foo_ = 0;
+ int bar_ = 0;
+ int baz_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(PickledStructBlink);
+};
+
+} // namespace test
+} // namespace mojo
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mojo::test::PickledStructBlink> {
+ using param_type = mojo::test::PickledStructBlink;
+
+ static void GetSize(base::PickleSizer* sizer, const param_type& p);
+ 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) {}
+};
+
+} // namespace IPC
+
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
+ mojo::test::PickledEnumBlink::VALUE_1)
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc
new file mode 100644
index 0000000000..9957c9a4d0
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/tests/pickled_types_chromium.h"
+
+#include "base/pickle.h"
+
+namespace mojo {
+namespace test {
+
+PickledStructChromium::PickledStructChromium() {}
+
+PickledStructChromium::PickledStructChromium(int foo, int bar)
+ : foo_(foo), bar_(bar) {}
+
+PickledStructChromium::~PickledStructChromium() {}
+
+bool operator==(const PickledStructChromium& a,
+ const PickledStructChromium& b) {
+ return a.foo() == b.foo() && a.bar() == b.bar() && a.baz() == b.baz();
+}
+
+} // namespace test
+} // namespace mojo
+
+namespace IPC {
+
+void ParamTraits<mojo::test::PickledStructChromium>::GetSize(
+ base::PickleSizer* sizer,
+ const param_type& p) {
+ sizer->AddInt();
+ sizer->AddInt();
+}
+
+void ParamTraits<mojo::test::PickledStructChromium>::Write(
+ base::Pickle* m,
+ const param_type& p) {
+ m->WriteInt(p.foo());
+ m->WriteInt(p.bar());
+}
+
+bool ParamTraits<mojo::test::PickledStructChromium>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ int foo, bar;
+ if (!iter->ReadInt(&foo) || !iter->ReadInt(&bar))
+ return false;
+
+ p->set_foo(foo);
+ p->set_bar(bar);
+ return true;
+}
+
+#include "ipc/param_traits_size_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
+ mojo::test::PickledEnumChromium::VALUE_2)
+#include "ipc/param_traits_write_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
+ mojo::test::PickledEnumChromium::VALUE_2)
+#include "ipc/param_traits_read_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
+ mojo::test::PickledEnumChromium::VALUE_2)
+#include "ipc/param_traits_log_macros.h"
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
+ mojo::test::PickledEnumChromium::VALUE_2)
+
+} // namespace IPC
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.h b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h
new file mode 100644
index 0000000000..d9287b62e7
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h
@@ -0,0 +1,81 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_param_traits.h"
+
+namespace base {
+class Pickle;
+class PickleIterator;
+class PickleSizer;
+}
+
+namespace mojo {
+namespace test {
+
+// Implementation of types with IPC::ParamTraits for consumers in the greater
+// Chromium tree.
+
+enum class PickledEnumChromium { VALUE_0, VALUE_1, VALUE_2 };
+
+class PickledStructChromium {
+ public:
+ PickledStructChromium();
+ PickledStructChromium(int foo, int bar);
+ PickledStructChromium(PickledStructChromium&& other) = default;
+ ~PickledStructChromium();
+
+ PickledStructChromium& operator=(PickledStructChromium&& other) = default;
+
+ int foo() const { return foo_; }
+ void set_foo(int foo) { foo_ = foo; }
+
+ int bar() const { return bar_; }
+ void set_bar(int bar) { bar_ = bar; }
+
+ // The |baz| field should never be serialized.
+ int baz() const { return baz_; }
+ void set_baz(int baz) { baz_ = baz; }
+
+ private:
+ int foo_ = 0;
+ int bar_ = 0;
+ int baz_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(PickledStructChromium);
+};
+
+bool operator==(const PickledStructChromium& a, const PickledStructChromium& b);
+
+} // namespace test
+} // namespace mojo
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mojo::test::PickledStructChromium> {
+ using param_type = mojo::test::PickledStructChromium;
+
+ static void GetSize(base::PickleSizer* sizer, const param_type& p);
+ 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) {}
+};
+
+} // namespace IPC
+
+IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
+ mojo::test::PickledEnumChromium::VALUE_2)
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_
diff --git a/mojo/public/cpp/bindings/tests/rect_blink.h b/mojo/public/cpp/bindings/tests/rect_blink.h
new file mode 100644
index 0000000000..7335989593
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/rect_blink.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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace test {
+
+// An implementation of a hypothetical Rect type specifically for consumers in
+// in Blink. Unlike the Chromium variant (see rect_chromium.h) this does not
+// support negative origin coordinates and is not copyable.
+class RectBlink {
+ public:
+ RectBlink() {}
+ RectBlink(int x, int y, int width, int height) :
+ x_(x), y_(y), width_(width), height_(height) {
+ DCHECK_GE(x_, 0);
+ DCHECK_GE(y_, 0);
+ DCHECK_GE(width_, 0);
+ DCHECK_GE(height_, 0);
+ }
+ ~RectBlink() {}
+
+ int x() const { return x_; }
+ void setX(int x) {
+ DCHECK_GE(x, 0);
+ x_ = x;
+ }
+
+ int y() const { return y_; }
+ void setY(int y) {
+ DCHECK_GE(y, 0);
+ y_ = y;
+ }
+
+ int width() const { return width_; }
+ void setWidth(int width) {
+ DCHECK_GE(width, 0);
+ width_ = width;
+ }
+
+ int height() const { return height_; }
+ void setHeight(int height) {
+ DCHECK_GE(height, 0);
+ height_ = height;
+ }
+
+ int computeArea() const { return width_ * height_; }
+
+ bool operator==(const RectBlink& other) const {
+ return (x() == other.x() && y() == other.y() && width() == other.width() &&
+ height() == other.height());
+ }
+ bool operator!=(const RectBlink& other) const { return !(*this == other); }
+
+ private:
+ int x_ = 0;
+ int y_ = 0;
+ int width_ = 0;
+ int height_ = 0;
+};
+
+} // namespace test
+} // namespace mojo
+
+namespace std {
+
+template <>
+struct hash<mojo::test::RectBlink> {
+ size_t operator()(const mojo::test::RectBlink& value) {
+ // Terrible hash function:
+ return (std::hash<int>()(value.x()) ^ std::hash<int>()(value.y()) ^
+ std::hash<int>()(value.width()) ^ std::hash<int>()(value.height()));
+ }
+};
+
+} // namespace std
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_
diff --git a/mojo/public/cpp/bindings/tests/rect_blink.typemap b/mojo/public/cpp/bindings/tests/rect_blink.typemap
new file mode 100644
index 0000000000..657ea1a6ca
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/rect_blink.typemap
@@ -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.
+
+mojom = "//mojo/public/interfaces/bindings/tests/rect.mojom"
+public_headers = [
+ "//mojo/public/cpp/bindings/tests/rect_blink.h",
+ "//mojo/public/cpp/bindings/tests/shared_rect.h",
+]
+traits_headers = [
+ "//mojo/public/cpp/bindings/tests/rect_blink_traits.h",
+ "//mojo/public/cpp/bindings/tests/shared_rect_traits.h",
+]
+
+type_mappings = [
+ "mojo.test.TypemappedRect=mojo::test::RectBlink[hashable]",
+ "mojo.test.SharedTypemappedRect=mojo::test::SharedRect",
+]
diff --git a/mojo/public/cpp/bindings/tests/rect_blink_traits.h b/mojo/public/cpp/bindings/tests/rect_blink_traits.h
new file mode 100644
index 0000000000..7258739a84
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/rect_blink_traits.h
@@ -0,0 +1,35 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_
+
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/tests/rect_blink.h"
+#include "mojo/public/interfaces/bindings/tests/rect.mojom-blink.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<test::TypemappedRectDataView, test::RectBlink> {
+ static int x(const test::RectBlink& r) { return r.x(); }
+ static int y(const test::RectBlink& r) { return r.y(); }
+ static int width(const test::RectBlink& r) { return r.width(); }
+ static int height(const test::RectBlink& r) { return r.height(); }
+
+ static bool Read(test::TypemappedRectDataView r, test::RectBlink* out) {
+ if (r.x() < 0 || r.y() < 0 || r.width() < 0 || r.height() < 0) {
+ return false;
+ }
+ out->setX(r.x());
+ out->setY(r.y());
+ out->setWidth(r.width());
+ out->setHeight(r.height());
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/tests/rect_chromium.h b/mojo/public/cpp/bindings/tests/rect_chromium.h
new file mode 100644
index 0000000000..d2e0a3e635
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/rect_chromium.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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace test {
+
+// An implementation of a hypothetical Rect type specifically for consumers in
+// in Chromium.
+class RectChromium {
+ public:
+ RectChromium() {}
+ RectChromium(const RectChromium& other)
+ : x_(other.x_),
+ y_(other.y_),
+ width_(other.width_),
+ height_(other.height_) {}
+ RectChromium(int x, int y, int width, int height) :
+ x_(x), y_(y), width_(width), height_(height) {
+ DCHECK_GE(width_, 0);
+ DCHECK_GE(height_, 0);
+ }
+ ~RectChromium() {}
+
+ RectChromium& operator=(const RectChromium& other) {
+ x_ = other.x_;
+ y_ = other.y_;
+ width_ = other.width_;
+ height_ = other.height_;
+ return *this;
+ }
+
+ int x() const { return x_; }
+ void set_x(int x) { x_ = x; }
+
+ int y() const { return y_; }
+ void set_y(int y) { y_ = y; }
+
+ int width() const { return width_; }
+ void set_width(int width) {
+ DCHECK_GE(width, 0);
+ width_ = width;
+ }
+
+ int height() const { return height_; }
+ void set_height(int height) {
+ DCHECK_GE(height, 0);
+ height_ = height;
+ }
+
+ int GetArea() const { return width_ * height_; }
+
+ bool operator==(const RectChromium& other) const {
+ return (x() == other.x() && y() == other.y() && width() == other.width() &&
+ height() == other.height());
+ }
+ bool operator!=(const RectChromium& other) const { return !(*this == other); }
+
+ private:
+ int x_ = 0;
+ int y_ = 0;
+ int width_ = 0;
+ int height_ = 0;
+};
+
+} // namespace test
+} // namespace mojo
+
+namespace std {
+
+template <>
+struct hash<mojo::test::RectChromium> {
+ size_t operator()(const mojo::test::RectChromium& value) {
+ // Terrible hash function:
+ return (std::hash<int>()(value.x()) ^ std::hash<int>()(value.y()) ^
+ std::hash<int>()(value.width()) ^ std::hash<int>()(value.height()));
+ }
+};
+
+} // namespace std
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_
diff --git a/mojo/public/cpp/bindings/tests/rect_chromium.typemap b/mojo/public/cpp/bindings/tests/rect_chromium.typemap
new file mode 100644
index 0000000000..7e5df8401a
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/rect_chromium.typemap
@@ -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.
+
+mojom = "//mojo/public/interfaces/bindings/tests/rect.mojom"
+public_headers = [
+ "//mojo/public/cpp/bindings/tests/rect_chromium.h",
+ "//mojo/public/cpp/bindings/tests/shared_rect.h",
+]
+traits_headers = [
+ "//mojo/public/cpp/bindings/tests/rect_chromium_traits.h",
+ "//mojo/public/cpp/bindings/tests/shared_rect_traits.h",
+]
+
+type_mappings = [
+ "mojo.test.TypemappedRect=mojo::test::RectChromium[hashable]",
+ "mojo.test.SharedTypemappedRect=mojo::test::SharedRect",
+]
diff --git a/mojo/public/cpp/bindings/tests/rect_chromium_traits.h b/mojo/public/cpp/bindings/tests/rect_chromium_traits.h
new file mode 100644
index 0000000000..b446d7d34a
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/rect_chromium_traits.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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_
+
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/tests/rect_chromium.h"
+#include "mojo/public/interfaces/bindings/tests/rect.mojom.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<test::TypemappedRectDataView, test::RectChromium> {
+ static int x(const test::RectChromium& r) { return r.x(); }
+ static int y(const test::RectChromium& r) { return r.y(); }
+ static int width(const test::RectChromium& r) { return r.width(); }
+ static int height(const test::RectChromium& r) { return r.height(); }
+
+ static bool Read(test::TypemappedRectDataView r, test::RectChromium* out) {
+ if (r.width() < 0 || r.height() < 0)
+ return false;
+ out->set_x(r.x());
+ out->set_y(r.y());
+ out->set_width(r.width());
+ out->set_height(r.height());
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc
new file mode 100644
index 0000000000..1bf3f7a4b7
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc
@@ -0,0 +1,194 @@
+// 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/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/interfaces/bindings/tests/test_bad_messages.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class TestBadMessagesImpl : public TestBadMessages {
+ public:
+ TestBadMessagesImpl() : binding_(this) {}
+ ~TestBadMessagesImpl() override {}
+
+ void BindImpl(TestBadMessagesRequest request) {
+ binding_.Bind(std::move(request));
+ }
+
+ const ReportBadMessageCallback& bad_message_callback() {
+ return bad_message_callback_;
+ }
+
+ private:
+ // TestBadMessages:
+ void RejectEventually(const RejectEventuallyCallback& callback) override {
+ bad_message_callback_ = GetBadMessageCallback();
+ callback.Run();
+ }
+
+ void RequestResponse(const RequestResponseCallback& callback) override {
+ callback.Run();
+ }
+
+ void RejectSync(const RejectSyncCallback& callback) override {
+ callback.Run();
+ ReportBadMessage("go away");
+ }
+
+ void RequestResponseSync(
+ const RequestResponseSyncCallback& callback) override {
+ callback.Run();
+ }
+
+ ReportBadMessageCallback bad_message_callback_;
+ mojo::Binding<TestBadMessages> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBadMessagesImpl);
+};
+
+class ReportBadMessageTest : public testing::Test {
+ public:
+ ReportBadMessageTest() {}
+
+ void SetUp() override {
+ mojo::edk::SetDefaultProcessErrorCallback(
+ base::Bind(&ReportBadMessageTest::OnProcessError,
+ base::Unretained(this)));
+
+ impl_.BindImpl(MakeRequest(&proxy_));
+ }
+
+ void TearDown() override {
+ mojo::edk::SetDefaultProcessErrorCallback(
+ mojo::edk::ProcessErrorCallback());
+ }
+
+ TestBadMessages* proxy() { return proxy_.get(); }
+
+ TestBadMessagesImpl* impl() { return &impl_; }
+
+ void SetErrorHandler(const base::Closure& handler) {
+ error_handler_ = handler;
+ }
+
+ private:
+ void OnProcessError(const std::string& error) {
+ if (!error_handler_.is_null())
+ error_handler_.Run();
+ }
+
+ TestBadMessagesPtr proxy_;
+ TestBadMessagesImpl impl_;
+ base::Closure error_handler_;
+ base::MessageLoop message_loop;
+};
+
+TEST_F(ReportBadMessageTest, Request) {
+ // Verify that basic immediate error reporting works.
+ bool error = false;
+ SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
+ EXPECT_TRUE(proxy()->RejectSync());
+ EXPECT_TRUE(error);
+}
+
+TEST_F(ReportBadMessageTest, RequestAsync) {
+ bool error = false;
+ SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
+
+ // This should capture a bad message reporting callback in the impl.
+ base::RunLoop loop;
+ proxy()->RejectEventually(loop.QuitClosure());
+ loop.Run();
+
+ EXPECT_FALSE(error);
+
+ // Now we can run the callback and it should trigger a bad message report.
+ DCHECK(!impl()->bad_message_callback().is_null());
+ impl()->bad_message_callback().Run("bad!");
+ EXPECT_TRUE(error);
+}
+
+TEST_F(ReportBadMessageTest, Response) {
+ bool error = false;
+ SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
+
+ base::RunLoop loop;
+ proxy()->RequestResponse(
+ base::Bind([] (const base::Closure& quit) {
+ // Report a bad message inside the response callback. This should
+ // trigger the error handler.
+ ReportBadMessage("no way!");
+ quit.Run();
+ },
+ loop.QuitClosure()));
+ loop.Run();
+
+ EXPECT_TRUE(error);
+}
+
+TEST_F(ReportBadMessageTest, ResponseAsync) {
+ bool error = false;
+ SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
+
+ ReportBadMessageCallback bad_message_callback;
+ base::RunLoop loop;
+ proxy()->RequestResponse(
+ base::Bind([] (const base::Closure& quit,
+ ReportBadMessageCallback* callback) {
+ // Capture the bad message callback inside the response callback.
+ *callback = GetBadMessageCallback();
+ quit.Run();
+ },
+ loop.QuitClosure(), &bad_message_callback));
+ loop.Run();
+
+ EXPECT_FALSE(error);
+
+ // Invoking this callback should report a bad message and trigger the error
+ // handler immediately.
+ bad_message_callback.Run("this message is bad and should feel bad");
+ EXPECT_TRUE(error);
+}
+
+TEST_F(ReportBadMessageTest, ResponseSync) {
+ bool error = false;
+ SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
+
+ SyncMessageResponseContext context;
+ proxy()->RequestResponseSync();
+
+ EXPECT_FALSE(error);
+ context.ReportBadMessage("i don't like this response");
+ EXPECT_TRUE(error);
+}
+
+TEST_F(ReportBadMessageTest, ResponseSyncDeferred) {
+ bool error = false;
+ SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
+
+ ReportBadMessageCallback bad_message_callback;
+ {
+ SyncMessageResponseContext context;
+ proxy()->RequestResponseSync();
+ bad_message_callback = context.GetBadMessageCallback();
+ }
+
+ EXPECT_FALSE(error);
+ bad_message_callback.Run("nope nope nope");
+ EXPECT_TRUE(error);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/request_response_unittest.cc b/mojo/public/cpp/bindings/tests/request_response_unittest.cc
new file mode 100644
index 0000000000..43b8f0dc90
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/request_response_unittest.cc
@@ -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.
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/interfaces/bindings/tests/sample_import.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class ProviderImpl : public sample::Provider {
+ public:
+ explicit ProviderImpl(InterfaceRequest<sample::Provider> request)
+ : binding_(this, std::move(request)) {}
+
+ void EchoString(const std::string& a,
+ const EchoStringCallback& callback) override {
+ EchoStringCallback callback_copy;
+ // Make sure operator= is used.
+ callback_copy = callback;
+ callback_copy.Run(a);
+ }
+
+ void EchoStrings(const std::string& a,
+ const std::string& b,
+ const EchoStringsCallback& callback) override {
+ callback.Run(a, b);
+ }
+
+ void EchoMessagePipeHandle(
+ ScopedMessagePipeHandle a,
+ const EchoMessagePipeHandleCallback& callback) override {
+ callback.Run(std::move(a));
+ }
+
+ void EchoEnum(sample::Enum a, const EchoEnumCallback& callback) override {
+ callback.Run(a);
+ }
+
+ void EchoInt(int32_t a, const EchoIntCallback& callback) override {
+ callback.Run(a);
+ }
+
+ Binding<sample::Provider> binding_;
+};
+
+void RecordString(std::string* storage,
+ const base::Closure& closure,
+ const std::string& str) {
+ *storage = str;
+ closure.Run();
+}
+
+void RecordStrings(std::string* storage,
+ const base::Closure& closure,
+ const std::string& a,
+ const std::string& b) {
+ *storage = a + b;
+ closure.Run();
+}
+
+void WriteToMessagePipe(const char* text,
+ const base::Closure& closure,
+ ScopedMessagePipeHandle handle) {
+ WriteTextMessage(handle.get(), text);
+ closure.Run();
+}
+
+void RecordEnum(sample::Enum* storage,
+ const base::Closure& closure,
+ sample::Enum value) {
+ *storage = value;
+ closure.Run();
+}
+
+class RequestResponseTest : public testing::Test {
+ public:
+ RequestResponseTest() {}
+ ~RequestResponseTest() override { base::RunLoop().RunUntilIdle(); }
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ private:
+ base::MessageLoop loop_;
+};
+
+TEST_F(RequestResponseTest, EchoString) {
+ sample::ProviderPtr provider;
+ ProviderImpl provider_impl(MakeRequest(&provider));
+
+ std::string buf;
+ base::RunLoop run_loop;
+ provider->EchoString("hello",
+ base::Bind(&RecordString, &buf, run_loop.QuitClosure()));
+
+ run_loop.Run();
+
+ EXPECT_EQ(std::string("hello"), buf);
+}
+
+TEST_F(RequestResponseTest, EchoStrings) {
+ sample::ProviderPtr provider;
+ ProviderImpl provider_impl(MakeRequest(&provider));
+
+ std::string buf;
+ base::RunLoop run_loop;
+ provider->EchoStrings("hello", " world", base::Bind(&RecordStrings, &buf,
+ run_loop.QuitClosure()));
+
+ run_loop.Run();
+
+ EXPECT_EQ(std::string("hello world"), buf);
+}
+
+TEST_F(RequestResponseTest, EchoMessagePipeHandle) {
+ sample::ProviderPtr provider;
+ ProviderImpl provider_impl(MakeRequest(&provider));
+
+ MessagePipe pipe2;
+ base::RunLoop run_loop;
+ provider->EchoMessagePipeHandle(
+ std::move(pipe2.handle1),
+ base::Bind(&WriteToMessagePipe, "hello", run_loop.QuitClosure()));
+
+ run_loop.Run();
+
+ std::string value;
+ ReadTextMessage(pipe2.handle0.get(), &value);
+
+ EXPECT_EQ(std::string("hello"), value);
+}
+
+TEST_F(RequestResponseTest, EchoEnum) {
+ sample::ProviderPtr provider;
+ ProviderImpl provider_impl(MakeRequest(&provider));
+
+ sample::Enum value;
+ base::RunLoop run_loop;
+ provider->EchoEnum(sample::Enum::VALUE,
+ base::Bind(&RecordEnum, &value, run_loop.QuitClosure()));
+ run_loop.Run();
+
+ EXPECT_EQ(sample::Enum::VALUE, value);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/router_test_util.cc b/mojo/public/cpp/bindings/tests/router_test_util.cc
new file mode 100644
index 0000000000..9bab1cb360
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/router_test_util.cc
@@ -0,0 +1,111 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/tests/router_test_util.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/tests/message_queue.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+
+void AllocRequestMessage(uint32_t name, const char* text, Message* message) {
+ size_t payload_size = strlen(text) + 1; // Plus null terminator.
+ internal::MessageBuilder builder(name, Message::kFlagExpectsResponse,
+ payload_size, 0);
+ memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
+ *message = std::move(*builder.message());
+}
+
+void AllocResponseMessage(uint32_t name,
+ const char* text,
+ uint64_t request_id,
+ Message* message) {
+ size_t payload_size = strlen(text) + 1; // Plus null terminator.
+ internal::MessageBuilder builder(name, Message::kFlagIsResponse, payload_size,
+ 0);
+ builder.message()->set_request_id(request_id);
+ memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
+ *message = std::move(*builder.message());
+}
+
+MessageAccumulator::MessageAccumulator(MessageQueue* queue,
+ const base::Closure& closure)
+ : queue_(queue), closure_(closure) {}
+
+MessageAccumulator::~MessageAccumulator() {}
+
+bool MessageAccumulator::Accept(Message* message) {
+ queue_->Push(message);
+ if (!closure_.is_null()) {
+ closure_.Run();
+ closure_.Reset();
+ }
+ return true;
+}
+
+ResponseGenerator::ResponseGenerator() {}
+
+bool ResponseGenerator::Accept(Message* message) {
+ return false;
+}
+
+bool ResponseGenerator::AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) {
+ EXPECT_TRUE(message->has_flag(Message::kFlagExpectsResponse));
+
+ bool result = SendResponse(message->name(), message->request_id(),
+ reinterpret_cast<const char*>(message->payload()),
+ responder.get());
+ EXPECT_TRUE(responder->IsValid());
+ return result;
+}
+
+bool ResponseGenerator::SendResponse(uint32_t name,
+ uint64_t request_id,
+ const char* request_string,
+ MessageReceiver* responder) {
+ Message response;
+ std::string response_string(request_string);
+ response_string += " world!";
+ AllocResponseMessage(name, response_string.c_str(), request_id, &response);
+
+ return responder->Accept(&response);
+}
+
+LazyResponseGenerator::LazyResponseGenerator(const base::Closure& closure)
+ : responder_(nullptr), name_(0), request_id_(0), closure_(closure) {}
+
+LazyResponseGenerator::~LazyResponseGenerator() = default;
+
+bool LazyResponseGenerator::AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) {
+ name_ = message->name();
+ request_id_ = message->request_id();
+ request_string_ =
+ std::string(reinterpret_cast<const char*>(message->payload()));
+ responder_ = std::move(responder);
+ if (!closure_.is_null()) {
+ closure_.Run();
+ closure_.Reset();
+ }
+ return true;
+}
+
+void LazyResponseGenerator::Complete(bool send_response) {
+ if (send_response) {
+ SendResponse(name_, request_id_, request_string_.c_str(), responder_.get());
+ }
+ responder_ = nullptr;
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/router_test_util.h b/mojo/public/cpp/bindings/tests/router_test_util.h
new file mode 100644
index 0000000000..dd6aff63da
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/router_test_util.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/callback.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace test {
+
+class MessageQueue;
+
+void AllocRequestMessage(uint32_t name, const char* text, Message* message);
+void AllocResponseMessage(uint32_t name,
+ const char* text,
+ uint64_t request_id,
+ Message* message);
+
+class MessageAccumulator : public MessageReceiver {
+ public:
+ MessageAccumulator(MessageQueue* queue,
+ const base::Closure& closure = base::Closure());
+ ~MessageAccumulator() override;
+
+ bool Accept(Message* message) override;
+
+ private:
+ MessageQueue* queue_;
+ base::Closure closure_;
+};
+
+class ResponseGenerator : public MessageReceiverWithResponderStatus {
+ public:
+ ResponseGenerator();
+
+ bool Accept(Message* message) override;
+
+ bool AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) override;
+ bool SendResponse(uint32_t name,
+ uint64_t request_id,
+ const char* request_string,
+ MessageReceiver* responder);
+};
+
+class LazyResponseGenerator : public ResponseGenerator {
+ public:
+ explicit LazyResponseGenerator(
+ const base::Closure& closure = base::Closure());
+
+ ~LazyResponseGenerator() override;
+
+ bool AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiverWithStatus> responder) override;
+
+ bool has_responder() const { return !!responder_; }
+
+ bool responder_is_valid() const { return responder_->IsValid(); }
+
+ void set_closure(const base::Closure& closure) { closure_ = closure; }
+
+ // Sends the response and delete the responder.
+ void CompleteWithResponse() { Complete(true); }
+
+ // Deletes the responder without sending a response.
+ void CompleteWithoutResponse() { Complete(false); }
+
+ private:
+ // Completes the request handling by deleting responder_. Optionally
+ // also sends a response.
+ void Complete(bool send_response);
+
+ std::unique_ptr<MessageReceiverWithStatus> responder_;
+ uint32_t name_;
+ uint64_t request_id_;
+ std::string request_string_;
+ base::Closure closure_;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_
diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
new file mode 100644
index 0000000000..1f95a27a5e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
@@ -0,0 +1,362 @@
+// Copyright 2014 The Chromium Authors. 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 <algorithm>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+template <>
+struct TypeConverter<int32_t, sample::BarPtr> {
+ static int32_t Convert(const sample::BarPtr& bar) {
+ return static_cast<int32_t>(bar->alpha) << 16 |
+ static_cast<int32_t>(bar->beta) << 8 |
+ static_cast<int32_t>(bar->gamma);
+ }
+};
+
+} // namespace mojo
+
+namespace sample {
+namespace {
+
+// Set this variable to true to print the message in hex.
+bool g_dump_message_as_hex = false;
+
+// Set this variable to true to print the message in human readable form.
+bool g_dump_message_as_text = false;
+
+// Make a sample |Foo|.
+FooPtr MakeFoo() {
+ std::string name("foopy");
+
+ BarPtr bar(Bar::New(20, 40, 60, Bar::Type::VERTICAL));
+
+ std::vector<BarPtr> extra_bars(3);
+ for (size_t i = 0; i < extra_bars.size(); ++i) {
+ Bar::Type type = i % 2 == 0 ? Bar::Type::VERTICAL : Bar::Type::HORIZONTAL;
+ uint8_t base = static_cast<uint8_t>(i * 100);
+ extra_bars[i] = Bar::New(base, base + 20, base + 40, type);
+ }
+
+ std::vector<uint8_t> data(10);
+ for (size_t i = 0; i < data.size(); ++i)
+ data[i] = static_cast<uint8_t>(data.size() - i);
+
+ std::vector<mojo::ScopedDataPipeConsumerHandle> input_streams(2);
+ std::vector<mojo::ScopedDataPipeProducerHandle> output_streams(2);
+ for (size_t i = 0; i < input_streams.size(); ++i) {
+ MojoCreateDataPipeOptions options;
+ options.struct_size = sizeof(MojoCreateDataPipeOptions);
+ options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = 1024;
+ mojo::ScopedDataPipeProducerHandle producer;
+ mojo::ScopedDataPipeConsumerHandle consumer;
+ mojo::CreateDataPipe(&options, &producer, &consumer);
+ input_streams[i] = std::move(consumer);
+ output_streams[i] = std::move(producer);
+ }
+
+ std::vector<std::vector<bool>> array_of_array_of_bools(2);
+ for (size_t i = 0; i < 2; ++i) {
+ std::vector<bool> array_of_bools(2);
+ for (size_t j = 0; j < 2; ++j)
+ array_of_bools[j] = j;
+ array_of_array_of_bools[i] = std::move(array_of_bools);
+ }
+
+ mojo::MessagePipe pipe;
+ return Foo::New(name, 1, 2, false, true, false, std::move(bar),
+ std::move(extra_bars), std::move(data),
+ std::move(pipe.handle1), std::move(input_streams),
+ std::move(output_streams), std::move(array_of_array_of_bools),
+ base::nullopt, base::nullopt);
+}
+
+// Check that the given |Foo| is identical to the one made by |MakeFoo()|.
+void CheckFoo(const Foo& foo) {
+ const std::string kName("foopy");
+ EXPECT_EQ(kName.size(), foo.name.size());
+ for (size_t i = 0; i < std::min(kName.size(), foo.name.size()); i++) {
+ // Test both |operator[]| and |at|.
+ EXPECT_EQ(kName[i], foo.name.at(i)) << i;
+ EXPECT_EQ(kName[i], foo.name[i]) << i;
+ }
+ EXPECT_EQ(kName, foo.name);
+
+ EXPECT_EQ(1, foo.x);
+ EXPECT_EQ(2, foo.y);
+ EXPECT_FALSE(foo.a);
+ EXPECT_TRUE(foo.b);
+ EXPECT_FALSE(foo.c);
+
+ EXPECT_EQ(20, foo.bar->alpha);
+ EXPECT_EQ(40, foo.bar->beta);
+ EXPECT_EQ(60, foo.bar->gamma);
+ EXPECT_EQ(Bar::Type::VERTICAL, foo.bar->type);
+
+ EXPECT_EQ(3u, foo.extra_bars->size());
+ for (size_t i = 0; i < foo.extra_bars->size(); i++) {
+ uint8_t base = static_cast<uint8_t>(i * 100);
+ Bar::Type type = i % 2 == 0 ? Bar::Type::VERTICAL : Bar::Type::HORIZONTAL;
+ EXPECT_EQ(base, (*foo.extra_bars)[i]->alpha) << i;
+ EXPECT_EQ(base + 20, (*foo.extra_bars)[i]->beta) << i;
+ EXPECT_EQ(base + 40, (*foo.extra_bars)[i]->gamma) << i;
+ EXPECT_EQ(type, (*foo.extra_bars)[i]->type) << i;
+ }
+
+ EXPECT_EQ(10u, foo.data->size());
+ for (size_t i = 0; i < foo.data->size(); ++i) {
+ EXPECT_EQ(static_cast<uint8_t>(foo.data->size() - i), (*foo.data)[i]) << i;
+ }
+
+ EXPECT_TRUE(foo.input_streams);
+ EXPECT_EQ(2u, foo.input_streams->size());
+
+ EXPECT_TRUE(foo.output_streams);
+ EXPECT_EQ(2u, foo.output_streams->size());
+
+ EXPECT_EQ(2u, foo.array_of_array_of_bools->size());
+ for (size_t i = 0; i < foo.array_of_array_of_bools->size(); ++i) {
+ EXPECT_EQ(2u, (*foo.array_of_array_of_bools)[i].size());
+ for (size_t j = 0; j < (*foo.array_of_array_of_bools)[i].size(); ++j) {
+ EXPECT_EQ(bool(j), (*foo.array_of_array_of_bools)[i][j]);
+ }
+ }
+}
+
+void PrintSpacer(int depth) {
+ for (int i = 0; i < depth; ++i)
+ std::cout << " ";
+}
+
+void Print(int depth, const char* name, bool value) {
+ PrintSpacer(depth);
+ std::cout << name << ": " << (value ? "true" : "false") << std::endl;
+}
+
+void Print(int depth, const char* name, int32_t value) {
+ PrintSpacer(depth);
+ std::cout << name << ": " << value << std::endl;
+}
+
+void Print(int depth, const char* name, uint8_t value) {
+ PrintSpacer(depth);
+ std::cout << name << ": " << uint32_t(value) << std::endl;
+}
+
+template <typename H>
+void Print(int depth,
+ const char* name,
+ const mojo::ScopedHandleBase<H>& value) {
+ PrintSpacer(depth);
+ std::cout << name << ": 0x" << std::hex << value.get().value() << std::endl;
+}
+
+void Print(int depth, const char* name, const std::string& str) {
+ PrintSpacer(depth);
+ std::cout << name << ": \"" << str << "\"" << std::endl;
+}
+
+void Print(int depth, const char* name, const BarPtr& bar) {
+ PrintSpacer(depth);
+ std::cout << name << ":" << std::endl;
+ if (!bar.is_null()) {
+ ++depth;
+ Print(depth, "alpha", bar->alpha);
+ Print(depth, "beta", bar->beta);
+ Print(depth, "gamma", bar->gamma);
+ Print(depth, "packed", bar.To<int32_t>());
+ --depth;
+ }
+}
+
+template <typename T>
+void Print(int depth, const char* name, const std::vector<T>& array) {
+ PrintSpacer(depth);
+ std::cout << name << ":" << std::endl;
+ ++depth;
+ for (size_t i = 0; i < array.size(); ++i) {
+ std::stringstream buf;
+ buf << i;
+ Print(depth, buf.str().data(), array.at(i));
+ }
+ --depth;
+}
+
+template <typename T>
+void Print(int depth,
+ const char* name,
+ const base::Optional<std::vector<T>>& array) {
+ if (array)
+ Print(depth, name, *array);
+ else
+ Print(depth, name, std::vector<T>());
+}
+
+void Print(int depth, const char* name, const FooPtr& foo) {
+ PrintSpacer(depth);
+ std::cout << name << ":" << std::endl;
+ if (!foo.is_null()) {
+ ++depth;
+ Print(depth, "name", foo->name);
+ Print(depth, "x", foo->x);
+ Print(depth, "y", foo->y);
+ Print(depth, "a", foo->a);
+ Print(depth, "b", foo->b);
+ Print(depth, "c", foo->c);
+ Print(depth, "bar", foo->bar);
+ Print(depth, "extra_bars", foo->extra_bars);
+ Print(depth, "data", foo->data);
+ Print(depth, "source", foo->source);
+ Print(depth, "input_streams", foo->input_streams);
+ Print(depth, "output_streams", foo->output_streams);
+ Print(depth, "array_of_array_of_bools", foo->array_of_array_of_bools);
+ --depth;
+ }
+}
+
+void DumpHex(const uint8_t* bytes, uint32_t num_bytes) {
+ for (uint32_t i = 0; i < num_bytes; ++i) {
+ std::cout << std::setw(2) << std::setfill('0') << std::hex
+ << uint32_t(bytes[i]);
+
+ if (i % 16 == 15) {
+ std::cout << std::endl;
+ continue;
+ }
+
+ if (i % 2 == 1)
+ std::cout << " ";
+ if (i % 8 == 7)
+ std::cout << " ";
+ }
+}
+
+class ServiceImpl : public Service {
+ public:
+ void Frobinate(FooPtr foo,
+ BazOptions baz,
+ PortPtr port,
+ const Service::FrobinateCallback& callback) override {
+ // Users code goes here to handle the incoming Frobinate message.
+
+ // We mainly check that we're given the expected arguments.
+ EXPECT_FALSE(foo.is_null());
+ if (!foo.is_null())
+ CheckFoo(*foo);
+ EXPECT_EQ(BazOptions::EXTRA, baz);
+
+ if (g_dump_message_as_text) {
+ // Also dump the Foo structure and all of its members.
+ std::cout << "Frobinate:" << std::endl;
+ int depth = 1;
+ Print(depth, "foo", foo);
+ Print(depth, "baz", static_cast<int32_t>(baz));
+ Print(depth, "port", port.get());
+ }
+ callback.Run(5);
+ }
+
+ void GetPort(mojo::InterfaceRequest<Port> port_request) override {}
+};
+
+class ServiceProxyImpl : public ServiceProxy {
+ public:
+ explicit ServiceProxyImpl(mojo::MessageReceiverWithResponder* receiver)
+ : ServiceProxy(receiver) {}
+};
+
+class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder {
+ public:
+ bool Accept(mojo::Message* message) override {
+ // Imagine some IPC happened here.
+
+ if (g_dump_message_as_hex) {
+ DumpHex(reinterpret_cast<const uint8_t*>(message->data()),
+ message->data_num_bytes());
+ }
+
+ // In the receiving process, an implementation of ServiceStub is known to
+ // the system. It receives the incoming message.
+ ServiceImpl impl;
+
+ ServiceStub<> stub;
+ stub.set_sink(&impl);
+ return stub.Accept(message);
+ }
+
+ bool AcceptWithResponder(
+ mojo::Message* message,
+ std::unique_ptr<mojo::MessageReceiver> responder) override {
+ return false;
+ }
+};
+
+using BindingsSampleTest = testing::Test;
+
+TEST_F(BindingsSampleTest, Basic) {
+ SimpleMessageReceiver receiver;
+
+ // User has a proxy to a Service somehow.
+ Service* service = new ServiceProxyImpl(&receiver);
+
+ // User constructs a message to send.
+
+ // Notice that it doesn't matter in what order the structs / arrays are
+ // allocated. Here, the various members of Foo are allocated before Foo is
+ // allocated.
+
+ FooPtr foo = MakeFoo();
+ CheckFoo(*foo);
+
+ PortPtr port;
+ service->Frobinate(std::move(foo), Service::BazOptions::EXTRA,
+ std::move(port), Service::FrobinateCallback());
+
+ delete service;
+}
+
+TEST_F(BindingsSampleTest, DefaultValues) {
+ DefaultsTestPtr defaults(DefaultsTest::New());
+ EXPECT_EQ(-12, defaults->a0);
+ EXPECT_EQ(kTwelve, defaults->a1);
+ EXPECT_EQ(1234, defaults->a2);
+ EXPECT_EQ(34567U, defaults->a3);
+ EXPECT_EQ(123456, defaults->a4);
+ EXPECT_EQ(3456789012U, defaults->a5);
+ EXPECT_EQ(-111111111111LL, defaults->a6);
+ EXPECT_EQ(9999999999999999999ULL, defaults->a7);
+ EXPECT_EQ(0x12345, defaults->a8);
+ EXPECT_EQ(-0x12345, defaults->a9);
+ EXPECT_EQ(1234, defaults->a10);
+ EXPECT_TRUE(defaults->a11);
+ EXPECT_FALSE(defaults->a12);
+ EXPECT_FLOAT_EQ(123.25f, defaults->a13);
+ EXPECT_DOUBLE_EQ(1234567890.123, defaults->a14);
+ EXPECT_DOUBLE_EQ(1E10, defaults->a15);
+ EXPECT_DOUBLE_EQ(-1.2E+20, defaults->a16);
+ EXPECT_DOUBLE_EQ(1.23E-20, defaults->a17);
+ EXPECT_TRUE(defaults->a18.empty());
+ EXPECT_TRUE(defaults->a19.empty());
+ EXPECT_EQ(Bar::Type::BOTH, defaults->a20);
+ EXPECT_TRUE(defaults->a21.is_null());
+ ASSERT_FALSE(defaults->a22.is_null());
+ EXPECT_EQ(imported::Shape::RECTANGLE, defaults->a22->shape);
+ EXPECT_EQ(imported::Color::BLACK, defaults->a22->color);
+ EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, defaults->a23);
+ EXPECT_EQ(0x123456789, defaults->a24);
+ EXPECT_EQ(-0x123456789, defaults->a25);
+}
+
+} // namespace
+} // namespace sample
diff --git a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc
new file mode 100644
index 0000000000..275f10f9e7
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/serialization_warning_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.
+
+// Serialization warnings are only recorded when DLOG is enabled.
+#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
+
+#include <stddef.h>
+#include <utility>
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using mojo::internal::ContainerValidateParams;
+
+// Creates an array of arrays of handles (2 X 3) for testing.
+std::vector<base::Optional<std::vector<ScopedHandle>>>
+CreateTestNestedHandleArray() {
+ std::vector<base::Optional<std::vector<ScopedHandle>>> array(2);
+ for (size_t i = 0; i < array.size(); ++i) {
+ std::vector<ScopedHandle> nested_array(3);
+ for (size_t j = 0; j < nested_array.size(); ++j) {
+ MessagePipe pipe;
+ nested_array[j] = ScopedHandle::From(std::move(pipe.handle1));
+ }
+ array[i].emplace(std::move(nested_array));
+ }
+
+ return array;
+}
+
+class SerializationWarningTest : public testing::Test {
+ public:
+ ~SerializationWarningTest() override {}
+
+ protected:
+ template <typename T>
+ void TestWarning(T obj, mojo::internal::ValidationError expected_warning) {
+ using MojomType = typename T::Struct::DataView;
+
+ warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
+
+ mojo::internal::SerializationContext context;
+ mojo::internal::FixedBufferForTesting buf(
+ mojo::internal::PrepareToSerialize<MojomType>(obj, &context));
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::Serialize<MojomType>(obj, &buf, &data, &context);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ template <typename MojomType, typename T>
+ void TestArrayWarning(T obj,
+ mojo::internal::ValidationError expected_warning,
+ const ContainerValidateParams* validate_params) {
+ warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
+
+ mojo::internal::SerializationContext context;
+ mojo::internal::FixedBufferForTesting buf(
+ mojo::internal::PrepareToSerialize<MojomType>(obj, &context));
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::Serialize<MojomType>(obj, &buf, &data, validate_params,
+ &context);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ template <typename T>
+ void TestUnionWarning(T obj,
+ mojo::internal::ValidationError expected_warning) {
+ using MojomType = typename T::Struct::DataView;
+
+ warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
+
+ mojo::internal::SerializationContext context;
+ mojo::internal::FixedBufferForTesting buf(
+ mojo::internal::PrepareToSerialize<MojomType>(obj, false, &context));
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::Serialize<MojomType>(obj, &buf, &data, false, &context);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ mojo::internal::SerializationWarningObserverForTesting warning_observer_;
+};
+
+TEST_F(SerializationWarningTest, HandleInStruct) {
+ Struct2Ptr test_struct(Struct2::New());
+ EXPECT_FALSE(test_struct->hdl.is_valid());
+
+ TestWarning(std::move(test_struct),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE);
+
+ test_struct = Struct2::New();
+ MessagePipe pipe;
+ test_struct->hdl = ScopedHandle::From(std::move(pipe.handle1));
+
+ TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, StructInStruct) {
+ Struct3Ptr test_struct(Struct3::New());
+ EXPECT_TRUE(!test_struct->struct_1);
+
+ TestWarning(std::move(test_struct),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct3::New();
+ test_struct->struct_1 = Struct1::New();
+
+ TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfStructsInStruct) {
+ Struct4Ptr test_struct(Struct4::New());
+ test_struct->data.resize(1);
+
+ TestWarning(std::move(test_struct),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct4::New();
+ test_struct->data.resize(0);
+
+ TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE);
+
+ test_struct = Struct4::New();
+ test_struct->data.resize(1);
+ test_struct->data[0] = Struct1::New();
+
+ TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, FixedArrayOfStructsInStruct) {
+ Struct5Ptr test_struct(Struct5::New());
+ test_struct->pair.resize(1);
+ test_struct->pair[0] = Struct1::New();
+
+ TestWarning(std::move(test_struct),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER);
+
+ test_struct = Struct5::New();
+ test_struct->pair.resize(2);
+ test_struct->pair[0] = Struct1::New();
+ test_struct->pair[1] = Struct1::New();
+
+ TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfArraysOfHandles) {
+ using MojomType = ArrayDataView<ArrayDataView<ScopedHandle>>;
+ auto test_array = CreateTestNestedHandleArray();
+ test_array[0] = base::nullopt;
+ (*test_array[1])[0] = ScopedHandle();
+
+ ContainerValidateParams validate_params_0(
+ 0, true, new ContainerValidateParams(0, true, nullptr));
+ TestArrayWarning<MojomType>(std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_NONE,
+ &validate_params_0);
+
+ test_array = CreateTestNestedHandleArray();
+ test_array[0] = base::nullopt;
+ ContainerValidateParams validate_params_1(
+ 0, false, new ContainerValidateParams(0, true, nullptr));
+ TestArrayWarning<MojomType>(
+ std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ &validate_params_1);
+
+ test_array = CreateTestNestedHandleArray();
+ (*test_array[1])[0] = ScopedHandle();
+ ContainerValidateParams validate_params_2(
+ 0, true, new ContainerValidateParams(0, false, nullptr));
+ TestArrayWarning<MojomType>(
+ std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ &validate_params_2);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfStrings) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ std::vector<std::string> test_array(3);
+ for (size_t i = 0; i < test_array.size(); ++i)
+ test_array[i] = "hello";
+
+ ContainerValidateParams validate_params_0(
+ 0, true, new ContainerValidateParams(0, false, nullptr));
+ TestArrayWarning<MojomType>(std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_NONE,
+ &validate_params_0);
+
+ std::vector<base::Optional<std::string>> optional_test_array(3);
+ ContainerValidateParams validate_params_1(
+ 0, false, new ContainerValidateParams(0, false, nullptr));
+ TestArrayWarning<MojomType>(
+ std::move(optional_test_array),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ &validate_params_1);
+
+ test_array = std::vector<std::string>(2);
+ ContainerValidateParams validate_params_2(
+ 3, true, new ContainerValidateParams(0, false, nullptr));
+ TestArrayWarning<MojomType>(
+ std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ &validate_params_2);
+}
+
+TEST_F(SerializationWarningTest, StructInUnion) {
+ DummyStructPtr dummy(nullptr);
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_dummy(std::move(dummy));
+
+ TestUnionWarning(std::move(obj),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+}
+
+TEST_F(SerializationWarningTest, UnionInUnion) {
+ PodUnionPtr pod(nullptr);
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_pod_union(std::move(pod));
+
+ TestUnionWarning(std::move(obj),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+}
+
+TEST_F(SerializationWarningTest, HandleInUnion) {
+ ScopedMessagePipeHandle pipe;
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_message_pipe(std::move(pipe));
+
+ TestUnionWarning(std::move(handle),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
+
+#endif
diff --git a/mojo/public/cpp/bindings/tests/shared_rect.h b/mojo/public/cpp/bindings/tests/shared_rect.h
new file mode 100644
index 0000000000..c0a4771c14
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/shared_rect.h
@@ -0,0 +1,43 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace test {
+
+// An implementation of a hypothetical Rect type specifically for consumers in
+// both Chromium and Blink.
+class SharedRect {
+ public:
+ SharedRect() {}
+ SharedRect(int x, int y, int width, int height)
+ : x_(x), y_(y), width_(width), height_(height) {}
+
+ int x() const { return x_; }
+ void set_x(int x) { x_ = x; }
+
+ int y() const { return y_; }
+ void set_y(int y) { y_ = y; }
+
+ int width() const { return width_; }
+ void set_width(int width) { width_ = width; }
+
+ int height() const { return height_; }
+ void set_height(int height) { height_ = height; }
+
+ private:
+ int x_ = 0;
+ int y_ = 0;
+ int width_ = 0;
+ int height_ = 0;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_
diff --git a/mojo/public/cpp/bindings/tests/shared_rect_traits.h b/mojo/public/cpp/bindings/tests/shared_rect_traits.h
new file mode 100644
index 0000000000..bbf04d5f6e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/shared_rect_traits.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_
+
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/tests/shared_rect.h"
+#include "mojo/public/interfaces/bindings/tests/rect.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<test::SharedTypemappedRectDataView, test::SharedRect> {
+ static int x(const test::SharedRect& r) { return r.x(); }
+ static int y(const test::SharedRect& r) { return r.y(); }
+ static int width(const test::SharedRect& r) { return r.width(); }
+ static int height(const test::SharedRect& r) { return r.height(); }
+
+ static bool Read(test::SharedTypemappedRectDataView r,
+ test::SharedRect* out) {
+ out->set_x(r.x());
+ out->set_y(r.y());
+ out->set_width(r.width());
+ out->set_height(r.height());
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
new file mode 100644
index 0000000000..77b448a215
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
@@ -0,0 +1,553 @@
+// Copyright 2015 The Chromium Authors. 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/callback.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/tests/rect_blink.h"
+#include "mojo/public/cpp/bindings/tests/rect_chromium.h"
+#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h"
+#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h"
+#include "mojo/public/cpp/bindings/tests/variant_test_util.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h"
+#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+template <typename T>
+void DoExpectResult(const T& expected,
+ const base::Closure& callback,
+ const T& actual) {
+ EXPECT_EQ(expected.x(), actual.x());
+ EXPECT_EQ(expected.y(), actual.y());
+ EXPECT_EQ(expected.width(), actual.width());
+ EXPECT_EQ(expected.height(), actual.height());
+ callback.Run();
+}
+
+template <typename T>
+base::Callback<void(const T&)> ExpectResult(const T& r,
+ const base::Closure& callback) {
+ return base::Bind(&DoExpectResult<T>, r, callback);
+}
+
+template <typename T>
+void DoFail(const std::string& reason, const T&) {
+ EXPECT_TRUE(false) << reason;
+}
+
+template <typename T>
+base::Callback<void(const T&)> Fail(const std::string& reason) {
+ return base::Bind(&DoFail<T>, reason);
+}
+
+template <typename T>
+void ExpectError(InterfacePtr<T> *proxy, const base::Closure& callback) {
+ proxy->set_connection_error_handler(callback);
+}
+
+// This implements the generated Chromium variant of RectService.
+class ChromiumRectServiceImpl : public RectService {
+ public:
+ ChromiumRectServiceImpl() {}
+
+ // mojo::test::RectService:
+ void AddRect(const RectChromium& r) override {
+ if (r.GetArea() > largest_rect_.GetArea())
+ largest_rect_ = r;
+ }
+
+ void GetLargestRect(const GetLargestRectCallback& callback) override {
+ callback.Run(largest_rect_);
+ }
+
+ void PassSharedRect(const SharedRect& r,
+ const PassSharedRectCallback& callback) override {
+ callback.Run(r);
+ }
+
+ private:
+ RectChromium largest_rect_;
+};
+
+// This implements the generated Blink variant of RectService.
+class BlinkRectServiceImpl : public blink::RectService {
+ public:
+ BlinkRectServiceImpl() {}
+
+ // mojo::test::blink::RectService:
+ void AddRect(const RectBlink& r) override {
+ if (r.computeArea() > largest_rect_.computeArea()) {
+ largest_rect_.setX(r.x());
+ largest_rect_.setY(r.y());
+ largest_rect_.setWidth(r.width());
+ largest_rect_.setHeight(r.height());
+ }
+ }
+
+ void GetLargestRect(const GetLargestRectCallback& callback) override {
+ callback.Run(largest_rect_);
+ }
+
+ void PassSharedRect(const SharedRect& r,
+ const PassSharedRectCallback& callback) override {
+ callback.Run(r);
+ }
+
+ private:
+ RectBlink largest_rect_;
+};
+
+// A test which runs both Chromium and Blink implementations of a RectService.
+class StructTraitsTest : public testing::Test,
+ public TraitsTestService {
+ public:
+ StructTraitsTest() {}
+
+ protected:
+ void BindToChromiumService(RectServiceRequest request) {
+ chromium_bindings_.AddBinding(&chromium_service_, std::move(request));
+ }
+ void BindToChromiumService(blink::RectServiceRequest request) {
+ chromium_bindings_.AddBinding(
+ &chromium_service_,
+ ConvertInterfaceRequest<RectService>(std::move(request)));
+ }
+
+ void BindToBlinkService(blink::RectServiceRequest request) {
+ blink_bindings_.AddBinding(&blink_service_, std::move(request));
+ }
+ void BindToBlinkService(RectServiceRequest request) {
+ blink_bindings_.AddBinding(
+ &blink_service_,
+ ConvertInterfaceRequest<blink::RectService>(std::move(request)));
+ }
+
+ TraitsTestServicePtr GetTraitsTestProxy() {
+ return traits_test_bindings_.CreateInterfacePtrAndBind(this);
+ }
+
+ private:
+ // TraitsTestService:
+ void EchoStructWithTraits(
+ const StructWithTraitsImpl& s,
+ const EchoStructWithTraitsCallback& callback) override {
+ callback.Run(s);
+ }
+
+ void EchoTrivialStructWithTraits(
+ TrivialStructWithTraitsImpl s,
+ const EchoTrivialStructWithTraitsCallback& callback) override {
+ callback.Run(s);
+ }
+
+ void EchoMoveOnlyStructWithTraits(
+ MoveOnlyStructWithTraitsImpl s,
+ const EchoMoveOnlyStructWithTraitsCallback& callback) override {
+ callback.Run(std::move(s));
+ }
+
+ void EchoNullableMoveOnlyStructWithTraits(
+ base::Optional<MoveOnlyStructWithTraitsImpl> s,
+ const EchoNullableMoveOnlyStructWithTraitsCallback& callback) override {
+ callback.Run(std::move(s));
+ }
+
+ void EchoEnumWithTraits(EnumWithTraitsImpl e,
+ const EchoEnumWithTraitsCallback& callback) override {
+ callback.Run(e);
+ }
+
+ void EchoStructWithTraitsForUniquePtr(
+ std::unique_ptr<int> e,
+ const EchoStructWithTraitsForUniquePtrCallback& callback) override {
+ callback.Run(std::move(e));
+ }
+
+ void EchoNullableStructWithTraitsForUniquePtr(
+ std::unique_ptr<int> e,
+ const EchoNullableStructWithTraitsForUniquePtrCallback& callback)
+ override {
+ callback.Run(std::move(e));
+ }
+
+ void EchoUnionWithTraits(
+ std::unique_ptr<test::UnionWithTraitsBase> u,
+ const EchoUnionWithTraitsCallback& callback) override {
+ callback.Run(std::move(u));
+ }
+
+ base::MessageLoop loop_;
+
+ ChromiumRectServiceImpl chromium_service_;
+ BindingSet<RectService> chromium_bindings_;
+
+ BlinkRectServiceImpl blink_service_;
+ BindingSet<blink::RectService> blink_bindings_;
+
+ BindingSet<TraitsTestService> traits_test_bindings_;
+};
+
+} // namespace
+
+TEST_F(StructTraitsTest, ChromiumProxyToChromiumService) {
+ RectServicePtr chromium_proxy;
+ BindToChromiumService(MakeRequest(&chromium_proxy));
+ {
+ base::RunLoop loop;
+ chromium_proxy->AddRect(RectChromium(1, 1, 4, 5));
+ chromium_proxy->AddRect(RectChromium(-1, -1, 2, 2));
+ chromium_proxy->GetLargestRect(
+ ExpectResult(RectChromium(1, 1, 4, 5), loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassSharedRect(
+ {1, 2, 3, 4},
+ ExpectResult(SharedRect({1, 2, 3, 4}), loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(StructTraitsTest, ChromiumToBlinkService) {
+ RectServicePtr chromium_proxy;
+ BindToBlinkService(MakeRequest(&chromium_proxy));
+ {
+ base::RunLoop loop;
+ chromium_proxy->AddRect(RectChromium(1, 1, 4, 5));
+ chromium_proxy->AddRect(RectChromium(2, 2, 5, 5));
+ chromium_proxy->GetLargestRect(
+ ExpectResult(RectChromium(2, 2, 5, 5), loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ chromium_proxy->PassSharedRect(
+ {1, 2, 3, 4},
+ ExpectResult(SharedRect({1, 2, 3, 4}), loop.QuitClosure()));
+ loop.Run();
+ }
+ // The Blink service should drop our connection because RectBlink's
+ // deserializer rejects negative origins.
+ {
+ base::RunLoop loop;
+ ExpectError(&chromium_proxy, loop.QuitClosure());
+ chromium_proxy->AddRect(RectChromium(-1, -1, 2, 2));
+ chromium_proxy->GetLargestRect(
+ Fail<RectChromium>("The pipe should have been closed."));
+ loop.Run();
+ }
+}
+
+TEST_F(StructTraitsTest, BlinkProxyToBlinkService) {
+ blink::RectServicePtr blink_proxy;
+ BindToBlinkService(MakeRequest(&blink_proxy));
+ {
+ base::RunLoop loop;
+ blink_proxy->AddRect(RectBlink(1, 1, 4, 5));
+ blink_proxy->AddRect(RectBlink(10, 10, 20, 20));
+ blink_proxy->GetLargestRect(
+ ExpectResult(RectBlink(10, 10, 20, 20), loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ blink_proxy->PassSharedRect(
+ {4, 3, 2, 1},
+ ExpectResult(SharedRect({4, 3, 2, 1}), loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(StructTraitsTest, BlinkProxyToChromiumService) {
+ blink::RectServicePtr blink_proxy;
+ BindToChromiumService(MakeRequest(&blink_proxy));
+ {
+ base::RunLoop loop;
+ blink_proxy->AddRect(RectBlink(1, 1, 4, 5));
+ blink_proxy->AddRect(RectBlink(10, 10, 2, 2));
+ blink_proxy->GetLargestRect(
+ ExpectResult(RectBlink(1, 1, 4, 5), loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ blink_proxy->PassSharedRect(
+ {4, 3, 2, 1},
+ ExpectResult(SharedRect({4, 3, 2, 1}), loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+void ExpectStructWithTraits(const StructWithTraitsImpl& expected,
+ const base::Closure& closure,
+ const StructWithTraitsImpl& passed) {
+ EXPECT_EQ(expected.get_enum(), passed.get_enum());
+ EXPECT_EQ(expected.get_bool(), passed.get_bool());
+ EXPECT_EQ(expected.get_uint32(), passed.get_uint32());
+ EXPECT_EQ(expected.get_uint64(), passed.get_uint64());
+ EXPECT_EQ(expected.get_string(), passed.get_string());
+ EXPECT_EQ(expected.get_string_array(), passed.get_string_array());
+ EXPECT_EQ(expected.get_struct(), passed.get_struct());
+ EXPECT_EQ(expected.get_struct_array(), passed.get_struct_array());
+ EXPECT_EQ(expected.get_struct_map(), passed.get_struct_map());
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, EchoStructWithTraits) {
+ StructWithTraitsImpl input;
+ input.set_enum(EnumWithTraitsImpl::CUSTOM_VALUE_1);
+ input.set_bool(true);
+ input.set_uint32(7);
+ input.set_uint64(42);
+ input.set_string("hello world!");
+ input.get_mutable_string_array().assign({"hello", "world!"});
+ input.get_mutable_string_set().insert("hello");
+ input.get_mutable_string_set().insert("world!");
+ input.get_mutable_struct().value = 42;
+ input.get_mutable_struct_array().resize(2);
+ input.get_mutable_struct_array()[0].value = 1;
+ input.get_mutable_struct_array()[1].value = 2;
+ input.get_mutable_struct_map()["hello"] = NestedStructWithTraitsImpl(1024);
+ input.get_mutable_struct_map()["world"] = NestedStructWithTraitsImpl(2048);
+
+ base::RunLoop loop;
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ proxy->EchoStructWithTraits(
+ input,
+ base::Bind(&ExpectStructWithTraits, input, loop.QuitClosure()));
+ loop.Run();
+}
+
+TEST_F(StructTraitsTest, CloneStructWithTraitsContainer) {
+ StructWithTraitsContainerPtr container = StructWithTraitsContainer::New();
+ container->f_struct.set_uint32(7);
+ container->f_struct.set_uint64(42);
+ StructWithTraitsContainerPtr cloned_container = container.Clone();
+ EXPECT_EQ(7u, cloned_container->f_struct.get_uint32());
+ EXPECT_EQ(42u, cloned_container->f_struct.get_uint64());
+}
+
+void ExpectTrivialStructWithTraits(TrivialStructWithTraitsImpl expected,
+ const base::Closure& closure,
+ TrivialStructWithTraitsImpl passed) {
+ EXPECT_EQ(expected.value, passed.value);
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, EchoTrivialStructWithTraits) {
+ TrivialStructWithTraitsImpl input;
+ input.value = 42;
+
+ base::RunLoop loop;
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ proxy->EchoTrivialStructWithTraits(
+ input,
+ base::Bind(&ExpectTrivialStructWithTraits, input, loop.QuitClosure()));
+ loop.Run();
+}
+
+void CaptureMessagePipe(ScopedMessagePipeHandle* storage,
+ const base::Closure& closure,
+ MoveOnlyStructWithTraitsImpl passed) {
+ storage->reset(MessagePipeHandle(
+ passed.get_mutable_handle().release().value()));
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, EchoMoveOnlyStructWithTraits) {
+ MessagePipe mp;
+ MoveOnlyStructWithTraitsImpl input;
+ input.get_mutable_handle().reset(mp.handle0.release());
+
+ base::RunLoop loop;
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ ScopedMessagePipeHandle received;
+ proxy->EchoMoveOnlyStructWithTraits(
+ std::move(input),
+ base::Bind(&CaptureMessagePipe, &received, loop.QuitClosure()));
+ loop.Run();
+
+ ASSERT_TRUE(received.is_valid());
+
+ // Verify that the message pipe handle is correctly passed.
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(mp.handle1.get(), kHello, kHelloSize, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE));
+
+ char buffer[10] = {0};
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(received.get(), buffer, &buffer_size, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+}
+
+void CaptureNullableMoveOnlyStructWithTraitsImpl(
+ base::Optional<MoveOnlyStructWithTraitsImpl>* storage,
+ const base::Closure& closure,
+ base::Optional<MoveOnlyStructWithTraitsImpl> passed) {
+ *storage = std::move(passed);
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, EchoNullableMoveOnlyStructWithTraits) {
+ base::RunLoop loop;
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ base::Optional<MoveOnlyStructWithTraitsImpl> received;
+ proxy->EchoNullableMoveOnlyStructWithTraits(
+ base::nullopt, base::Bind(&CaptureNullableMoveOnlyStructWithTraitsImpl,
+ &received, loop.QuitClosure()));
+ loop.Run();
+
+ EXPECT_FALSE(received);
+}
+
+void ExpectEnumWithTraits(EnumWithTraitsImpl expected_value,
+ const base::Closure& closure,
+ EnumWithTraitsImpl value) {
+ EXPECT_EQ(expected_value, value);
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, EchoEnumWithTraits) {
+ base::RunLoop loop;
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ proxy->EchoEnumWithTraits(
+ EnumWithTraitsImpl::CUSTOM_VALUE_1,
+ base::Bind(&ExpectEnumWithTraits, EnumWithTraitsImpl::CUSTOM_VALUE_1,
+ loop.QuitClosure()));
+ loop.Run();
+}
+
+TEST_F(StructTraitsTest, SerializeStructWithTraits) {
+ StructWithTraitsImpl input;
+ input.set_enum(EnumWithTraitsImpl::CUSTOM_VALUE_1);
+ input.set_bool(true);
+ input.set_uint32(7);
+ input.set_uint64(42);
+ input.set_string("hello world!");
+ input.get_mutable_string_array().assign({ "hello", "world!" });
+ input.get_mutable_string_set().insert("hello");
+ input.get_mutable_string_set().insert("world!");
+ input.get_mutable_struct().value = 42;
+ input.get_mutable_struct_array().resize(2);
+ input.get_mutable_struct_array()[0].value = 1;
+ input.get_mutable_struct_array()[1].value = 2;
+ input.get_mutable_struct_map()["hello"] = NestedStructWithTraitsImpl(1024);
+ input.get_mutable_struct_map()["world"] = NestedStructWithTraitsImpl(2048);
+
+ auto data = StructWithTraits::Serialize(&input);
+ StructWithTraitsImpl output;
+ ASSERT_TRUE(StructWithTraits::Deserialize(std::move(data), &output));
+
+ EXPECT_EQ(input.get_enum(), output.get_enum());
+ EXPECT_EQ(input.get_bool(), output.get_bool());
+ EXPECT_EQ(input.get_uint32(), output.get_uint32());
+ EXPECT_EQ(input.get_uint64(), output.get_uint64());
+ EXPECT_EQ(input.get_string(), output.get_string());
+ EXPECT_EQ(input.get_string_array(), output.get_string_array());
+ EXPECT_EQ(input.get_string_set(), output.get_string_set());
+ EXPECT_EQ(input.get_struct(), output.get_struct());
+ EXPECT_EQ(input.get_struct_array(), output.get_struct_array());
+ EXPECT_EQ(input.get_struct_map(), output.get_struct_map());
+}
+
+void ExpectUniquePtr(std::unique_ptr<int> expected,
+ const base::Closure& closure,
+ std::unique_ptr<int> value) {
+ ASSERT_EQ(!expected, !value);
+ if (expected)
+ EXPECT_EQ(*expected, *value);
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, TypemapUniquePtr) {
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ {
+ base::RunLoop loop;
+ proxy->EchoStructWithTraitsForUniquePtr(
+ base::MakeUnique<int>(12345),
+ base::Bind(&ExpectUniquePtr, base::Passed(base::MakeUnique<int>(12345)),
+ loop.QuitClosure()));
+ loop.Run();
+ }
+ {
+ base::RunLoop loop;
+ proxy->EchoNullableStructWithTraitsForUniquePtr(
+ nullptr, base::Bind(&ExpectUniquePtr, nullptr, loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(StructTraitsTest, EchoUnionWithTraits) {
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ {
+ std::unique_ptr<test::UnionWithTraitsBase> input(
+ new test::UnionWithTraitsInt32(1234));
+ base::RunLoop loop;
+ proxy->EchoUnionWithTraits(
+ std::move(input),
+ base::Bind(
+ [](const base::Closure& quit_closure,
+ std::unique_ptr<test::UnionWithTraitsBase> passed) {
+ ASSERT_EQ(test::UnionWithTraitsBase::Type::INT32, passed->type());
+ EXPECT_EQ(1234,
+ static_cast<test::UnionWithTraitsInt32*>(passed.get())
+ ->value());
+ quit_closure.Run();
+
+ },
+ loop.QuitClosure()));
+ loop.Run();
+ }
+
+ {
+ std::unique_ptr<test::UnionWithTraitsBase> input(
+ new test::UnionWithTraitsStruct(4321));
+ base::RunLoop loop;
+ proxy->EchoUnionWithTraits(
+ std::move(input),
+ base::Bind(
+ [](const base::Closure& quit_closure,
+ std::unique_ptr<test::UnionWithTraitsBase> passed) {
+ ASSERT_EQ(test::UnionWithTraitsBase::Type::STRUCT,
+ passed->type());
+ EXPECT_EQ(4321,
+ static_cast<test::UnionWithTraitsStruct*>(passed.get())
+ ->get_struct()
+ .value);
+ quit_closure.Run();
+
+ },
+ loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc
new file mode 100644
index 0000000000..a687052706
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_unittest.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 <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <utility>
+
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/interfaces/bindings/tests/test_export2.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+RectPtr MakeRect(int32_t factor = 1) {
+ return Rect::New(1 * factor, 2 * factor, 10 * factor, 20 * factor);
+}
+
+void CheckRect(const Rect& rect, int32_t factor = 1) {
+ EXPECT_EQ(1 * factor, rect.x);
+ EXPECT_EQ(2 * factor, rect.y);
+ EXPECT_EQ(10 * factor, rect.width);
+ EXPECT_EQ(20 * factor, rect.height);
+}
+
+MultiVersionStructPtr MakeMultiVersionStruct() {
+ MessagePipe pipe;
+ return MultiVersionStruct::New(123, MakeRect(5), std::string("hello"),
+ std::vector<int8_t>{10, 9, 8},
+ std::move(pipe.handle0), false, 42);
+}
+
+template <typename U, typename T>
+U SerializeAndDeserialize(T input) {
+ using InputMojomType = typename T::Struct::DataView;
+ using OutputMojomType = typename U::Struct::DataView;
+
+ using InputDataType =
+ typename mojo::internal::MojomTypeTraits<InputMojomType>::Data*;
+ using OutputDataType =
+ typename mojo::internal::MojomTypeTraits<OutputMojomType>::Data*;
+
+ mojo::internal::SerializationContext context;
+ size_t size =
+ mojo::internal::PrepareToSerialize<InputMojomType>(input, &context);
+ mojo::internal::FixedBufferForTesting buf(size + 32);
+ InputDataType data;
+ mojo::internal::Serialize<InputMojomType>(input, &buf, &data, &context);
+
+ // Set the subsequent area to a special value, so that we can find out if we
+ // mistakenly access the area.
+ void* subsequent_area = buf.Allocate(32);
+ memset(subsequent_area, 0xAA, 32);
+
+ OutputDataType output_data = reinterpret_cast<OutputDataType>(data);
+
+ U output;
+ mojo::internal::Deserialize<OutputMojomType>(output_data, &output, &context);
+ return std::move(output);
+}
+
+using StructTest = testing::Test;
+
+} // namespace
+
+TEST_F(StructTest, Rect) {
+ RectPtr rect;
+ EXPECT_TRUE(rect.is_null());
+ EXPECT_TRUE(!rect);
+ EXPECT_FALSE(rect);
+
+ rect = nullptr;
+ EXPECT_TRUE(rect.is_null());
+ EXPECT_TRUE(!rect);
+ EXPECT_FALSE(rect);
+
+ rect = MakeRect();
+ EXPECT_FALSE(rect.is_null());
+ EXPECT_FALSE(!rect);
+ EXPECT_TRUE(rect);
+
+ RectPtr null_rect = nullptr;
+ EXPECT_TRUE(null_rect.is_null());
+ EXPECT_TRUE(!null_rect);
+ EXPECT_FALSE(null_rect);
+
+ CheckRect(*rect);
+}
+
+TEST_F(StructTest, Clone) {
+ NamedRegionPtr region;
+
+ NamedRegionPtr clone_region = region.Clone();
+ EXPECT_TRUE(clone_region.is_null());
+
+ region = NamedRegion::New();
+ clone_region = region.Clone();
+ EXPECT_FALSE(clone_region->name);
+ EXPECT_FALSE(clone_region->rects);
+
+ region->name.emplace("hello world");
+ clone_region = region.Clone();
+ EXPECT_EQ(region->name, clone_region->name);
+
+ region->rects.emplace(2);
+ (*region->rects)[1] = MakeRect();
+ clone_region = region.Clone();
+ EXPECT_EQ(2u, clone_region->rects->size());
+ EXPECT_TRUE((*clone_region->rects)[0].is_null());
+ CheckRect(*(*clone_region->rects)[1]);
+
+ // NoDefaultFieldValues contains handles, so Clone() is not available, but
+ // NoDefaultFieldValuesPtr should still compile.
+ NoDefaultFieldValuesPtr no_default_field_values(NoDefaultFieldValues::New());
+ EXPECT_FALSE(no_default_field_values->f13.is_valid());
+}
+
+// Serialization test of a struct with no pointer or handle members.
+TEST_F(StructTest, Serialization_Basic) {
+ RectPtr rect(MakeRect());
+
+ size_t size = mojo::internal::PrepareToSerialize<RectDataView>(rect, nullptr);
+ EXPECT_EQ(8U + 16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::Rect_Data* data;
+ mojo::internal::Serialize<RectDataView>(rect, &buf, &data, nullptr);
+
+ RectPtr rect2;
+ mojo::internal::Deserialize<RectDataView>(data, &rect2, nullptr);
+
+ CheckRect(*rect2);
+}
+
+// Construction of a struct with struct pointers from null.
+TEST_F(StructTest, Construction_StructPointers) {
+ RectPairPtr pair;
+ EXPECT_TRUE(pair.is_null());
+
+ pair = RectPair::New();
+ EXPECT_FALSE(pair.is_null());
+ EXPECT_TRUE(pair->first.is_null());
+ EXPECT_TRUE(pair->first.is_null());
+
+ pair = nullptr;
+ EXPECT_TRUE(pair.is_null());
+}
+
+// Serialization test of a struct with struct pointers.
+TEST_F(StructTest, Serialization_StructPointers) {
+ RectPairPtr pair(RectPair::New(MakeRect(), MakeRect()));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<RectPairDataView>(pair, nullptr);
+ EXPECT_EQ(8U + 16U + 2 * (8U + 16U), size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::RectPair_Data* data;
+ mojo::internal::Serialize<RectPairDataView>(pair, &buf, &data, nullptr);
+
+ RectPairPtr pair2;
+ mojo::internal::Deserialize<RectPairDataView>(data, &pair2, nullptr);
+
+ CheckRect(*pair2->first);
+ CheckRect(*pair2->second);
+}
+
+// Serialization test of a struct with an array member.
+TEST_F(StructTest, Serialization_ArrayPointers) {
+ std::vector<RectPtr> rects;
+ for (size_t i = 0; i < 4; ++i)
+ rects.push_back(MakeRect(static_cast<int32_t>(i) + 1));
+
+ NamedRegionPtr region(
+ NamedRegion::New(std::string("region"), std::move(rects)));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<NamedRegionDataView>(region, nullptr);
+ EXPECT_EQ(8U + // header
+ 8U + // name pointer
+ 8U + // rects pointer
+ 8U + // name header
+ 8U + // name payload (rounded up)
+ 8U + // rects header
+ 4 * 8U + // rects payload (four pointers)
+ 4 * (8U + // rect header
+ 16U), // rect payload (four ints)
+ size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::NamedRegion_Data* data;
+ mojo::internal::Serialize<NamedRegionDataView>(region, &buf, &data, nullptr);
+
+ NamedRegionPtr region2;
+ mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, nullptr);
+
+ EXPECT_EQ("region", *region2->name);
+
+ EXPECT_EQ(4U, region2->rects->size());
+ for (size_t i = 0; i < region2->rects->size(); ++i)
+ CheckRect(*(*region2->rects)[i], static_cast<int32_t>(i) + 1);
+}
+
+// Serialization test of a struct with null array pointers.
+TEST_F(StructTest, Serialization_NullArrayPointers) {
+ NamedRegionPtr region(NamedRegion::New());
+ EXPECT_FALSE(region->name);
+ EXPECT_FALSE(region->rects);
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<NamedRegionDataView>(region, nullptr);
+ EXPECT_EQ(8U + // header
+ 8U + // name pointer
+ 8U, // rects pointer
+ size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::NamedRegion_Data* data;
+ mojo::internal::Serialize<NamedRegionDataView>(region, &buf, &data, nullptr);
+
+ NamedRegionPtr region2;
+ mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, nullptr);
+
+ EXPECT_FALSE(region2->name);
+ EXPECT_FALSE(region2->rects);
+}
+
+// Tests deserializing structs as a newer version.
+TEST_F(StructTest, Versioning_OldToNew) {
+ {
+ MultiVersionStructV0Ptr input(MultiVersionStructV0::New(123));
+ MultiVersionStructPtr expected_output(MultiVersionStruct::New(123));
+
+ MultiVersionStructPtr output =
+ SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructV1Ptr input(MultiVersionStructV1::New(123, MakeRect(5)));
+ MultiVersionStructPtr expected_output(
+ MultiVersionStruct::New(123, MakeRect(5)));
+
+ MultiVersionStructPtr output =
+ SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructV3Ptr input(
+ MultiVersionStructV3::New(123, MakeRect(5), std::string("hello")));
+ MultiVersionStructPtr expected_output(
+ MultiVersionStruct::New(123, MakeRect(5), std::string("hello")));
+
+ MultiVersionStructPtr output =
+ SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructV5Ptr input(MultiVersionStructV5::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+ MultiVersionStructPtr expected_output(MultiVersionStruct::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+
+ MultiVersionStructPtr output =
+ SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MessagePipe pipe;
+ MultiVersionStructV7Ptr input(MultiVersionStructV7::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8},
+ std::move(pipe.handle0), false));
+
+ MultiVersionStructPtr expected_output(MultiVersionStruct::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+ // Save the raw handle value separately so that we can compare later.
+ MojoHandle expected_handle = input->f_message_pipe.get().value();
+
+ MultiVersionStructPtr output =
+ SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_EQ(expected_handle, output->f_message_pipe.get().value());
+ output->f_message_pipe.reset();
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+}
+
+// Tests deserializing structs as an older version.
+TEST_F(StructTest, Versioning_NewToOld) {
+ {
+ MultiVersionStructPtr input = MakeMultiVersionStruct();
+ MultiVersionStructV7Ptr expected_output(MultiVersionStructV7::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+ // Save the raw handle value separately so that we can compare later.
+ MojoHandle expected_handle = input->f_message_pipe.get().value();
+
+ MultiVersionStructV7Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV7Ptr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_EQ(expected_handle, output->f_message_pipe.get().value());
+ output->f_message_pipe.reset();
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructPtr input = MakeMultiVersionStruct();
+ MultiVersionStructV5Ptr expected_output(MultiVersionStructV5::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+
+ MultiVersionStructV5Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV5Ptr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructPtr input = MakeMultiVersionStruct();
+ MultiVersionStructV3Ptr expected_output(
+ MultiVersionStructV3::New(123, MakeRect(5), std::string("hello")));
+
+ MultiVersionStructV3Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV3Ptr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructPtr input = MakeMultiVersionStruct();
+ MultiVersionStructV1Ptr expected_output(
+ MultiVersionStructV1::New(123, MakeRect(5)));
+
+ MultiVersionStructV1Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV1Ptr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructPtr input = MakeMultiVersionStruct();
+ MultiVersionStructV0Ptr expected_output(MultiVersionStructV0::New(123));
+
+ MultiVersionStructV0Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV0Ptr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+}
+
+// Serialization test for native struct.
+TEST_F(StructTest, Serialization_NativeStruct) {
+ using Data = mojo::internal::NativeStruct_Data;
+ {
+ // Serialization of a null native struct.
+ NativeStructPtr native;
+ size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
+ native, nullptr);
+ EXPECT_EQ(0u, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ Data* data = nullptr;
+ mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
+ &data, nullptr);
+
+ EXPECT_EQ(nullptr, data);
+
+ NativeStructPtr output_native;
+ mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
+ nullptr);
+ EXPECT_TRUE(output_native.is_null());
+ }
+
+ {
+ // Serialization of a native struct with null data.
+ NativeStructPtr native(NativeStruct::New());
+ size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
+ native, nullptr);
+ EXPECT_EQ(0u, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ Data* data = nullptr;
+ mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
+ &data, nullptr);
+
+ EXPECT_EQ(nullptr, data);
+
+ NativeStructPtr output_native;
+ mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
+ nullptr);
+ EXPECT_TRUE(output_native.is_null());
+ }
+
+ {
+ NativeStructPtr native(NativeStruct::New());
+ native->data = std::vector<uint8_t>{'X', 'Y'};
+
+ size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
+ native, nullptr);
+ EXPECT_EQ(16u, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ Data* data = nullptr;
+ mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
+ &data, nullptr);
+
+ EXPECT_NE(nullptr, data);
+
+ NativeStructPtr output_native;
+ mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
+ nullptr);
+ ASSERT_TRUE(output_native);
+ ASSERT_FALSE(output_native->data->empty());
+ EXPECT_EQ(2u, output_native->data->size());
+ EXPECT_EQ('X', (*output_native->data)[0]);
+ EXPECT_EQ('Y', (*output_native->data)[1]);
+ }
+}
+
+TEST_F(StructTest, Serialization_PublicAPI) {
+ {
+ // A null struct pointer.
+ RectPtr null_struct;
+ auto data = Rect::Serialize(&null_struct);
+ EXPECT_TRUE(data.empty());
+
+ // Initialize it to non-null.
+ RectPtr output(Rect::New());
+ ASSERT_TRUE(Rect::Deserialize(data, &output));
+ EXPECT_TRUE(output.is_null());
+ }
+
+ {
+ // A struct with no fields.
+ EmptyStructPtr empty_struct(EmptyStruct::New());
+ auto data = EmptyStruct::Serialize(&empty_struct);
+ EXPECT_FALSE(data.empty());
+
+ EmptyStructPtr output;
+ ASSERT_TRUE(EmptyStruct::Deserialize(data, &output));
+ EXPECT_FALSE(output.is_null());
+ }
+
+ {
+ // A simple struct.
+ RectPtr rect = MakeRect();
+ RectPtr cloned_rect = rect.Clone();
+ auto data = Rect::Serialize(&rect);
+
+ RectPtr output;
+ ASSERT_TRUE(Rect::Deserialize(data, &output));
+ EXPECT_TRUE(output.Equals(cloned_rect));
+ }
+
+ {
+ // A struct containing other objects.
+ std::vector<RectPtr> rects;
+ for (size_t i = 0; i < 3; ++i)
+ rects.push_back(MakeRect(static_cast<int32_t>(i) + 1));
+ NamedRegionPtr region(
+ NamedRegion::New(std::string("region"), std::move(rects)));
+
+ NamedRegionPtr cloned_region = region.Clone();
+ auto data = NamedRegion::Serialize(&region);
+
+ // Make sure that the serialized result gets pointers encoded properly.
+ auto cloned_data = data;
+ NamedRegionPtr output;
+ ASSERT_TRUE(NamedRegion::Deserialize(cloned_data, &output));
+ EXPECT_TRUE(output.Equals(cloned_region));
+ }
+
+ {
+ // Deserialization failure.
+ RectPtr rect = MakeRect();
+ auto data = Rect::Serialize(&rect);
+
+ NamedRegionPtr output;
+ EXPECT_FALSE(NamedRegion::Deserialize(data, &output));
+ }
+
+ {
+ // A struct from another component.
+ auto pair = test_export2::StringPair::New("hello", "world");
+ auto data = test_export2::StringPair::Serialize(&pair);
+
+ test_export2::StringPairPtr output;
+ ASSERT_TRUE(test_export2::StringPair::Deserialize(data, &output));
+ EXPECT_TRUE(output.Equals(pair));
+ }
+}
+
+TEST_F(StructTest, VersionedStructConstructor) {
+ auto reordered = ReorderedStruct::New(123, 456, 789);
+ EXPECT_EQ(123, reordered->a);
+ EXPECT_EQ(456, reordered->b);
+ EXPECT_EQ(789, reordered->c);
+
+ reordered = ReorderedStruct::New(123, 456);
+ EXPECT_EQ(123, reordered->a);
+ EXPECT_EQ(6, reordered->b);
+ EXPECT_EQ(456, reordered->c);
+
+ reordered = ReorderedStruct::New(123);
+ EXPECT_EQ(3, reordered->a);
+ EXPECT_EQ(6, reordered->b);
+ EXPECT_EQ(123, reordered->c);
+
+ reordered = ReorderedStruct::New();
+ EXPECT_EQ(3, reordered->a);
+ EXPECT_EQ(6, reordered->b);
+ EXPECT_EQ(1, reordered->c);
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits.typemap b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap
new file mode 100644
index 0000000000..752ce44b58
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap
@@ -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.
+
+mojom = "//mojo/public/interfaces/bindings/tests/struct_with_traits.mojom"
+public_headers =
+ [ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" ]
+traits_headers =
+ [ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" ]
+sources = [
+ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc",
+]
+deps = [
+ "//mojo/public/cpp/bindings/tests:struct_with_traits_impl",
+ "//mojo/public/cpp/system:system",
+]
+
+type_mappings = [
+ "mojo.test.EnumWithTraits=mojo::test::EnumWithTraitsImpl",
+ "mojo.test.StructWithTraits=mojo::test::StructWithTraitsImpl",
+ "mojo.test.NestedStructWithTraits=mojo::test::NestedStructWithTraitsImpl",
+ "mojo.test.TrivialStructWithTraits=mojo::test::TrivialStructWithTraitsImpl[copyable_pass_by_value]",
+ "mojo.test.MoveOnlyStructWithTraits=mojo::test::MoveOnlyStructWithTraitsImpl[move_only]",
+ "mojo.test.StructWithTraitsForUniquePtr=std::unique_ptr<int>[move_only,nullable_is_same_type]",
+ "mojo.test.UnionWithTraits=std::unique_ptr<mojo::test::UnionWithTraitsBase>[move_only,nullable_is_same_type]",
+]
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc
new file mode 100644
index 0000000000..cbdd4bfde7
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h"
+
+namespace mojo {
+namespace test {
+
+NestedStructWithTraitsImpl::NestedStructWithTraitsImpl() {}
+NestedStructWithTraitsImpl::NestedStructWithTraitsImpl(int32_t in_value)
+ : value(in_value) {}
+
+StructWithTraitsImpl::StructWithTraitsImpl() {}
+
+StructWithTraitsImpl::~StructWithTraitsImpl() {}
+
+StructWithTraitsImpl::StructWithTraitsImpl(const StructWithTraitsImpl& other) =
+ default;
+
+MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl() {}
+
+MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl(
+ MoveOnlyStructWithTraitsImpl&& other) = default;
+
+MoveOnlyStructWithTraitsImpl::~MoveOnlyStructWithTraitsImpl() {}
+
+MoveOnlyStructWithTraitsImpl& MoveOnlyStructWithTraitsImpl::operator=(
+ MoveOnlyStructWithTraitsImpl&& other) = default;
+
+UnionWithTraitsInt32::~UnionWithTraitsInt32() {}
+
+UnionWithTraitsStruct::~UnionWithTraitsStruct() {}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h
new file mode 100644
index 0000000000..7b007cc083
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/system/handle.h"
+
+namespace mojo {
+namespace test {
+
+struct NestedStructWithTraitsImpl {
+ public:
+ NestedStructWithTraitsImpl();
+ explicit NestedStructWithTraitsImpl(int32_t in_value);
+
+ bool operator==(const NestedStructWithTraitsImpl& other) const {
+ return value == other.value;
+ }
+
+ int32_t value = 0;
+};
+
+enum class EnumWithTraitsImpl { CUSTOM_VALUE_0 = 10, CUSTOM_VALUE_1 = 11 };
+
+// A type which knows how to look like a mojo::test::StructWithTraits mojom type
+// by way of mojo::StructTraits.
+class StructWithTraitsImpl {
+ public:
+ StructWithTraitsImpl();
+ ~StructWithTraitsImpl();
+
+ StructWithTraitsImpl(const StructWithTraitsImpl& other);
+
+ void set_enum(EnumWithTraitsImpl value) { enum_ = value; }
+ EnumWithTraitsImpl get_enum() const { return enum_; }
+
+ void set_bool(bool value) { bool_ = value; }
+ bool get_bool() const { return bool_; }
+
+ void set_uint32(uint32_t value) { uint32_ = value; }
+ uint32_t get_uint32() const { return uint32_; }
+
+ void set_uint64(uint64_t value) { uint64_ = value; }
+ uint64_t get_uint64() const { return uint64_; }
+
+ void set_string(std::string value) { string_ = value; }
+ base::StringPiece get_string_as_string_piece() const { return string_; }
+ const std::string& get_string() const { return string_; }
+
+ const std::vector<std::string>& get_string_array() const {
+ return string_array_;
+ }
+ std::vector<std::string>& get_mutable_string_array() { return string_array_; }
+
+ const std::set<std::string>& get_string_set() const {
+ return string_set_;
+ }
+ std::set<std::string>& get_mutable_string_set() { return string_set_; }
+
+ const NestedStructWithTraitsImpl& get_struct() const { return struct_; }
+ NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; }
+
+ const std::vector<NestedStructWithTraitsImpl>& get_struct_array() const {
+ return struct_array_;
+ }
+ std::vector<NestedStructWithTraitsImpl>& get_mutable_struct_array() {
+ return struct_array_;
+ }
+
+ const std::map<std::string, NestedStructWithTraitsImpl>& get_struct_map()
+ const {
+ return struct_map_;
+ }
+ std::map<std::string, NestedStructWithTraitsImpl>& get_mutable_struct_map() {
+ return struct_map_;
+ }
+
+ private:
+ EnumWithTraitsImpl enum_ = EnumWithTraitsImpl::CUSTOM_VALUE_0;
+ bool bool_ = false;
+ uint32_t uint32_ = 0;
+ uint64_t uint64_ = 0;
+ std::string string_;
+ std::vector<std::string> string_array_;
+ std::set<std::string> string_set_;
+ NestedStructWithTraitsImpl struct_;
+ std::vector<NestedStructWithTraitsImpl> struct_array_;
+ std::map<std::string, NestedStructWithTraitsImpl> struct_map_;
+};
+
+// A type which knows how to look like a mojo::test::TrivialStructWithTraits
+// mojom type by way of mojo::StructTraits.
+struct TrivialStructWithTraitsImpl {
+ int32_t value;
+};
+
+// A type which knows how to look like a mojo::test::MoveOnlyStructWithTraits
+// mojom type by way of mojo::StructTraits.
+class MoveOnlyStructWithTraitsImpl {
+ public:
+ MoveOnlyStructWithTraitsImpl();
+ MoveOnlyStructWithTraitsImpl(MoveOnlyStructWithTraitsImpl&& other);
+ ~MoveOnlyStructWithTraitsImpl();
+
+ ScopedHandle& get_mutable_handle() { return handle_; }
+
+ MoveOnlyStructWithTraitsImpl& operator=(MoveOnlyStructWithTraitsImpl&& other);
+
+ private:
+ ScopedHandle handle_;
+ DISALLOW_COPY_AND_ASSIGN(MoveOnlyStructWithTraitsImpl);
+};
+
+class UnionWithTraitsBase {
+ public:
+ enum class Type { INT32, STRUCT };
+
+ virtual ~UnionWithTraitsBase() {}
+
+ Type type() const { return type_; }
+
+ protected:
+ Type type_ = Type::INT32;
+};
+
+class UnionWithTraitsInt32 : public UnionWithTraitsBase {
+ public:
+ UnionWithTraitsInt32() {}
+ explicit UnionWithTraitsInt32(int32_t value) : value_(value) {}
+
+ ~UnionWithTraitsInt32() override;
+
+ int32_t value() const { return value_; }
+ void set_value(int32_t value) { value_ = value; }
+
+ private:
+ int32_t value_ = 0;
+};
+
+class UnionWithTraitsStruct : public UnionWithTraitsBase {
+ public:
+ UnionWithTraitsStruct() { type_ = Type::STRUCT; }
+ explicit UnionWithTraitsStruct(int32_t value) : struct_(value) {
+ type_ = Type::STRUCT;
+ }
+ ~UnionWithTraitsStruct() override;
+
+ NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; }
+ const NestedStructWithTraitsImpl& get_struct() const { return struct_; }
+
+ private:
+ NestedStructWithTraitsImpl struct_;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc
new file mode 100644
index 0000000000..6b770b1a49
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc
@@ -0,0 +1,137 @@
+// 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 "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h"
+
+namespace mojo {
+namespace {
+
+struct Context {
+ int32_t value;
+};
+
+} // namespace
+
+// static
+void* StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ SetUpContext(const test::NestedStructWithTraitsImpl& input) {
+ Context* context = new Context;
+ context->value = input.value;
+ return context;
+}
+
+// static
+void StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ TearDownContext(const test::NestedStructWithTraitsImpl& input,
+ void* context) {
+ Context* context_obj = static_cast<Context*>(context);
+ CHECK_EQ(context_obj->value, input.value);
+ delete context_obj;
+}
+
+// static
+int32_t StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ value(const test::NestedStructWithTraitsImpl& input, void* context) {
+ Context* context_obj = static_cast<Context*>(context);
+ CHECK_EQ(context_obj->value, input.value);
+ return input.value;
+}
+
+// static
+bool StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ Read(test::NestedStructWithTraits::DataView data,
+ test::NestedStructWithTraitsImpl* output) {
+ output->value = data.value();
+ return true;
+}
+
+test::EnumWithTraits
+EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl>::ToMojom(
+ test::EnumWithTraitsImpl input) {
+ switch (input) {
+ case test::EnumWithTraitsImpl::CUSTOM_VALUE_0:
+ return test::EnumWithTraits::VALUE_0;
+ case test::EnumWithTraitsImpl::CUSTOM_VALUE_1:
+ return test::EnumWithTraits::VALUE_1;
+ };
+
+ NOTREACHED();
+ return test::EnumWithTraits::VALUE_0;
+}
+
+bool EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl>::FromMojom(
+ test::EnumWithTraits input,
+ test::EnumWithTraitsImpl* output) {
+ switch (input) {
+ case test::EnumWithTraits::VALUE_0:
+ *output = test::EnumWithTraitsImpl::CUSTOM_VALUE_0;
+ return true;
+ case test::EnumWithTraits::VALUE_1:
+ *output = test::EnumWithTraitsImpl::CUSTOM_VALUE_1;
+ return true;
+ };
+
+ return false;
+}
+
+// static
+bool StructTraits<test::StructWithTraitsDataView, test::StructWithTraitsImpl>::
+ Read(test::StructWithTraits::DataView data,
+ test::StructWithTraitsImpl* out) {
+ test::EnumWithTraitsImpl f_enum;
+ if (!data.ReadFEnum(&f_enum))
+ return false;
+ out->set_enum(f_enum);
+
+ out->set_bool(data.f_bool());
+ out->set_uint32(data.f_uint32());
+ out->set_uint64(data.f_uint64());
+
+ base::StringPiece f_string;
+ std::string f_string2;
+ if (!data.ReadFString(&f_string) || !data.ReadFString2(&f_string2) ||
+ f_string != f_string2) {
+ return false;
+ }
+ out->set_string(f_string2);
+
+ if (!data.ReadFStringArray(&out->get_mutable_string_array()))
+ return false;
+
+ // We can't deserialize as a std::set, so we have to manually copy from the
+ // data view.
+ ArrayDataView<StringDataView> string_set_data_view;
+ data.GetFStringSetDataView(&string_set_data_view);
+ for (size_t i = 0; i < string_set_data_view.size(); ++i) {
+ std::string value;
+ string_set_data_view.Read(i, &value);
+ out->get_mutable_string_set().insert(value);
+ }
+
+ if (!data.ReadFStruct(&out->get_mutable_struct()))
+ return false;
+
+ if (!data.ReadFStructArray(&out->get_mutable_struct_array()))
+ return false;
+
+ if (!data.ReadFStructMap(&out->get_mutable_struct_map()))
+ return false;
+
+ return true;
+}
+
+// static
+bool StructTraits<test::MoveOnlyStructWithTraitsDataView,
+ test::MoveOnlyStructWithTraitsImpl>::
+ Read(test::MoveOnlyStructWithTraits::DataView data,
+ test::MoveOnlyStructWithTraitsImpl* out) {
+ out->get_mutable_handle() = data.TakeFHandle();
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h
new file mode 100644
index 0000000000..adcad8aa9e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h
@@ -0,0 +1,196 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h"
+#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl> {
+ static void* SetUpContext(const test::NestedStructWithTraitsImpl& input);
+ static void TearDownContext(const test::NestedStructWithTraitsImpl& input,
+ void* context);
+
+ static int32_t value(const test::NestedStructWithTraitsImpl& input,
+ void* context);
+
+ static bool Read(test::NestedStructWithTraitsDataView data,
+ test::NestedStructWithTraitsImpl* output);
+};
+
+template <>
+struct EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl> {
+ static test::EnumWithTraits ToMojom(test::EnumWithTraitsImpl input);
+ static bool FromMojom(test::EnumWithTraits input,
+ test::EnumWithTraitsImpl* output);
+};
+
+template <>
+struct StructTraits<test::StructWithTraitsDataView,
+ test::StructWithTraitsImpl> {
+ // Deserialization to test::StructTraitsImpl.
+ static bool Read(test::StructWithTraitsDataView data,
+ test::StructWithTraitsImpl* out);
+
+ // Fields in test::StructWithTraits.
+ // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.
+ static test::EnumWithTraitsImpl f_enum(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_enum();
+ }
+
+ static bool f_bool(const test::StructWithTraitsImpl& value) {
+ return value.get_bool();
+ }
+
+ static uint32_t f_uint32(const test::StructWithTraitsImpl& value) {
+ return value.get_uint32();
+ }
+
+ static uint64_t f_uint64(const test::StructWithTraitsImpl& value) {
+ return value.get_uint64();
+ }
+
+ static base::StringPiece f_string(const test::StructWithTraitsImpl& value) {
+ return value.get_string_as_string_piece();
+ }
+
+ static const std::string& f_string2(const test::StructWithTraitsImpl& value) {
+ return value.get_string();
+ }
+
+ static const std::vector<std::string>& f_string_array(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_string_array();
+ }
+
+ static const std::set<std::string>& f_string_set(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_string_set();
+ }
+
+ static const test::NestedStructWithTraitsImpl& f_struct(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_struct();
+ }
+
+ static const std::vector<test::NestedStructWithTraitsImpl>& f_struct_array(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_struct_array();
+ }
+
+ static const std::map<std::string, test::NestedStructWithTraitsImpl>&
+ f_struct_map(const test::StructWithTraitsImpl& value) {
+ return value.get_struct_map();
+ }
+};
+
+template <>
+struct StructTraits<test::TrivialStructWithTraitsDataView,
+ test::TrivialStructWithTraitsImpl> {
+ // Deserialization to test::TrivialStructTraitsImpl.
+ static bool Read(test::TrivialStructWithTraitsDataView data,
+ test::TrivialStructWithTraitsImpl* out) {
+ out->value = data.value();
+ return true;
+ }
+
+ // Fields in test::TrivialStructWithTraits.
+ // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.
+ static int32_t value(test::TrivialStructWithTraitsImpl& input) {
+ return input.value;
+ }
+};
+
+template <>
+struct StructTraits<test::MoveOnlyStructWithTraitsDataView,
+ test::MoveOnlyStructWithTraitsImpl> {
+ // Deserialization to test::MoveOnlyStructTraitsImpl.
+ static bool Read(test::MoveOnlyStructWithTraitsDataView data,
+ test::MoveOnlyStructWithTraitsImpl* out);
+
+ // Fields in test::MoveOnlyStructWithTraits.
+ // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.
+ static ScopedHandle f_handle(test::MoveOnlyStructWithTraitsImpl& value) {
+ return std::move(value.get_mutable_handle());
+ }
+};
+
+template <>
+struct StructTraits<test::StructWithTraitsForUniquePtrDataView,
+ std::unique_ptr<int>> {
+ static bool IsNull(const std::unique_ptr<int>& data) { return !data; }
+ static void SetToNull(std::unique_ptr<int>* data) { data->reset(); }
+
+ static int f_int32(const std::unique_ptr<int>& data) { return *data; }
+
+ static bool Read(test::StructWithTraitsForUniquePtrDataView data,
+ std::unique_ptr<int>* out) {
+ out->reset(new int(data.f_int32()));
+ return true;
+ }
+};
+
+template <>
+struct UnionTraits<test::UnionWithTraitsDataView,
+ std::unique_ptr<test::UnionWithTraitsBase>> {
+ static bool IsNull(const std::unique_ptr<test::UnionWithTraitsBase>& data) {
+ return !data;
+ }
+ static void SetToNull(std::unique_ptr<test::UnionWithTraitsBase>* data) {
+ data->reset();
+ }
+
+ static test::UnionWithTraitsDataView::Tag GetTag(
+ const std::unique_ptr<test::UnionWithTraitsBase>& data) {
+ if (data->type() == test::UnionWithTraitsBase::Type::INT32)
+ return test::UnionWithTraitsDataView::Tag::F_INT32;
+
+ return test::UnionWithTraitsDataView::Tag::F_STRUCT;
+ }
+
+ static int32_t f_int32(
+ const std::unique_ptr<test::UnionWithTraitsBase>& data) {
+ return static_cast<test::UnionWithTraitsInt32*>(data.get())->value();
+ }
+
+ static const test::NestedStructWithTraitsImpl& f_struct(
+ const std::unique_ptr<test::UnionWithTraitsBase>& data) {
+ return static_cast<test::UnionWithTraitsStruct*>(data.get())->get_struct();
+ }
+
+ static bool Read(test::UnionWithTraitsDataView data,
+ std::unique_ptr<test::UnionWithTraitsBase>* out) {
+ switch (data.tag()) {
+ case test::UnionWithTraitsDataView::Tag::F_INT32: {
+ out->reset(new test::UnionWithTraitsInt32(data.f_int32()));
+ return true;
+ }
+ case test::UnionWithTraitsDataView::Tag::F_STRUCT: {
+ auto* struct_object = new test::UnionWithTraitsStruct();
+ out->reset(struct_object);
+ return data.ReadFStruct(&struct_object->get_mutable_struct());
+ }
+ }
+
+ NOTREACHED();
+ return false;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
new file mode 100644
index 0000000000..084e080ad3
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
@@ -0,0 +1,831 @@
+// 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 <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+template <typename... Args>
+struct LambdaBinder {
+ using CallbackType = base::Callback<void(Args...)>;
+
+ template <typename Func>
+ static void RunLambda(Func func, Args... args) {
+ func(std::move(args)...);
+ }
+
+ template <typename Func>
+ static CallbackType BindLambda(Func func) {
+ return base::Bind(&LambdaBinder::RunLambda<Func>, func);
+ }
+};
+
+class TestSyncCommonImpl {
+ public:
+ TestSyncCommonImpl() {}
+
+ using PingHandler = base::Callback<void(const base::Callback<void()>&)>;
+ using PingBinder = LambdaBinder<const base::Callback<void()>&>;
+ template <typename Func>
+ void set_ping_handler(Func handler) {
+ ping_handler_ = PingBinder::BindLambda(handler);
+ }
+
+ using EchoHandler =
+ base::Callback<void(int32_t, const base::Callback<void(int32_t)>&)>;
+ using EchoBinder =
+ LambdaBinder<int32_t, const base::Callback<void(int32_t)>&>;
+ template <typename Func>
+ void set_echo_handler(Func handler) {
+ echo_handler_ = EchoBinder::BindLambda(handler);
+ }
+
+ using AsyncEchoHandler =
+ base::Callback<void(int32_t, const base::Callback<void(int32_t)>&)>;
+ using AsyncEchoBinder =
+ LambdaBinder<int32_t, const base::Callback<void(int32_t)>&>;
+ template <typename Func>
+ void set_async_echo_handler(Func handler) {
+ async_echo_handler_ = AsyncEchoBinder::BindLambda(handler);
+ }
+
+ using SendInterfaceHandler = base::Callback<void(TestSyncAssociatedPtrInfo)>;
+ using SendInterfaceBinder = LambdaBinder<TestSyncAssociatedPtrInfo>;
+ template <typename Func>
+ void set_send_interface_handler(Func handler) {
+ send_interface_handler_ = SendInterfaceBinder::BindLambda(handler);
+ }
+
+ using SendRequestHandler = base::Callback<void(TestSyncAssociatedRequest)>;
+ using SendRequestBinder = LambdaBinder<TestSyncAssociatedRequest>;
+ template <typename Func>
+ void set_send_request_handler(Func handler) {
+ send_request_handler_ = SendRequestBinder::BindLambda(handler);
+ }
+
+ void PingImpl(const base::Callback<void()>& callback) {
+ if (ping_handler_.is_null()) {
+ callback.Run();
+ return;
+ }
+ ping_handler_.Run(callback);
+ }
+ void EchoImpl(int32_t value, const base::Callback<void(int32_t)>& callback) {
+ if (echo_handler_.is_null()) {
+ callback.Run(value);
+ return;
+ }
+ echo_handler_.Run(value, callback);
+ }
+ void AsyncEchoImpl(int32_t value,
+ const base::Callback<void(int32_t)>& callback) {
+ if (async_echo_handler_.is_null()) {
+ callback.Run(value);
+ return;
+ }
+ async_echo_handler_.Run(value, callback);
+ }
+ void SendInterfaceImpl(TestSyncAssociatedPtrInfo ptr) {
+ send_interface_handler_.Run(std::move(ptr));
+ }
+ void SendRequestImpl(TestSyncAssociatedRequest request) {
+ send_request_handler_.Run(std::move(request));
+ }
+
+ private:
+ PingHandler ping_handler_;
+ EchoHandler echo_handler_;
+ AsyncEchoHandler async_echo_handler_;
+ SendInterfaceHandler send_interface_handler_;
+ SendRequestHandler send_request_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSyncCommonImpl);
+};
+
+class TestSyncImpl : public TestSync, public TestSyncCommonImpl {
+ public:
+ explicit TestSyncImpl(TestSyncRequest request)
+ : binding_(this, std::move(request)) {}
+
+ // TestSync implementation:
+ void Ping(const PingCallback& callback) override { PingImpl(callback); }
+ void Echo(int32_t value, const EchoCallback& callback) override {
+ EchoImpl(value, callback);
+ }
+ void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override {
+ AsyncEchoImpl(value, callback);
+ }
+
+ Binding<TestSync>* binding() { return &binding_; }
+
+ private:
+ Binding<TestSync> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSyncImpl);
+};
+
+class TestSyncMasterImpl : public TestSyncMaster, public TestSyncCommonImpl {
+ public:
+ explicit TestSyncMasterImpl(TestSyncMasterRequest request)
+ : binding_(this, std::move(request)) {}
+
+ // TestSyncMaster implementation:
+ void Ping(const PingCallback& callback) override { PingImpl(callback); }
+ void Echo(int32_t value, const EchoCallback& callback) override {
+ EchoImpl(value, callback);
+ }
+ void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override {
+ AsyncEchoImpl(value, callback);
+ }
+ void SendInterface(TestSyncAssociatedPtrInfo ptr) override {
+ SendInterfaceImpl(std::move(ptr));
+ }
+ void SendRequest(TestSyncAssociatedRequest request) override {
+ SendRequestImpl(std::move(request));
+ }
+
+ Binding<TestSyncMaster>* binding() { return &binding_; }
+
+ private:
+ Binding<TestSyncMaster> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSyncMasterImpl);
+};
+
+class TestSyncAssociatedImpl : public TestSync, public TestSyncCommonImpl {
+ public:
+ explicit TestSyncAssociatedImpl(TestSyncAssociatedRequest request)
+ : binding_(this, std::move(request)) {}
+
+ // TestSync implementation:
+ void Ping(const PingCallback& callback) override { PingImpl(callback); }
+ void Echo(int32_t value, const EchoCallback& callback) override {
+ EchoImpl(value, callback);
+ }
+ void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override {
+ AsyncEchoImpl(value, callback);
+ }
+
+ AssociatedBinding<TestSync>* binding() { return &binding_; }
+
+ private:
+ AssociatedBinding<TestSync> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSyncAssociatedImpl);
+};
+
+template <typename Interface>
+struct ImplTraits;
+
+template <>
+struct ImplTraits<TestSync> {
+ using Type = TestSyncImpl;
+};
+
+template <>
+struct ImplTraits<TestSyncMaster> {
+ using Type = TestSyncMasterImpl;
+};
+
+template <typename Interface>
+using ImplTypeFor = typename ImplTraits<Interface>::Type;
+
+// A wrapper for either an InterfacePtr or scoped_refptr<ThreadSafeInterfacePtr>
+// that exposes the InterfacePtr interface.
+template <typename Interface>
+class PtrWrapper {
+ public:
+ explicit PtrWrapper(InterfacePtr<Interface> ptr) : ptr_(std::move(ptr)) {}
+
+ explicit PtrWrapper(
+ scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr)
+ : thread_safe_ptr_(thread_safe_ptr) {}
+
+ PtrWrapper(PtrWrapper&& other) = default;
+
+ Interface* operator->() {
+ return thread_safe_ptr_ ? thread_safe_ptr_->get() : ptr_.get();
+ }
+
+ void set_connection_error_handler(const base::Closure& error_handler) {
+ DCHECK(!thread_safe_ptr_);
+ ptr_.set_connection_error_handler(error_handler);
+ }
+
+ void reset() {
+ ptr_ = nullptr;
+ thread_safe_ptr_ = nullptr;
+ }
+
+ private:
+ InterfacePtr<Interface> ptr_;
+ scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PtrWrapper);
+};
+
+// The type parameter for SyncMethodCommonTests for varying the Interface and
+// whether to use InterfacePtr or ThreadSafeInterfacePtr.
+template <typename InterfaceT, bool use_thread_safe_ptr>
+struct TestParams {
+ using Interface = InterfaceT;
+ static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr;
+
+ static PtrWrapper<InterfaceT> Wrap(InterfacePtr<Interface> ptr) {
+ if (kIsThreadSafeInterfacePtrTest) {
+ return PtrWrapper<Interface>(
+ ThreadSafeInterfacePtr<Interface>::Create(std::move(ptr)));
+ } else {
+ return PtrWrapper<Interface>(std::move(ptr));
+ }
+ }
+};
+
+template <typename Interface>
+class TestSyncServiceThread {
+ public:
+ TestSyncServiceThread()
+ : thread_("TestSyncServiceThread"), ping_called_(false) {
+ thread_.Start();
+ }
+
+ void SetUp(InterfaceRequest<Interface> request) {
+ CHECK(thread_.task_runner()->BelongsToCurrentThread());
+ impl_.reset(new ImplTypeFor<Interface>(std::move(request)));
+ impl_->set_ping_handler(
+ [this](const typename Interface::PingCallback& callback) {
+ {
+ base::AutoLock locker(lock_);
+ ping_called_ = true;
+ }
+ callback.Run();
+ });
+ }
+
+ void TearDown() {
+ CHECK(thread_.task_runner()->BelongsToCurrentThread());
+ impl_.reset();
+ }
+
+ base::Thread* thread() { return &thread_; }
+ bool ping_called() const {
+ base::AutoLock locker(lock_);
+ return ping_called_;
+ }
+
+ private:
+ base::Thread thread_;
+
+ std::unique_ptr<ImplTypeFor<Interface>> impl_;
+
+ mutable base::Lock lock_;
+ bool ping_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSyncServiceThread);
+};
+
+class SyncMethodTest : public testing::Test {
+ public:
+ SyncMethodTest() {}
+ ~SyncMethodTest() override { base::RunLoop().RunUntilIdle(); }
+
+ protected:
+ base::MessageLoop loop_;
+};
+
+template <typename T>
+class SyncMethodCommonTest : public SyncMethodTest {
+ public:
+ SyncMethodCommonTest() {}
+ ~SyncMethodCommonTest() override {}
+};
+
+class SyncMethodAssociatedTest : public SyncMethodTest {
+ public:
+ SyncMethodAssociatedTest() {}
+ ~SyncMethodAssociatedTest() override {}
+
+ protected:
+ void SetUp() override {
+ master_impl_.reset(new TestSyncMasterImpl(MakeRequest(&master_ptr_)));
+
+ asso_request_ = MakeRequest(&asso_ptr_info_);
+ opposite_asso_request_ = MakeRequest(&opposite_asso_ptr_info_);
+
+ master_impl_->set_send_interface_handler(
+ [this](TestSyncAssociatedPtrInfo ptr) {
+ opposite_asso_ptr_info_ = std::move(ptr);
+ });
+ base::RunLoop run_loop;
+ master_impl_->set_send_request_handler(
+ [this, &run_loop](TestSyncAssociatedRequest request) {
+ asso_request_ = std::move(request);
+ run_loop.Quit();
+ });
+
+ master_ptr_->SendInterface(std::move(opposite_asso_ptr_info_));
+ master_ptr_->SendRequest(std::move(asso_request_));
+ run_loop.Run();
+ }
+
+ void TearDown() override {
+ asso_ptr_info_ = TestSyncAssociatedPtrInfo();
+ asso_request_ = TestSyncAssociatedRequest();
+ opposite_asso_ptr_info_ = TestSyncAssociatedPtrInfo();
+ opposite_asso_request_ = TestSyncAssociatedRequest();
+
+ master_ptr_ = nullptr;
+ master_impl_.reset();
+ }
+
+ InterfacePtr<TestSyncMaster> master_ptr_;
+ std::unique_ptr<TestSyncMasterImpl> master_impl_;
+
+ // An associated interface whose binding lives at the |master_impl_| side.
+ TestSyncAssociatedPtrInfo asso_ptr_info_;
+ TestSyncAssociatedRequest asso_request_;
+
+ // An associated interface whose binding lives at the |master_ptr_| side.
+ TestSyncAssociatedPtrInfo opposite_asso_ptr_info_;
+ TestSyncAssociatedRequest opposite_asso_request_;
+};
+
+void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
+ *flag = true;
+ closure.Run();
+}
+
+void ExpectValueAndRunClosure(int32_t expected_value,
+ const base::Closure& closure,
+ int32_t value) {
+ EXPECT_EQ(expected_value, value);
+ closure.Run();
+}
+
+template <typename Func>
+void CallAsyncEchoCallback(Func func, int32_t value) {
+ func(value);
+}
+
+template <typename Func>
+TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) {
+ return base::Bind(&CallAsyncEchoCallback<Func>, func);
+}
+
+// TestSync (without associated interfaces) and TestSyncMaster (with associated
+// interfaces) exercise MultiplexRouter with different configurations.
+// Each test is run once with an InterfacePtr and once with a
+// ThreadSafeInterfacePtr to ensure that they behave the same with respect to
+// sync calls.
+using InterfaceTypes = testing::Types<TestParams<TestSync, true>,
+ TestParams<TestSync, false>,
+ TestParams<TestSyncMaster, true>,
+ TestParams<TestSyncMaster, false>>;
+TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes);
+
+TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) {
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ base::RunLoop run_loop;
+ ptr->Echo(123, base::Bind(&ExpectValueAndRunClosure, 123,
+ run_loop.QuitClosure()));
+ run_loop.Run();
+}
+
+TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) {
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ InterfaceRequest<Interface> request = MakeRequest(&interface_ptr);
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ TestSyncServiceThread<Interface> service_thread;
+ service_thread.thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestSyncServiceThread<Interface>::SetUp,
+ base::Unretained(&service_thread), base::Passed(&request)));
+ ASSERT_TRUE(ptr->Ping());
+ ASSERT_TRUE(service_thread.ping_called());
+
+ int32_t output_value = -1;
+ ASSERT_TRUE(ptr->Echo(42, &output_value));
+ ASSERT_EQ(42, output_value);
+
+ base::RunLoop run_loop;
+ service_thread.thread()->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&TestSyncServiceThread<Interface>::TearDown,
+ base::Unretained(&service_thread)),
+ run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodBinding) {
+ // Test that an interface pointer waiting for a sync call response can be
+ // reentered by a binding serving sync methods on the same thread.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ // The binding lives on the same thread as the interface pointer.
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+ int32_t output_value = -1;
+ ASSERT_TRUE(ptr->Echo(42, &output_value));
+ EXPECT_EQ(42, output_value);
+}
+
+TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) {
+ // Test that it won't result in crash or hang if an interface pointer is
+ // destroyed while it is waiting for a sync call response.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+ impl.set_ping_handler([&ptr](const TestSync::PingCallback& callback) {
+ ptr.reset();
+ callback.Run();
+ });
+ ASSERT_FALSE(ptr->Ping());
+}
+
+TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) {
+ // Test that it won't result in crash or hang if a binding is
+ // closed (and therefore the message pipe handle is closed) while the
+ // corresponding interface pointer is waiting for a sync call response.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+ impl.set_ping_handler([&impl](const TestSync::PingCallback& callback) {
+ impl.binding()->Close();
+ callback.Run();
+ });
+ ASSERT_FALSE(ptr->Ping());
+}
+
+TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) {
+ // Test that we can call a sync method on an interface ptr, while there is
+ // already a sync call ongoing. The responses arrive in order.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ // The same variable is used to store the output of the two sync calls, in
+ // order to test that responses are handled in the correct order.
+ int32_t result_value = -1;
+
+ bool first_call = true;
+ impl.set_echo_handler([&first_call, &ptr, &result_value](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ if (first_call) {
+ first_call = false;
+ ASSERT_TRUE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+ }
+ callback.Run(value);
+ });
+
+ ASSERT_TRUE(ptr->Echo(123, &result_value));
+ EXPECT_EQ(123, result_value);
+}
+
+TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) {
+ // Test that we can call a sync method on an interface ptr, while there is
+ // already a sync call ongoing. The responses arrive out of order.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ // The same variable is used to store the output of the two sync calls, in
+ // order to test that responses are handled in the correct order.
+ int32_t result_value = -1;
+
+ bool first_call = true;
+ impl.set_echo_handler([&first_call, &ptr, &result_value](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ callback.Run(value);
+ if (first_call) {
+ first_call = false;
+ ASSERT_TRUE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+ }
+ });
+
+ ASSERT_TRUE(ptr->Echo(123, &result_value));
+ EXPECT_EQ(123, result_value);
+}
+
+TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, async responses are queued until the sync call completes.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ int32_t async_echo_request_value = -1;
+ TestSync::AsyncEchoCallback async_echo_request_callback;
+ base::RunLoop run_loop1;
+ impl.set_async_echo_handler(
+ [&async_echo_request_value, &async_echo_request_callback, &run_loop1](
+ int32_t value, const TestSync::AsyncEchoCallback& callback) {
+ async_echo_request_value = value;
+ async_echo_request_callback = callback;
+ run_loop1.Quit();
+ });
+
+ bool async_echo_response_dispatched = false;
+ base::RunLoop run_loop2;
+ ptr->AsyncEcho(
+ 123,
+ BindAsyncEchoCallback(
+ [&async_echo_response_dispatched, &run_loop2](int32_t result) {
+ async_echo_response_dispatched = true;
+ EXPECT_EQ(123, result);
+ run_loop2.Quit();
+ }));
+ // Run until the AsyncEcho request reaches the service side.
+ run_loop1.Run();
+
+ impl.set_echo_handler(
+ [&async_echo_request_value, &async_echo_request_callback](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ // Send back the async response first.
+ EXPECT_FALSE(async_echo_request_callback.is_null());
+ async_echo_request_callback.Run(async_echo_request_value);
+
+ callback.Run(value);
+ });
+
+ int32_t result_value = -1;
+ ASSERT_TRUE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+
+ // Although the AsyncEcho response arrives before the Echo response, it should
+ // be queued and not yet dispatched.
+ EXPECT_FALSE(async_echo_response_dispatched);
+
+ // Run until the AsyncEcho response is dispatched.
+ run_loop2.Run();
+
+ EXPECT_TRUE(async_echo_response_dispatched);
+}
+
+TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, async requests for a binding running on the same thread are queued
+ // until the sync call completes.
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ bool async_echo_request_dispatched = false;
+ impl.set_async_echo_handler([&async_echo_request_dispatched](
+ int32_t value, const TestSync::AsyncEchoCallback& callback) {
+ async_echo_request_dispatched = true;
+ callback.Run(value);
+ });
+
+ bool async_echo_response_dispatched = false;
+ base::RunLoop run_loop;
+ ptr->AsyncEcho(
+ 123,
+ BindAsyncEchoCallback(
+ [&async_echo_response_dispatched, &run_loop](int32_t result) {
+ async_echo_response_dispatched = true;
+ EXPECT_EQ(123, result);
+ run_loop.Quit();
+ }));
+
+ impl.set_echo_handler([&async_echo_request_dispatched](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ // Although the AsyncEcho request is sent before the Echo request, it
+ // shouldn't be dispatched yet at this point, because there is an ongoing
+ // sync call on the same thread.
+ EXPECT_FALSE(async_echo_request_dispatched);
+ callback.Run(value);
+ });
+
+ int32_t result_value = -1;
+ ASSERT_TRUE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+
+ // Although the AsyncEcho request is sent before the Echo request, it
+ // shouldn't be dispatched yet.
+ EXPECT_FALSE(async_echo_request_dispatched);
+
+ // Run until the AsyncEcho response is dispatched.
+ run_loop.Run();
+
+ EXPECT_TRUE(async_echo_response_dispatched);
+}
+
+TYPED_TEST(SyncMethodCommonTest,
+ QueuedMessagesProcessedBeforeErrorNotification) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, async responses are queued. If the message pipe is disconnected
+ // before the queued messages are processed, the connection error
+ // notification is delayed until all the queued messages are processed.
+
+ // ThreadSafeInterfacePtr doesn't guarantee that messages are delivered before
+ // error notifications, so skip it for this test.
+ if (TypeParam::kIsThreadSafeInterfacePtrTest)
+ return;
+
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> ptr;
+ ImplTypeFor<Interface> impl(MakeRequest(&ptr));
+
+ int32_t async_echo_request_value = -1;
+ TestSync::AsyncEchoCallback async_echo_request_callback;
+ base::RunLoop run_loop1;
+ impl.set_async_echo_handler(
+ [&async_echo_request_value, &async_echo_request_callback, &run_loop1](
+ int32_t value, const TestSync::AsyncEchoCallback& callback) {
+ async_echo_request_value = value;
+ async_echo_request_callback = callback;
+ run_loop1.Quit();
+ });
+
+ bool async_echo_response_dispatched = false;
+ bool connection_error_dispatched = false;
+ base::RunLoop run_loop2;
+ ptr->AsyncEcho(
+ 123,
+ BindAsyncEchoCallback(
+ [&async_echo_response_dispatched, &connection_error_dispatched, &ptr,
+ &run_loop2](int32_t result) {
+ async_echo_response_dispatched = true;
+ // At this point, error notification should not be dispatched
+ // yet.
+ EXPECT_FALSE(connection_error_dispatched);
+ EXPECT_FALSE(ptr.encountered_error());
+ EXPECT_EQ(123, result);
+ run_loop2.Quit();
+ }));
+ // Run until the AsyncEcho request reaches the service side.
+ run_loop1.Run();
+
+ impl.set_echo_handler(
+ [&impl, &async_echo_request_value, &async_echo_request_callback](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ // Send back the async response first.
+ EXPECT_FALSE(async_echo_request_callback.is_null());
+ async_echo_request_callback.Run(async_echo_request_value);
+
+ impl.binding()->Close();
+ });
+
+ base::RunLoop run_loop3;
+ ptr.set_connection_error_handler(
+ base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched,
+ run_loop3.QuitClosure()));
+
+ int32_t result_value = -1;
+ ASSERT_FALSE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(-1, result_value);
+ ASSERT_FALSE(connection_error_dispatched);
+ EXPECT_FALSE(ptr.encountered_error());
+
+ // Although the AsyncEcho response arrives before the Echo response, it should
+ // be queued and not yet dispatched.
+ EXPECT_FALSE(async_echo_response_dispatched);
+
+ // Run until the AsyncEcho response is dispatched.
+ run_loop2.Run();
+
+ EXPECT_TRUE(async_echo_response_dispatched);
+
+ // Run until the error notification is dispatched.
+ run_loop3.Run();
+
+ ASSERT_TRUE(connection_error_dispatched);
+ EXPECT_TRUE(ptr.encountered_error());
+}
+
+TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, an invalid incoming message will disconnect the message pipe, cause
+ // the sync call to return false, and run the connection error handler
+ // asynchronously.
+
+ using Interface = typename TypeParam::Interface;
+ MessagePipe pipe;
+
+ InterfacePtr<Interface> interface_ptr;
+ interface_ptr.Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ MessagePipeHandle raw_binding_handle = pipe.handle1.get();
+ ImplTypeFor<Interface> impl(MakeRequest<Interface>(std::move(pipe.handle1)));
+
+ impl.set_echo_handler([&raw_binding_handle](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ // Write a 1-byte message, which is considered invalid.
+ char invalid_message = 0;
+ MojoResult result =
+ WriteMessageRaw(raw_binding_handle, &invalid_message, 1u, nullptr, 0u,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ callback.Run(value);
+ });
+
+ bool connection_error_dispatched = false;
+ base::RunLoop run_loop;
+ // ThreadSafeInterfacePtr doesn't support setting connection error handlers.
+ if (!TypeParam::kIsThreadSafeInterfacePtrTest) {
+ ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure,
+ &connection_error_dispatched,
+ run_loop.QuitClosure()));
+ }
+
+ int32_t result_value = -1;
+ ASSERT_FALSE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(-1, result_value);
+ ASSERT_FALSE(connection_error_dispatched);
+
+ if (!TypeParam::kIsThreadSafeInterfacePtrTest) {
+ run_loop.Run();
+ ASSERT_TRUE(connection_error_dispatched);
+ }
+}
+
+TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) {
+ // Test that an interface pointer waiting for a sync call response can be
+ // reentered by an associated binding serving sync methods on the same thread.
+ // The associated binding belongs to the same MultiplexRouter as the waiting
+ // interface pointer.
+
+ TestSyncAssociatedImpl opposite_asso_impl(std::move(opposite_asso_request_));
+ TestSyncAssociatedPtr opposite_asso_ptr;
+ opposite_asso_ptr.Bind(std::move(opposite_asso_ptr_info_));
+
+ master_impl_->set_echo_handler([&opposite_asso_ptr](
+ int32_t value, const TestSyncMaster::EchoCallback& callback) {
+ int32_t result_value = -1;
+
+ ASSERT_TRUE(opposite_asso_ptr->Echo(123, &result_value));
+ EXPECT_EQ(123, result_value);
+ callback.Run(value);
+ });
+
+ int32_t result_value = -1;
+ ASSERT_TRUE(master_ptr_->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+}
+
+TEST_F(SyncMethodAssociatedTest,
+ ReenteredBySyncMethodAssoBindingOfDifferentRouter) {
+ // Test that an interface pointer waiting for a sync call response can be
+ // reentered by an associated binding serving sync methods on the same thread.
+ // The associated binding belongs to a different MultiplexRouter as the
+ // waiting interface pointer.
+
+ TestSyncAssociatedImpl asso_impl(std::move(asso_request_));
+ TestSyncAssociatedPtr asso_ptr;
+ asso_ptr.Bind(std::move(asso_ptr_info_));
+
+ master_impl_->set_echo_handler(
+ [&asso_ptr](int32_t value, const TestSyncMaster::EchoCallback& callback) {
+ int32_t result_value = -1;
+
+ ASSERT_TRUE(asso_ptr->Echo(123, &result_value));
+ EXPECT_EQ(123, result_value);
+ callback.Run(value);
+ });
+
+ int32_t result_value = -1;
+ ASSERT_TRUE(master_ptr_->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+}
+
+// TODO(yzshen): Add more tests related to associated interfaces.
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap b/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap
new file mode 100644
index 0000000000..1bdfbbc1a7
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap
@@ -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.
+
+mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom"
+public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_blink.h" ]
+sources = [
+ "//mojo/public/cpp/bindings/tests/pickled_types_blink.cc",
+]
+deps = [
+ "//ipc",
+]
+
+type_mappings = [
+ "mojo.test.PickledEnum=mojo::test::PickledEnumBlink",
+ "mojo.test.PickledStruct=mojo::test::PickledStructBlink[move_only]",
+]
diff --git a/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap
new file mode 100644
index 0000000000..50e8076a50
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap
@@ -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.
+
+mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom"
+public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_chromium.h" ]
+sources = [
+ "//mojo/public/cpp/bindings/tests/pickled_types_chromium.cc",
+]
+deps = [
+ "//ipc",
+]
+
+type_mappings = [
+ "mojo.test.PickledEnum=mojo::test::PickledEnumChromium",
+ "mojo.test.PickledStruct=mojo::test::PickledStructChromium[move_only]",
+]
diff --git a/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc
new file mode 100644
index 0000000000..b0124aa0e2
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc
@@ -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.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+struct RedmondRect {
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
+};
+
+struct RedmondNamedRegion {
+ std::string name;
+ std::vector<RedmondRect> rects;
+};
+
+bool AreEqualRectArrays(const std::vector<test::RectPtr>& rects1,
+ const std::vector<test::RectPtr>& rects2) {
+ if (rects1.size() != rects2.size())
+ return false;
+
+ for (size_t i = 0; i < rects1.size(); ++i) {
+ if (rects1[i]->x != rects2[i]->x || rects1[i]->y != rects2[i]->y ||
+ rects1[i]->width != rects2[i]->width ||
+ rects1[i]->height != rects2[i]->height) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+template <>
+struct TypeConverter<test::RectPtr, RedmondRect> {
+ static test::RectPtr Convert(const RedmondRect& input) {
+ return test::Rect::New(input.left, input.top, input.right - input.left,
+ input.bottom - input.top);
+ }
+};
+
+template <>
+struct TypeConverter<RedmondRect, test::RectPtr> {
+ static RedmondRect Convert(const test::RectPtr& input) {
+ RedmondRect rect;
+ rect.left = input->x;
+ rect.top = input->y;
+ rect.right = input->x + input->width;
+ rect.bottom = input->y + input->height;
+ return rect;
+ }
+};
+
+template <>
+struct TypeConverter<test::NamedRegionPtr, RedmondNamedRegion> {
+ static test::NamedRegionPtr Convert(const RedmondNamedRegion& input) {
+ return test::NamedRegion::New(
+ input.name, ConvertTo<std::vector<test::RectPtr>>(input.rects));
+ }
+};
+
+template <>
+struct TypeConverter<RedmondNamedRegion, test::NamedRegionPtr> {
+ static RedmondNamedRegion Convert(const test::NamedRegionPtr& input) {
+ RedmondNamedRegion region;
+ if (input->name)
+ region.name = input->name.value();
+ if (input->rects) {
+ region.rects.reserve(input->rects->size());
+ for (const auto& element : *input->rects)
+ region.rects.push_back(element.To<RedmondRect>());
+ }
+ return region;
+ }
+};
+
+namespace test {
+namespace {
+
+TEST(TypeConversionTest, CustomTypeConverter) {
+ RectPtr rect(Rect::New(10, 20, 50, 45));
+
+ RedmondRect rr = rect.To<RedmondRect>();
+ EXPECT_EQ(10, rr.left);
+ EXPECT_EQ(20, rr.top);
+ EXPECT_EQ(60, rr.right);
+ EXPECT_EQ(65, rr.bottom);
+
+ RectPtr rect2(Rect::From(rr));
+ EXPECT_EQ(rect->x, rect2->x);
+ EXPECT_EQ(rect->y, rect2->y);
+ EXPECT_EQ(rect->width, rect2->width);
+ EXPECT_EQ(rect->height, rect2->height);
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Array_Null) {
+ std::vector<RectPtr> rects;
+
+ auto redmond_rects = ConvertTo<std::vector<RedmondRect>>(rects);
+
+ EXPECT_TRUE(redmond_rects.empty());
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Array) {
+ const RedmondRect kBase = {10, 20, 30, 40};
+
+ std::vector<RectPtr> rects(10);
+ for (size_t i = 0; i < rects.size(); ++i) {
+ RedmondRect rr = kBase;
+ rr.left += static_cast<int32_t>(i);
+ rr.top += static_cast<int32_t>(i);
+ rects[i] = Rect::From(rr);
+ }
+
+ auto redmond_rects = ConvertTo<std::vector<RedmondRect>>(rects);
+
+ auto rects2 = ConvertTo<std::vector<RectPtr>>(redmond_rects);
+ EXPECT_TRUE(AreEqualRectArrays(rects, rects2));
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Nested) {
+ RedmondNamedRegion redmond_region;
+ redmond_region.name = "foopy";
+
+ const RedmondRect kBase = {10, 20, 30, 40};
+
+ for (size_t i = 0; i < 10; ++i) {
+ RedmondRect rect = kBase;
+ rect.left += static_cast<int32_t>(i);
+ rect.top += static_cast<int32_t>(i);
+ redmond_region.rects.push_back(rect);
+ }
+
+ // Round-trip through generated struct and TypeConverter.
+
+ NamedRegionPtr copy = NamedRegion::From(redmond_region);
+ RedmondNamedRegion redmond_region2 = copy.To<RedmondNamedRegion>();
+
+ EXPECT_EQ(redmond_region.name, redmond_region2.name);
+ EXPECT_EQ(redmond_region.rects.size(), redmond_region2.rects.size());
+ for (size_t i = 0; i < redmond_region.rects.size(); ++i) {
+ EXPECT_EQ(redmond_region.rects[i].left, redmond_region2.rects[i].left);
+ EXPECT_EQ(redmond_region.rects[i].top, redmond_region2.rects[i].top);
+ EXPECT_EQ(redmond_region.rects[i].right, redmond_region2.rects[i].right);
+ EXPECT_EQ(redmond_region.rects[i].bottom, redmond_region2.rects[i].bottom);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/union_unittest.cc b/mojo/public/cpp/bindings/tests/union_unittest.cc
new file mode 100644
index 0000000000..bdf27dfff3
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/union_unittest.cc
@@ -0,0 +1,1246 @@
+// Copyright 2014 The Chromium Authors. 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 <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+
+TEST(UnionTest, PlainOldDataGetterSetter) {
+ PodUnionPtr pod(PodUnion::New());
+
+ pod->set_f_int8(10);
+ EXPECT_EQ(10, pod->get_f_int8());
+ EXPECT_TRUE(pod->is_f_int8());
+ EXPECT_FALSE(pod->is_f_int8_other());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT8);
+
+ pod->set_f_uint8(11);
+ EXPECT_EQ(11, pod->get_f_uint8());
+ EXPECT_TRUE(pod->is_f_uint8());
+ EXPECT_FALSE(pod->is_f_int8());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT8);
+
+ pod->set_f_int16(12);
+ EXPECT_EQ(12, pod->get_f_int16());
+ EXPECT_TRUE(pod->is_f_int16());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT16);
+
+ pod->set_f_uint16(13);
+ EXPECT_EQ(13, pod->get_f_uint16());
+ EXPECT_TRUE(pod->is_f_uint16());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT16);
+
+ pod->set_f_int32(14);
+ EXPECT_EQ(14, pod->get_f_int32());
+ EXPECT_TRUE(pod->is_f_int32());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT32);
+
+ pod->set_f_uint32(static_cast<uint32_t>(15));
+ EXPECT_EQ(static_cast<uint32_t>(15), pod->get_f_uint32());
+ EXPECT_TRUE(pod->is_f_uint32());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT32);
+
+ pod->set_f_int64(16);
+ EXPECT_EQ(16, pod->get_f_int64());
+ EXPECT_TRUE(pod->is_f_int64());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT64);
+
+ pod->set_f_uint64(static_cast<uint64_t>(17));
+ EXPECT_EQ(static_cast<uint64_t>(17), pod->get_f_uint64());
+ EXPECT_TRUE(pod->is_f_uint64());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT64);
+
+ pod->set_f_float(1.5);
+ EXPECT_EQ(1.5, pod->get_f_float());
+ EXPECT_TRUE(pod->is_f_float());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_FLOAT);
+
+ pod->set_f_double(1.9);
+ EXPECT_EQ(1.9, pod->get_f_double());
+ EXPECT_TRUE(pod->is_f_double());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_DOUBLE);
+
+ pod->set_f_bool(true);
+ EXPECT_TRUE(pod->get_f_bool());
+ pod->set_f_bool(false);
+ EXPECT_FALSE(pod->get_f_bool());
+ EXPECT_TRUE(pod->is_f_bool());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_BOOL);
+
+ pod->set_f_enum(AnEnum::SECOND);
+ EXPECT_EQ(AnEnum::SECOND, pod->get_f_enum());
+ EXPECT_TRUE(pod->is_f_enum());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM);
+}
+
+TEST(UnionTest, PodEquals) {
+ PodUnionPtr pod1(PodUnion::New());
+ PodUnionPtr pod2(PodUnion::New());
+
+ pod1->set_f_int8(10);
+ pod2->set_f_int8(10);
+ EXPECT_TRUE(pod1.Equals(pod2));
+
+ pod2->set_f_int8(11);
+ EXPECT_FALSE(pod1.Equals(pod2));
+
+ pod2->set_f_int8_other(10);
+ EXPECT_FALSE(pod1.Equals(pod2));
+}
+
+TEST(UnionTest, PodClone) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(10);
+
+ PodUnionPtr pod_clone = pod.Clone();
+ EXPECT_EQ(10, pod_clone->get_f_int8());
+ EXPECT_TRUE(pod_clone->is_f_int8());
+ EXPECT_EQ(pod_clone->which(), PodUnion::Tag::F_INT8);
+}
+
+TEST(UnionTest, PodSerialization) {
+ PodUnionPtr pod1(PodUnion::New());
+ pod1->set_f_int8(10);
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<PodUnionDataView>(
+ pod1, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false,
+ &context);
+
+ PodUnionPtr pod2;
+ mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, &context);
+
+ EXPECT_EQ(10, pod2->get_f_int8());
+ EXPECT_TRUE(pod2->is_f_int8());
+ EXPECT_EQ(pod2->which(), PodUnion::Tag::F_INT8);
+}
+
+TEST(UnionTest, EnumSerialization) {
+ PodUnionPtr pod1(PodUnion::New());
+ pod1->set_f_enum(AnEnum::SECOND);
+
+ size_t size = mojo::internal::PrepareToSerialize<PodUnionDataView>(
+ pod1, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false,
+ nullptr);
+
+ PodUnionPtr pod2;
+ mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr);
+
+ EXPECT_EQ(AnEnum::SECOND, pod2->get_f_enum());
+ EXPECT_TRUE(pod2->is_f_enum());
+ EXPECT_EQ(pod2->which(), PodUnion::Tag::F_ENUM);
+}
+
+TEST(UnionTest, PodValidation) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(10);
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(
+ internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, SerializeNotNull) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(0);
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ EXPECT_FALSE(data->is_null());
+}
+
+TEST(UnionTest, SerializeIsNullInlined) {
+ PodUnionPtr pod;
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf);
+
+ // Check that dirty output buffers are handled correctly by serialization.
+ data->size = 16U;
+ data->tag = PodUnion::Tag::F_UINT16;
+ data->data.f_f_int16 = 20;
+
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, true, nullptr);
+ EXPECT_TRUE(data->is_null());
+
+ PodUnionPtr pod2;
+ mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr);
+ EXPECT_TRUE(pod2.is_null());
+}
+
+TEST(UnionTest, SerializeIsNullNotInlined) {
+ PodUnionPtr pod;
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ EXPECT_EQ(nullptr, data);
+}
+
+TEST(UnionTest, NullValidation) {
+ void* buf = nullptr;
+ mojo::internal::ValidationContext validation_context(buf, 0, 0, 0);
+ EXPECT_TRUE(internal::PodUnion_Data::Validate(
+ buf, &validation_context, false));
+}
+
+TEST(UnionTest, OutOfAlignmentValidation) {
+ size_t size = sizeof(internal::PodUnion_Data);
+ // Get an aligned object and shift the alignment.
+ mojo::internal::FixedBufferForTesting aligned_buf(size + 1);
+ void* raw_buf = aligned_buf.Leak();
+ char* buf = reinterpret_cast<char*>(raw_buf) + 1;
+
+ internal::PodUnion_Data* data =
+ reinterpret_cast<internal::PodUnion_Data*>(buf);
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::PodUnion_Data::Validate(
+ buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, OOBValidation) {
+ size_t size = sizeof(internal::PodUnion_Data) - 1;
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf);
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ void* raw_buf = buf.Leak();
+ EXPECT_FALSE(
+ internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, UnknownTagValidation) {
+ size_t size = sizeof(internal::PodUnion_Data);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf);
+ data->tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(0xFFFFFF);
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ void* raw_buf = buf.Leak();
+ EXPECT_FALSE(
+ internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, UnknownEnumValueValidation) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_enum(static_cast<AnEnum>(0xFFFF));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(
+ internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, UnknownExtensibleEnumValueValidation) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_extensible_enum(static_cast<AnExtensibleEnum>(0xFFFF));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(
+ internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, StringGetterSetter) {
+ ObjectUnionPtr pod(ObjectUnion::New());
+
+ std::string hello("hello world");
+ pod->set_f_string(hello);
+ EXPECT_EQ(hello, pod->get_f_string());
+ EXPECT_TRUE(pod->is_f_string());
+ EXPECT_EQ(pod->which(), ObjectUnion::Tag::F_STRING);
+}
+
+TEST(UnionTest, StringEquals) {
+ ObjectUnionPtr pod1(ObjectUnion::New());
+ ObjectUnionPtr pod2(ObjectUnion::New());
+
+ pod1->set_f_string("hello world");
+ pod2->set_f_string("hello world");
+ EXPECT_TRUE(pod1.Equals(pod2));
+
+ pod2->set_f_string("hello universe");
+ EXPECT_FALSE(pod1.Equals(pod2));
+}
+
+TEST(UnionTest, StringClone) {
+ ObjectUnionPtr pod(ObjectUnion::New());
+
+ std::string hello("hello world");
+ pod->set_f_string(hello);
+ ObjectUnionPtr pod_clone = pod.Clone();
+ EXPECT_EQ(hello, pod_clone->get_f_string());
+ EXPECT_TRUE(pod_clone->is_f_string());
+ EXPECT_EQ(pod_clone->which(), ObjectUnion::Tag::F_STRING);
+}
+
+TEST(UnionTest, StringSerialization) {
+ ObjectUnionPtr pod1(ObjectUnion::New());
+
+ std::string hello("hello world");
+ pod1->set_f_string(hello);
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ pod1, false, nullptr);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(pod1, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr pod2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(data, &pod2, nullptr);
+ EXPECT_EQ(hello, pod2->get_f_string());
+ EXPECT_TRUE(pod2->is_f_string());
+ EXPECT_EQ(pod2->which(), ObjectUnion::Tag::F_STRING);
+}
+
+TEST(UnionTest, NullStringValidation) {
+ size_t size = sizeof(internal::ObjectUnion_Data);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf);
+ data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
+ data->data.unknown = 0x0;
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ void* raw_buf = buf.Leak();
+ EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, StringPointerOverflowValidation) {
+ size_t size = sizeof(internal::ObjectUnion_Data);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf);
+ data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
+ data->data.unknown = 0xFFFFFFFFFFFFFFFF;
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ void* raw_buf = buf.Leak();
+ EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, StringValidateOOB) {
+ size_t size = 32;
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf);
+ data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
+
+ data->data.f_f_string.offset = 8;
+ char* ptr = reinterpret_cast<char*>(&data->data.f_f_string);
+ mojo::internal::ArrayHeader* array_header =
+ reinterpret_cast<mojo::internal::ArrayHeader*>(ptr + *ptr);
+ array_header->num_bytes = 20; // This should go out of bounds.
+ array_header->num_elements = 20;
+ mojo::internal::ValidationContext validation_context(data, 32, 0, 0);
+ void* raw_buf = buf.Leak();
+ EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+// TODO(azani): Move back in array_unittest.cc when possible.
+// Array tests
+TEST(UnionTest, PodUnionInArray) {
+ SmallStructPtr small_struct(SmallStruct::New());
+ small_struct->pod_union_array.emplace(2);
+ small_struct->pod_union_array.value()[0] = PodUnion::New();
+ small_struct->pod_union_array.value()[1] = PodUnion::New();
+
+ small_struct->pod_union_array.value()[0]->set_f_int8(10);
+ small_struct->pod_union_array.value()[1]->set_f_int16(12);
+
+ EXPECT_EQ(10, small_struct->pod_union_array.value()[0]->get_f_int8());
+ EXPECT_EQ(12, small_struct->pod_union_array.value()[1]->get_f_int16());
+}
+
+TEST(UnionTest, PodUnionInArraySerialization) {
+ std::vector<PodUnionPtr> array(2);
+ array[0] = PodUnion::New();
+ array[1] = PodUnion::New();
+
+ array[0]->set_f_int8(10);
+ array[1]->set_f_int16(12);
+ EXPECT_EQ(2U, array.size());
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>(
+ array, nullptr);
+ EXPECT_EQ(40U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ mojo::internal::Array_Data<internal::PodUnion_Data>* data;
+ mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
+ mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>(
+ array, &buf, &data, &validate_params, nullptr);
+
+ std::vector<PodUnionPtr> array2;
+ mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(data, &array2,
+ nullptr);
+
+ EXPECT_EQ(2U, array2.size());
+
+ EXPECT_EQ(10, array2[0]->get_f_int8());
+ EXPECT_EQ(12, array2[1]->get_f_int16());
+}
+
+TEST(UnionTest, PodUnionInArraySerializationWithNull) {
+ std::vector<PodUnionPtr> array(2);
+ array[0] = PodUnion::New();
+
+ array[0]->set_f_int8(10);
+ EXPECT_EQ(2U, array.size());
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>(
+ array, nullptr);
+ EXPECT_EQ(40U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ mojo::internal::Array_Data<internal::PodUnion_Data>* data;
+ mojo::internal::ContainerValidateParams validate_params(0, true, nullptr);
+ mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>(
+ array, &buf, &data, &validate_params, nullptr);
+
+ std::vector<PodUnionPtr> array2;
+ mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(data, &array2,
+ nullptr);
+
+ EXPECT_EQ(2U, array2.size());
+
+ EXPECT_EQ(10, array2[0]->get_f_int8());
+ EXPECT_TRUE(array2[1].is_null());
+}
+
+TEST(UnionTest, ObjectUnionInArraySerialization) {
+ std::vector<ObjectUnionPtr> array(2);
+ array[0] = ObjectUnion::New();
+ array[1] = ObjectUnion::New();
+
+ array[0]->set_f_string("hello");
+ array[1]->set_f_string("world");
+ EXPECT_EQ(2U, array.size());
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<ArrayDataView<ObjectUnionDataView>>(
+ array, nullptr);
+ EXPECT_EQ(72U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ mojo::internal::Array_Data<internal::ObjectUnion_Data>* data;
+ mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
+ mojo::internal::Serialize<ArrayDataView<ObjectUnionDataView>>(
+ array, &buf, &data, &validate_params, nullptr);
+
+ std::vector<char> new_buf;
+ new_buf.resize(size);
+
+ void* raw_buf = buf.Leak();
+ memcpy(new_buf.data(), raw_buf, size);
+ free(raw_buf);
+
+ data =
+ reinterpret_cast<mojo::internal::Array_Data<internal::ObjectUnion_Data>*>(
+ new_buf.data());
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ ASSERT_TRUE(mojo::internal::Array_Data<internal::ObjectUnion_Data>::Validate(
+ data, &validation_context, &validate_params));
+
+ std::vector<ObjectUnionPtr> array2;
+ mojo::internal::Deserialize<ArrayDataView<ObjectUnionDataView>>(data, &array2,
+ nullptr);
+
+ EXPECT_EQ(2U, array2.size());
+
+ EXPECT_EQ("hello", array2[0]->get_f_string());
+ EXPECT_EQ("world", array2[1]->get_f_string());
+}
+
+// TODO(azani): Move back in struct_unittest.cc when possible.
+// Struct tests
+TEST(UnionTest, Clone_Union) {
+ SmallStructPtr small_struct(SmallStruct::New());
+ small_struct->pod_union = PodUnion::New();
+ small_struct->pod_union->set_f_int8(10);
+
+ SmallStructPtr clone = small_struct.Clone();
+ EXPECT_EQ(10, clone->pod_union->get_f_int8());
+}
+
+// Serialization test of a struct with a union of plain old data.
+TEST(UnionTest, Serialization_UnionOfPods) {
+ SmallStructPtr small_struct(SmallStruct::New());
+ small_struct->pod_union = PodUnion::New();
+ small_struct->pod_union->set_f_int32(10);
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+
+ SmallStructPtr deserialized;
+ mojo::internal::Deserialize<SmallStructDataView>(data, &deserialized,
+ &context);
+
+ EXPECT_EQ(10, deserialized->pod_union->get_f_int32());
+}
+
+// Serialization test of a struct with a union of structs.
+TEST(UnionTest, Serialization_UnionOfObjects) {
+ SmallObjStructPtr obj_struct(SmallObjStruct::New());
+ obj_struct->obj_union = ObjectUnion::New();
+ std::string hello("hello world");
+ obj_struct->obj_union->set_f_string(hello);
+
+ size_t size = mojo::internal::PrepareToSerialize<SmallObjStructDataView>(
+ obj_struct, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallObjStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallObjStructDataView>(obj_struct, &buf, &data,
+ nullptr);
+
+ SmallObjStructPtr deserialized;
+ mojo::internal::Deserialize<SmallObjStructDataView>(data, &deserialized,
+ nullptr);
+
+ EXPECT_EQ(hello, deserialized->obj_union->get_f_string());
+}
+
+// Validation test of a struct with a union.
+TEST(UnionTest, Validation_UnionsInStruct) {
+ SmallStructPtr small_struct(SmallStruct::New());
+ small_struct->pod_union = PodUnion::New();
+ small_struct->pod_union->set_f_int32(10);
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(internal::SmallStruct_Data::Validate(
+ raw_buf, &validation_context));
+ free(raw_buf);
+}
+
+// Validation test of a struct union fails due to unknown union tag.
+TEST(UnionTest, Validation_PodUnionInStruct_Failure) {
+ SmallStructPtr small_struct(SmallStruct::New());
+ small_struct->pod_union = PodUnion::New();
+ small_struct->pod_union->set_f_int32(10);
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+ data->pod_union.tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(100);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::SmallStruct_Data::Validate(
+ raw_buf, &validation_context));
+ free(raw_buf);
+}
+
+// Validation fails due to non-nullable null union in struct.
+TEST(UnionTest, Validation_NullUnion_Failure) {
+ SmallStructNonNullableUnionPtr small_struct(
+ SmallStructNonNullableUnion::New());
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<SmallStructNonNullableUnionDataView>(
+ small_struct, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStructNonNullableUnion_Data* data =
+ internal::SmallStructNonNullableUnion_Data::New(&buf);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::SmallStructNonNullableUnion_Data::Validate(
+ raw_buf, &validation_context));
+ free(raw_buf);
+}
+
+// Validation passes with nullable null union.
+TEST(UnionTest, Validation_NullableUnion) {
+ SmallStructPtr small_struct(SmallStruct::New());
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(internal::SmallStruct_Data::Validate(
+ raw_buf, &validation_context));
+ free(raw_buf);
+}
+
+// TODO(azani): Move back in map_unittest.cc when possible.
+// Map Tests
+TEST(UnionTest, PodUnionInMap) {
+ SmallStructPtr small_struct(SmallStruct::New());
+ small_struct->pod_union_map.emplace();
+ small_struct->pod_union_map.value()["one"] = PodUnion::New();
+ small_struct->pod_union_map.value()["two"] = PodUnion::New();
+
+ small_struct->pod_union_map.value()["one"]->set_f_int8(8);
+ small_struct->pod_union_map.value()["two"]->set_f_int16(16);
+
+ EXPECT_EQ(8, small_struct->pod_union_map.value()["one"]->get_f_int8());
+ EXPECT_EQ(16, small_struct->pod_union_map.value()["two"]->get_f_int16());
+}
+
+TEST(UnionTest, PodUnionInMapSerialization) {
+ using MojomType = MapDataView<StringDataView, PodUnionDataView>;
+
+ std::unordered_map<std::string, PodUnionPtr> map;
+ map.insert(std::make_pair("one", PodUnion::New()));
+ map.insert(std::make_pair("two", PodUnion::New()));
+
+ map["one"]->set_f_int8(8);
+ map["two"]->set_f_int16(16);
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<MojomType>(map, &context);
+ EXPECT_EQ(120U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ new mojo::internal::ContainerValidateParams(0, false, nullptr),
+ new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params,
+ &context);
+
+ std::unordered_map<std::string, PodUnionPtr> map2;
+ mojo::internal::Deserialize<MojomType>(data, &map2, &context);
+
+ EXPECT_EQ(8, map2["one"]->get_f_int8());
+ EXPECT_EQ(16, map2["two"]->get_f_int16());
+}
+
+TEST(UnionTest, PodUnionInMapSerializationWithNull) {
+ using MojomType = MapDataView<StringDataView, PodUnionDataView>;
+
+ std::unordered_map<std::string, PodUnionPtr> map;
+ map.insert(std::make_pair("one", PodUnion::New()));
+ map.insert(std::make_pair("two", nullptr));
+
+ map["one"]->set_f_int8(8);
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<MojomType>(map, &context);
+ EXPECT_EQ(120U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ new mojo::internal::ContainerValidateParams(0, false, nullptr),
+ new mojo::internal::ContainerValidateParams(0, true, nullptr));
+ mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params,
+ &context);
+
+ std::unordered_map<std::string, PodUnionPtr> map2;
+ mojo::internal::Deserialize<MojomType>(data, &map2, &context);
+
+ EXPECT_EQ(8, map2["one"]->get_f_int8());
+ EXPECT_TRUE(map2["two"].is_null());
+}
+
+TEST(UnionTest, StructInUnionGetterSetterPasser) {
+ DummyStructPtr dummy(DummyStruct::New());
+ dummy->f_int8 = 8;
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_dummy(std::move(dummy));
+
+ EXPECT_EQ(8, obj->get_f_dummy()->f_int8);
+}
+
+TEST(UnionTest, StructInUnionSerialization) {
+ DummyStructPtr dummy(DummyStruct::New());
+ dummy->f_int8 = 8;
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_dummy(std::move(dummy));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr);
+ EXPECT_EQ(8, obj2->get_f_dummy()->f_int8);
+}
+
+TEST(UnionTest, StructInUnionValidation) {
+ DummyStructPtr dummy(DummyStruct::New());
+ dummy->f_int8 = 8;
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_dummy(std::move(dummy));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, StructInUnionValidationNonNullable) {
+ mojo::internal::SerializationWarningObserverForTesting suppress_warning;
+
+ DummyStructPtr dummy(nullptr);
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_dummy(std::move(dummy));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, StructInUnionValidationNullable) {
+ DummyStructPtr dummy(nullptr);
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_nullable(std::move(dummy));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, ArrayInUnionGetterSetter) {
+ std::vector<int8_t> array(2);
+ array[0] = 8;
+ array[1] = 9;
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_array_int8(std::move(array));
+
+ EXPECT_EQ(8, obj->get_f_array_int8()[0]);
+ EXPECT_EQ(9, obj->get_f_array_int8()[1]);
+}
+
+TEST(UnionTest, ArrayInUnionSerialization) {
+ std::vector<int8_t> array(2);
+ array[0] = 8;
+ array[1] = 9;
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_array_int8(std::move(array));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr);
+
+ EXPECT_EQ(8, obj2->get_f_array_int8()[0]);
+ EXPECT_EQ(9, obj2->get_f_array_int8()[1]);
+}
+
+TEST(UnionTest, ArrayInUnionValidation) {
+ std::vector<int8_t> array(2);
+ array[0] = 8;
+ array[1] = 9;
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_array_int8(std::move(array));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, MapInUnionGetterSetter) {
+ std::unordered_map<std::string, int8_t> map;
+ map.insert({"one", 1});
+ map.insert({"two", 2});
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_map_int8(std::move(map));
+
+ EXPECT_EQ(1, obj->get_f_map_int8()["one"]);
+ EXPECT_EQ(2, obj->get_f_map_int8()["two"]);
+}
+
+TEST(UnionTest, MapInUnionSerialization) {
+ std::unordered_map<std::string, int8_t> map;
+ map.insert({"one", 1});
+ map.insert({"two", 2});
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_map_int8(std::move(map));
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, &context);
+ EXPECT_EQ(112U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ &context);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, &context);
+
+ EXPECT_EQ(1, obj2->get_f_map_int8()["one"]);
+ EXPECT_EQ(2, obj2->get_f_map_int8()["two"]);
+}
+
+TEST(UnionTest, MapInUnionValidation) {
+ std::unordered_map<std::string, int8_t> map;
+ map.insert({"one", 1});
+ map.insert({"two", 2});
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_map_int8(std::move(map));
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, &context);
+ EXPECT_EQ(112U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, UnionInUnionGetterSetter) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(10);
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_pod_union(std::move(pod));
+
+ EXPECT_EQ(10, obj->get_f_pod_union()->get_f_int8());
+}
+
+TEST(UnionTest, UnionInUnionSerialization) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(10);
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_pod_union(std::move(pod));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr);
+ EXPECT_EQ(10, obj2->get_f_pod_union()->get_f_int8());
+}
+
+TEST(UnionTest, UnionInUnionValidation) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(10);
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_pod_union(std::move(pod));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, UnionInUnionValidationNonNullable) {
+ mojo::internal::SerializationWarningObserverForTesting suppress_warning;
+
+ PodUnionPtr pod(nullptr);
+
+ ObjectUnionPtr obj(ObjectUnion::New());
+ obj->set_f_pod_union(std::move(pod));
+
+ size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, HandleInUnionGetterSetter) {
+ ScopedMessagePipeHandle pipe0;
+ ScopedMessagePipeHandle pipe1;
+
+ CreateMessagePipe(nullptr, &pipe0, &pipe1);
+
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_message_pipe(std::move(pipe1));
+
+ std::string golden("hello world");
+ WriteTextMessage(pipe0.get(), golden);
+
+ std::string actual;
+ ReadTextMessage(handle->get_f_message_pipe().get(), &actual);
+
+ EXPECT_EQ(golden, actual);
+}
+
+TEST(UnionTest, HandleInUnionSerialization) {
+ ScopedMessagePipeHandle pipe0;
+ ScopedMessagePipeHandle pipe1;
+
+ CreateMessagePipe(nullptr, &pipe0, &pipe1);
+
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_message_pipe(std::move(pipe1));
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+ EXPECT_EQ(1U, context.handles.size());
+
+ HandleUnionPtr handle2(HandleUnion::New());
+ mojo::internal::Deserialize<HandleUnionDataView>(data, &handle2, &context);
+
+ std::string golden("hello world");
+ WriteTextMessage(pipe0.get(), golden);
+
+ std::string actual;
+ ReadTextMessage(handle2->get_f_message_pipe().get(), &actual);
+
+ EXPECT_EQ(golden, actual);
+}
+
+TEST(UnionTest, HandleInUnionValidation) {
+ ScopedMessagePipeHandle pipe0;
+ ScopedMessagePipeHandle pipe1;
+
+ CreateMessagePipe(nullptr, &pipe0, &pipe1);
+
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_message_pipe(std::move(pipe1));
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 1, 0);
+ EXPECT_TRUE(internal::HandleUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, HandleInUnionValidationNull) {
+ mojo::internal::SerializationWarningObserverForTesting suppress_warning;
+
+ ScopedMessagePipeHandle pipe;
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_message_pipe(std::move(pipe));
+
+ mojo::internal::SerializationContext context;
+ size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 1, 0);
+ EXPECT_FALSE(internal::HandleUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+class SmallCacheImpl : public SmallCache {
+ public:
+ explicit SmallCacheImpl(const base::Closure& closure)
+ : int_value_(0), closure_(closure) {}
+ ~SmallCacheImpl() override {}
+ int64_t int_value() const { return int_value_; }
+
+ private:
+ void SetIntValue(int64_t int_value) override {
+ int_value_ = int_value;
+ closure_.Run();
+ }
+ void GetIntValue(const GetIntValueCallback& callback) override {
+ callback.Run(int_value_);
+ }
+
+ int64_t int_value_;
+ base::Closure closure_;
+};
+
+TEST(UnionTest, InterfaceInUnion) {
+ base::MessageLoop message_loop;
+ base::RunLoop run_loop;
+ SmallCacheImpl impl(run_loop.QuitClosure());
+ SmallCachePtr ptr;
+ Binding<SmallCache> bindings(&impl, MakeRequest(&ptr));
+
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_small_cache(std::move(ptr));
+
+ handle->get_f_small_cache()->SetIntValue(10);
+ run_loop.Run();
+ EXPECT_EQ(10, impl.int_value());
+}
+
+TEST(UnionTest, InterfaceInUnionSerialization) {
+ base::MessageLoop message_loop;
+ base::RunLoop run_loop;
+ SmallCacheImpl impl(run_loop.QuitClosure());
+ SmallCachePtr ptr;
+ Binding<SmallCache> bindings(&impl, MakeRequest(&ptr));
+
+ mojo::internal::SerializationContext context;
+ HandleUnionPtr handle(HandleUnion::New());
+ handle->set_f_small_cache(std::move(ptr));
+ size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+ EXPECT_EQ(1U, context.handles.size());
+
+ HandleUnionPtr handle2(HandleUnion::New());
+ mojo::internal::Deserialize<HandleUnionDataView>(data, &handle2, &context);
+
+ handle2->get_f_small_cache()->SetIntValue(10);
+ run_loop.Run();
+ EXPECT_EQ(10, impl.int_value());
+}
+
+class UnionInterfaceImpl : public UnionInterface {
+ public:
+ UnionInterfaceImpl() {}
+ ~UnionInterfaceImpl() override {}
+
+ private:
+ void Echo(PodUnionPtr in, const EchoCallback& callback) override {
+ callback.Run(std::move(in));
+ }
+};
+
+void ExpectInt16(int16_t value, PodUnionPtr out) {
+ EXPECT_EQ(value, out->get_f_int16());
+}
+
+TEST(UnionTest, UnionInInterface) {
+ base::MessageLoop message_loop;
+ UnionInterfaceImpl impl;
+ UnionInterfacePtr ptr;
+ Binding<UnionInterface> bindings(&impl, MakeRequest(&ptr));
+
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int16(16);
+
+ ptr->Echo(std::move(pod), base::Bind(&ExpectInt16, 16));
+ base::RunLoop().RunUntilIdle();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/validation_context_unittest.cc b/mojo/public/cpp/bindings/tests/validation_context_unittest.cc
new file mode 100644
index 0000000000..9ce9d60e49
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_context_unittest.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 <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/system/core.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using Handle_Data = mojo::internal::Handle_Data;
+using AssociatedEndpointHandle_Data =
+ mojo::internal::AssociatedEndpointHandle_Data;
+
+const void* ToPtr(uintptr_t ptr) {
+ return reinterpret_cast<const void*>(ptr);
+}
+
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+TEST(ValidationContextTest, ConstructorRangeOverflow) {
+ {
+ // Test memory range overflow.
+ internal::ValidationContext context(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 5000, 0, 0);
+
+ EXPECT_FALSE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1));
+ EXPECT_FALSE(context.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1));
+ }
+
+ if (sizeof(size_t) <= sizeof(uint32_t))
+ return;
+
+ {
+ // Test handle index range overflow.
+ size_t num_handles =
+ static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 5;
+ internal::ValidationContext context(ToPtr(0), 0, num_handles, 0);
+
+ EXPECT_FALSE(context.ClaimHandle(Handle_Data(0)));
+ EXPECT_FALSE(context.ClaimHandle(
+ Handle_Data(std::numeric_limits<uint32_t>::max() - 1)));
+
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ size_t num_associated_endpoint_handles =
+ static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 5;
+ internal::ValidationContext context(ToPtr(0), 0, 0,
+ num_associated_endpoint_handles);
+
+ EXPECT_FALSE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(0)));
+ EXPECT_FALSE(
+ context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data(
+ std::numeric_limits<uint32_t>::max() - 1)));
+
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+}
+#endif
+
+TEST(ValidationContextTest, IsValidRange) {
+ {
+ internal::ValidationContext context(ToPtr(1234), 100, 0, 0);
+
+ // Basics.
+ EXPECT_FALSE(context.IsValidRange(ToPtr(100), 5));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1230), 50));
+ EXPECT_TRUE(context.IsValidRange(ToPtr(1234), 5));
+ EXPECT_TRUE(context.IsValidRange(ToPtr(1240), 50));
+ EXPECT_TRUE(context.IsValidRange(ToPtr(1234), 100));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 101));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1240), 100));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1333), 5));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(2234), 5));
+
+ // ClaimMemory() updates the valid range.
+ EXPECT_TRUE(context.ClaimMemory(ToPtr(1254), 10));
+
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 1));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1254), 10));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1263), 1));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1263), 10));
+ EXPECT_TRUE(context.IsValidRange(ToPtr(1264), 10));
+ EXPECT_TRUE(context.IsValidRange(ToPtr(1264), 70));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1264), 71));
+ }
+
+ {
+ internal::ValidationContext context(ToPtr(1234), 100, 0, 0);
+ // Should return false for empty ranges.
+ EXPECT_FALSE(context.IsValidRange(ToPtr(0), 0));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1200), 0));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 0));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1240), 0));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(2234), 0));
+ }
+
+ {
+ // The valid memory range is empty.
+ internal::ValidationContext context(ToPtr(1234), 0, 0, 0);
+
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 1));
+ EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 0));
+ }
+
+ {
+ internal::ValidationContext context(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 2000), 1000, 0, 0);
+
+ // Test overflow.
+ EXPECT_FALSE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 4000));
+ EXPECT_FALSE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500),
+ std::numeric_limits<uint32_t>::max()));
+
+ // This should be fine.
+ EXPECT_TRUE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 200));
+ }
+}
+
+TEST(ValidationContextTest, ClaimHandle) {
+ {
+ internal::ValidationContext context(ToPtr(0), 0, 10, 0);
+
+ // Basics.
+ EXPECT_TRUE(context.ClaimHandle(Handle_Data(0)));
+ EXPECT_FALSE(context.ClaimHandle(Handle_Data(0)));
+
+ EXPECT_TRUE(context.ClaimHandle(Handle_Data(9)));
+ EXPECT_FALSE(context.ClaimHandle(Handle_Data(10)));
+
+ // Should fail because it is smaller than the max index that has been
+ // claimed.
+ EXPECT_FALSE(context.ClaimHandle(Handle_Data(8)));
+
+ // Should return true for invalid handle.
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(internal::kEncodedInvalidHandleValue)));
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ // No handle to claim.
+ internal::ValidationContext context(ToPtr(0), 0, 0, 0);
+
+ EXPECT_FALSE(context.ClaimHandle(Handle_Data(0)));
+
+ // Should still return true for invalid handle.
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ // Test the case that |num_handles| is the same value as
+ // |internal::kEncodedInvalidHandleValue|.
+ EXPECT_EQ(internal::kEncodedInvalidHandleValue,
+ std::numeric_limits<uint32_t>::max());
+ internal::ValidationContext context(
+ ToPtr(0), 0, std::numeric_limits<uint32_t>::max(), 0);
+
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(context.ClaimHandle(
+ Handle_Data(std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(context.ClaimHandle(Handle_Data(0)));
+
+ // Should still return true for invalid handle.
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+}
+
+TEST(ValidationContextTest, ClaimAssociatedEndpointHandle) {
+ {
+ internal::ValidationContext context(ToPtr(0), 0, 0, 10);
+
+ // Basics.
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(0)));
+ EXPECT_FALSE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(0)));
+
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(9)));
+ EXPECT_FALSE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(10)));
+
+ // Should fail because it is smaller than the max index that has been
+ // claimed.
+ EXPECT_FALSE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(8)));
+
+ // Should return true for invalid handle.
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue)));
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ // No handle to claim.
+ internal::ValidationContext context(ToPtr(0), 0, 0, 0);
+
+ EXPECT_FALSE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(0)));
+
+ // Should still return true for invalid handle.
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ // Test the case that |num_associated_endpoint_handles| is the same value as
+ // |internal::kEncodedInvalidHandleValue|.
+ EXPECT_EQ(internal::kEncodedInvalidHandleValue,
+ std::numeric_limits<uint32_t>::max());
+ internal::ValidationContext context(ToPtr(0), 0, 0,
+ std::numeric_limits<uint32_t>::max());
+
+ EXPECT_TRUE(
+ context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data(
+ std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(
+ context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data(
+ std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(0)));
+
+ // Should still return true for invalid handle.
+ EXPECT_TRUE(context.ClaimAssociatedEndpointHandle(
+ AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+}
+
+TEST(ValidationContextTest, ClaimMemory) {
+ {
+ internal::ValidationContext context(ToPtr(1000), 2000, 0, 0);
+
+ // Basics.
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(500), 100));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(800), 300));
+ EXPECT_TRUE(context.ClaimMemory(ToPtr(1000), 100));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(1099), 100));
+ EXPECT_TRUE(context.ClaimMemory(ToPtr(1100), 200));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(2000), 1001));
+ EXPECT_TRUE(context.ClaimMemory(ToPtr(2000), 500));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(2000), 500));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(1400), 100));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(3000), 1));
+ EXPECT_TRUE(context.ClaimMemory(ToPtr(2500), 500));
+ }
+
+ {
+ // No memory to claim.
+ internal::ValidationContext context(ToPtr(10000), 0, 0, 0);
+
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(10000), 1));
+ EXPECT_FALSE(context.ClaimMemory(ToPtr(10000), 0));
+ }
+
+ {
+ internal::ValidationContext context(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1000), 500, 0, 0);
+
+ // Test overflow.
+ EXPECT_FALSE(context.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 4000));
+ EXPECT_FALSE(
+ context.ClaimMemory(ToPtr(std::numeric_limits<uintptr_t>::max() - 750),
+ std::numeric_limits<uint32_t>::max()));
+
+ // This should be fine.
+ EXPECT_TRUE(context.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 200));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc
new file mode 100644
index 0000000000..f3097372a4
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.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 "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <limits>
+#include <map>
+#include <set>
+#include <utility>
+
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class ValidationTestInputParser {
+ public:
+ ValidationTestInputParser(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message);
+ ~ValidationTestInputParser();
+
+ bool Run();
+
+ private:
+ struct DataType;
+
+ typedef std::pair<const char*, const char*> Range;
+
+ typedef bool (ValidationTestInputParser::*ParseDataFunc)(
+ const DataType& type,
+ const std::string& value_string);
+
+ struct DataType {
+ const char* name;
+ size_t name_size;
+ size_t data_size;
+ ParseDataFunc parse_data_func;
+ };
+
+ // A dist4/8 item that hasn't been matched with an anchr item.
+ struct PendingDistanceItem {
+ // Where this data item is located in |data_|.
+ size_t pos;
+ // Either 4 or 8 (bytes).
+ size_t data_size;
+ };
+
+ bool GetNextItem(Range* range);
+
+ bool ParseItem(const Range& range);
+
+ bool ParseUnsignedInteger(const DataType& type,
+ const std::string& value_string);
+ bool ParseSignedInteger(const DataType& type,
+ const std::string& value_string);
+ bool ParseFloat(const DataType& type, const std::string& value_string);
+ bool ParseDouble(const DataType& type, const std::string& value_string);
+ bool ParseBinarySequence(const DataType& type,
+ const std::string& value_string);
+ bool ParseDistance(const DataType& type, const std::string& value_string);
+ bool ParseAnchor(const DataType& type, const std::string& value_string);
+ bool ParseHandles(const DataType& type, const std::string& value_string);
+
+ bool StartsWith(const Range& range, const char* prefix, size_t prefix_length);
+
+ bool ConvertToUnsignedInteger(const std::string& value_string,
+ unsigned long long int* value);
+
+ template <typename T>
+ void AppendData(T data) {
+ size_t pos = data_->size();
+ data_->resize(pos + sizeof(T));
+ memcpy(&(*data_)[pos], &data, sizeof(T));
+ }
+
+ template <typename TargetType, typename InputType>
+ bool ConvertAndAppendData(InputType value) {
+ if (value > std::numeric_limits<TargetType>::max() ||
+ value < std::numeric_limits<TargetType>::min()) {
+ return false;
+ }
+ AppendData(static_cast<TargetType>(value));
+ return true;
+ }
+
+ template <typename TargetType, typename InputType>
+ bool ConvertAndFillData(size_t pos, InputType value) {
+ if (value > std::numeric_limits<TargetType>::max() ||
+ value < std::numeric_limits<TargetType>::min()) {
+ return false;
+ }
+ TargetType target_value = static_cast<TargetType>(value);
+ assert(pos + sizeof(TargetType) <= data_->size());
+ memcpy(&(*data_)[pos], &target_value, sizeof(TargetType));
+ return true;
+ }
+
+ static const DataType kDataTypes[];
+ static const size_t kDataTypeCount;
+
+ const std::string& input_;
+ size_t input_cursor_;
+
+ std::vector<uint8_t>* data_;
+ size_t* num_handles_;
+ std::string* error_message_;
+
+ std::map<std::string, PendingDistanceItem> pending_distance_items_;
+ std::set<std::string> anchors_;
+};
+
+#define DATA_TYPE(name, data_size, parse_data_func) \
+ { name, sizeof(name) - 1, data_size, parse_data_func }
+
+const ValidationTestInputParser::DataType
+ ValidationTestInputParser::kDataTypes[] = {
+ DATA_TYPE("[u1]", 1, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[u2]", 2, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[u4]", 4, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[u8]", 8, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[s1]", 1, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[s2]", 2, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[s4]", 4, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[s8]", 8, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[b]", 1, &ValidationTestInputParser::ParseBinarySequence),
+ DATA_TYPE("[f]", 4, &ValidationTestInputParser::ParseFloat),
+ DATA_TYPE("[d]", 8, &ValidationTestInputParser::ParseDouble),
+ DATA_TYPE("[dist4]", 4, &ValidationTestInputParser::ParseDistance),
+ DATA_TYPE("[dist8]", 8, &ValidationTestInputParser::ParseDistance),
+ DATA_TYPE("[anchr]", 0, &ValidationTestInputParser::ParseAnchor),
+ DATA_TYPE("[handles]", 0, &ValidationTestInputParser::ParseHandles)};
+
+const size_t ValidationTestInputParser::kDataTypeCount =
+ sizeof(ValidationTestInputParser::kDataTypes) /
+ sizeof(ValidationTestInputParser::kDataTypes[0]);
+
+ValidationTestInputParser::ValidationTestInputParser(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message)
+ : input_(input),
+ input_cursor_(0),
+ data_(data),
+ num_handles_(num_handles),
+ error_message_(error_message) {
+ assert(data_);
+ assert(num_handles_);
+ assert(error_message_);
+ data_->clear();
+ *num_handles_ = 0;
+ error_message_->clear();
+}
+
+ValidationTestInputParser::~ValidationTestInputParser() {
+}
+
+bool ValidationTestInputParser::Run() {
+ Range range;
+ bool result = true;
+ while (result && GetNextItem(&range))
+ result = ParseItem(range);
+
+ if (!result) {
+ *error_message_ =
+ "Error occurred when parsing " + std::string(range.first, range.second);
+ } else if (!pending_distance_items_.empty()) {
+ // We have parsed all the contents in |input_| successfully, but there are
+ // unmatched dist4/8 items.
+ *error_message_ = "Error occurred when matching [dist4/8] and [anchr].";
+ result = false;
+ }
+ if (!result) {
+ data_->clear();
+ *num_handles_ = 0;
+ } else {
+ assert(error_message_->empty());
+ }
+
+ return result;
+}
+
+bool ValidationTestInputParser::GetNextItem(Range* range) {
+ const char kWhitespaceChars[] = " \t\n\r";
+ const char kItemDelimiters[] = " \t\n\r/";
+ const char kEndOfLineChars[] = "\n\r";
+ while (true) {
+ // Skip leading whitespaces.
+ // If there are no non-whitespace characters left, |input_cursor_| will be
+ // set to std::npos.
+ input_cursor_ = input_.find_first_not_of(kWhitespaceChars, input_cursor_);
+
+ if (input_cursor_ >= input_.size())
+ return false;
+
+ if (StartsWith(
+ Range(&input_[0] + input_cursor_, &input_[0] + input_.size()),
+ "//",
+ 2)) {
+ // Skip contents until the end of the line.
+ input_cursor_ = input_.find_first_of(kEndOfLineChars, input_cursor_);
+ } else {
+ range->first = &input_[0] + input_cursor_;
+ input_cursor_ = input_.find_first_of(kItemDelimiters, input_cursor_);
+ range->second = input_cursor_ >= input_.size()
+ ? &input_[0] + input_.size()
+ : &input_[0] + input_cursor_;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ValidationTestInputParser::ParseItem(const Range& range) {
+ for (size_t i = 0; i < kDataTypeCount; ++i) {
+ if (StartsWith(range, kDataTypes[i].name, kDataTypes[i].name_size)) {
+ return (this->*kDataTypes[i].parse_data_func)(
+ kDataTypes[i],
+ std::string(range.first + kDataTypes[i].name_size, range.second));
+ }
+ }
+
+ // "[u1]" is optional.
+ return ParseUnsignedInteger(kDataTypes[0],
+ std::string(range.first, range.second));
+}
+
+bool ValidationTestInputParser::ParseUnsignedInteger(
+ const DataType& type,
+ const std::string& value_string) {
+ unsigned long long int value;
+ if (!ConvertToUnsignedInteger(value_string, &value))
+ return false;
+
+ switch (type.data_size) {
+ case 1:
+ return ConvertAndAppendData<uint8_t>(value);
+ case 2:
+ return ConvertAndAppendData<uint16_t>(value);
+ case 4:
+ return ConvertAndAppendData<uint32_t>(value);
+ case 8:
+ return ConvertAndAppendData<uint64_t>(value);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool ValidationTestInputParser::ParseSignedInteger(
+ const DataType& type,
+ const std::string& value_string) {
+ long long int value;
+ if (sscanf(value_string.c_str(), "%lli", &value) != 1)
+ return false;
+
+ switch (type.data_size) {
+ case 1:
+ return ConvertAndAppendData<int8_t>(value);
+ case 2:
+ return ConvertAndAppendData<int16_t>(value);
+ case 4:
+ return ConvertAndAppendData<int32_t>(value);
+ case 8:
+ return ConvertAndAppendData<int64_t>(value);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool ValidationTestInputParser::ParseFloat(const DataType& type,
+ const std::string& value_string) {
+ static_assert(sizeof(float) == 4, "sizeof(float) is not 4");
+
+ float value;
+ if (sscanf(value_string.c_str(), "%f", &value) != 1)
+ return false;
+
+ AppendData(value);
+ return true;
+}
+
+bool ValidationTestInputParser::ParseDouble(const DataType& type,
+ const std::string& value_string) {
+ static_assert(sizeof(double) == 8, "sizeof(double) is not 8");
+
+ double value;
+ if (sscanf(value_string.c_str(), "%lf", &value) != 1)
+ return false;
+
+ AppendData(value);
+ return true;
+}
+
+bool ValidationTestInputParser::ParseBinarySequence(
+ const DataType& type,
+ const std::string& value_string) {
+ if (value_string.size() != 8)
+ return false;
+
+ uint8_t value = 0;
+ for (std::string::const_iterator iter = value_string.begin();
+ iter != value_string.end();
+ ++iter) {
+ value <<= 1;
+ if (*iter == '1')
+ value++;
+ else if (*iter != '0')
+ return false;
+ }
+ AppendData(value);
+ return true;
+}
+
+bool ValidationTestInputParser::ParseDistance(const DataType& type,
+ const std::string& value_string) {
+ if (pending_distance_items_.find(value_string) !=
+ pending_distance_items_.end())
+ return false;
+
+ PendingDistanceItem item = {data_->size(), type.data_size};
+ data_->resize(data_->size() + type.data_size);
+ pending_distance_items_[value_string] = item;
+
+ return true;
+}
+
+bool ValidationTestInputParser::ParseAnchor(const DataType& type,
+ const std::string& value_string) {
+ if (anchors_.find(value_string) != anchors_.end())
+ return false;
+ anchors_.insert(value_string);
+
+ std::map<std::string, PendingDistanceItem>::iterator iter =
+ pending_distance_items_.find(value_string);
+ if (iter == pending_distance_items_.end())
+ return false;
+
+ PendingDistanceItem dist_item = iter->second;
+ pending_distance_items_.erase(iter);
+
+ size_t distance = data_->size() - dist_item.pos;
+ switch (dist_item.data_size) {
+ case 4:
+ return ConvertAndFillData<uint32_t>(dist_item.pos, distance);
+ case 8:
+ return ConvertAndFillData<uint64_t>(dist_item.pos, distance);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool ValidationTestInputParser::ParseHandles(const DataType& type,
+ const std::string& value_string) {
+ // It should be the first item.
+ if (!data_->empty())
+ return false;
+
+ unsigned long long int value;
+ if (!ConvertToUnsignedInteger(value_string, &value))
+ return false;
+
+ if (value > std::numeric_limits<size_t>::max())
+ return false;
+
+ *num_handles_ = static_cast<size_t>(value);
+ return true;
+}
+
+bool ValidationTestInputParser::StartsWith(const Range& range,
+ const char* prefix,
+ size_t prefix_length) {
+ if (static_cast<size_t>(range.second - range.first) < prefix_length)
+ return false;
+
+ return memcmp(range.first, prefix, prefix_length) == 0;
+}
+
+bool ValidationTestInputParser::ConvertToUnsignedInteger(
+ const std::string& value_string,
+ unsigned long long int* value) {
+ const char* format = nullptr;
+ if (value_string.find_first_of("xX") != std::string::npos)
+ format = "%llx";
+ else
+ format = "%llu";
+ return sscanf(value_string.c_str(), format, value) == 1;
+}
+
+} // namespace
+
+bool ParseValidationTestInput(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message) {
+ ValidationTestInputParser parser(input, data, num_handles, error_message);
+ return parser.Run();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.h b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h
new file mode 100644
index 0000000000..d08f359c17
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+namespace mojo {
+namespace test {
+
+// Input Format of Mojo Message Validation Tests.
+//
+// Data items are separated by whitespaces:
+// - ' ' (0x20) space;
+// - '\t' (0x09) horizontal tab;
+// - '\n' (0x0a) newline;
+// - '\r' (0x0d) carriage return.
+// A comment starts with //, extending to the end of the line.
+// Each data item is of the format [<type>]<value>. The types defined and the
+// corresponding value formats are described below.
+//
+// Type: u1 / u2 / u4 / u8
+// Description: Little-endian 1/2/4/8-byte unsigned integer.
+// Value Format:
+// - Decimal integer: 0|[1-9][0-9]*
+// - Hexadecimal integer: 0[xX][0-9a-fA-F]+
+// - The type prefix (including the square brackets) of 1-byte unsigned
+// integer is optional.
+//
+// Type: s1 / s2 / s4 / s8
+// Description: Little-endian 1/2/4/8-byte signed integer.
+// Value Format:
+// - Decimal integer: [-+]?(0|[1-9][0-9]*)
+// - Hexadecimal integer: [-+]?0[xX][0-9a-fA-F]+
+//
+// Type: b
+// Description: Binary sequence of 1 byte.
+// Value Format: [01]{8}
+//
+// Type: f / d
+// Description: Little-endian IEEE-754 format of float (4 bytes) and double (8
+// bytes).
+// Value Format: [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?
+//
+// Type: dist4 / dist8
+// Description: Little-endian 4/8-byte unsigned integer. The actual value is set
+// to the byte distance from the location of this integer to the location of the
+// anchr item with the same ID. A dist8 and anchr pair can be used to easily
+// represent an encoded pointer. A dist4 and anchr pair can be used to easily
+// calculate struct/array size.
+// Value Format: The value is an ID: [0-9a-zA-Z_]+
+//
+// Type: anchr
+// Description: Mark an anchor location. It doesn’t translate into any actual
+// data.
+// Value Format: The value is an ID of the same format as that of dist4/8.
+//
+// Type: handles
+// Description: The number of handles that are associated with the message. This
+// special item is not part of the message data. If specified, it should be the
+// first item.
+// Value Format: The same format as u1/2/4/8.
+//
+// EXAMPLE:
+//
+// Suppose you have the following Mojo types defined:
+// struct Bar {
+// int32_t a;
+// bool b;
+// bool c;
+// };
+// struct Foo {
+// Bar x;
+// uint32_t y;
+// };
+//
+// The following describes a valid message whose payload is a Foo struct:
+// // message header
+// [dist4]message_header // num_bytes
+// [u4]3 // version
+// [u4]0 // type
+// [u4]1 // flags
+// [u8]1234 // request_id
+// [anchr]message_header
+//
+// // payload
+// [dist4]foo // num_bytes
+// [u4]2 // version
+// [dist8]bar_ptr // x
+// [u4]0xABCD // y
+// [u4]0 // padding
+// [anchr]foo
+//
+// [anchr]bar_ptr
+// [dist4]bar // num_bytes
+// [u4]3 // version
+// [s4]-1 // a
+// [b]00000010 // b and c
+// 0 0 0 // padding
+// [anchr]bar
+
+// Parses validation test input.
+// On success, |data| and |num_handles| store the parsing result,
+// |error_message| is cleared; on failure, |error_message| is set to a message
+// describing the error, |data| is cleared and |num_handles| set to 0.
+// Note: For now, this method only works on little-endian platforms.
+bool ParseValidationTestInput(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message);
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_
diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc
new file mode 100644
index 0000000000..7af7396d4e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc
@@ -0,0 +1,498 @@
+// Copyright 2014 The Chromium Authors. 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 <stdio.h>
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/message_header_validator.h"
+#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+template <typename T>
+void Append(std::vector<uint8_t>* data_vector, T data) {
+ size_t pos = data_vector->size();
+ data_vector->resize(pos + sizeof(T));
+ memcpy(&(*data_vector)[pos], &data, sizeof(T));
+}
+
+bool TestInputParser(const std::string& input,
+ bool expected_result,
+ const std::vector<uint8_t>& expected_data,
+ size_t expected_num_handles) {
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ std::string error_message;
+
+ bool result =
+ ParseValidationTestInput(input, &data, &num_handles, &error_message);
+ if (expected_result) {
+ if (result && error_message.empty() && expected_data == data &&
+ expected_num_handles == num_handles) {
+ return true;
+ }
+
+ // Compare with an empty string instead of checking |error_message.empty()|,
+ // so that the message will be printed out if the two are not equal.
+ EXPECT_EQ(std::string(), error_message);
+ EXPECT_EQ(expected_data, data);
+ EXPECT_EQ(expected_num_handles, num_handles);
+ return false;
+ }
+
+ EXPECT_FALSE(error_message.empty());
+ return !result && !error_message.empty();
+}
+
+std::vector<std::string> GetMatchingTests(const std::vector<std::string>& names,
+ const std::string& prefix) {
+ const std::string suffix = ".data";
+ std::vector<std::string> tests;
+ for (size_t i = 0; i < names.size(); ++i) {
+ if (names[i].size() >= suffix.size() &&
+ names[i].substr(0, prefix.size()) == prefix &&
+ names[i].substr(names[i].size() - suffix.size()) == suffix)
+ tests.push_back(names[i].substr(0, names[i].size() - suffix.size()));
+ }
+ return tests;
+}
+
+bool ReadFile(const std::string& path, std::string* result) {
+ FILE* fp = OpenSourceRootRelativeFile(path.c_str());
+ if (!fp) {
+ ADD_FAILURE() << "File not found: " << path;
+ return false;
+ }
+ fseek(fp, 0, SEEK_END);
+ size_t size = static_cast<size_t>(ftell(fp));
+ if (size == 0) {
+ result->clear();
+ fclose(fp);
+ return true;
+ }
+ fseek(fp, 0, SEEK_SET);
+ result->resize(size);
+ size_t size_read = fread(&result->at(0), 1, size, fp);
+ fclose(fp);
+ return size == size_read;
+}
+
+bool ReadAndParseDataFile(const std::string& path,
+ std::vector<uint8_t>* data,
+ size_t* num_handles) {
+ std::string input;
+ if (!ReadFile(path, &input))
+ return false;
+
+ std::string error_message;
+ if (!ParseValidationTestInput(input, data, num_handles, &error_message)) {
+ ADD_FAILURE() << error_message;
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadResultFile(const std::string& path, std::string* result) {
+ if (!ReadFile(path, result))
+ return false;
+
+ // Result files are new-line delimited text files. Remove any CRs.
+ result->erase(std::remove(result->begin(), result->end(), '\r'),
+ result->end());
+
+ // Remove trailing LFs.
+ size_t pos = result->find_last_not_of('\n');
+ if (pos == std::string::npos)
+ result->clear();
+ else
+ result->resize(pos + 1);
+
+ return true;
+}
+
+std::string GetPath(const std::string& root, const std::string& suffix) {
+ return "mojo/public/interfaces/bindings/tests/data/validation/" + root +
+ suffix;
+}
+
+// |message| should be a newly created object.
+bool ReadTestCase(const std::string& test,
+ Message* message,
+ std::string* expected) {
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) ||
+ !ReadResultFile(GetPath(test, ".expected"), expected)) {
+ return false;
+ }
+
+ message->Initialize(static_cast<uint32_t>(data.size()),
+ false /* zero_initialized */);
+ if (!data.empty())
+ memcpy(message->mutable_data(), &data[0], data.size());
+ message->mutable_handles()->resize(num_handles);
+
+ return true;
+}
+
+void RunValidationTests(const std::string& prefix,
+ MessageReceiver* test_message_receiver) {
+ std::vector<std::string> names =
+ EnumerateSourceRootRelativeDirectory(GetPath("", ""));
+ std::vector<std::string> tests = GetMatchingTests(names, prefix);
+ ASSERT_FALSE(tests.empty());
+
+ for (size_t i = 0; i < tests.size(); ++i) {
+ Message message;
+ std::string expected;
+ ASSERT_TRUE(ReadTestCase(tests[i], &message, &expected));
+
+ std::string result;
+ base::RunLoop run_loop;
+ mojo::internal::ValidationErrorObserverForTesting observer(
+ run_loop.QuitClosure());
+ ignore_result(test_message_receiver->Accept(&message));
+ if (expected != "PASS") // Observer only gets called on errors.
+ run_loop.Run();
+ if (observer.last_error() == mojo::internal::VALIDATION_ERROR_NONE)
+ result = "PASS";
+ else
+ result = mojo::internal::ValidationErrorToString(observer.last_error());
+
+ EXPECT_EQ(expected, result) << "failed test: " << tests[i];
+ }
+}
+
+class DummyMessageReceiver : public MessageReceiver {
+ public:
+ bool Accept(Message* message) override {
+ return true; // Any message is OK.
+ }
+};
+
+class ValidationTest : public testing::Test {
+ public:
+ ValidationTest() {}
+
+ protected:
+ base::MessageLoop loop_;
+};
+
+class ValidationIntegrationTest : public ValidationTest {
+ public:
+ ValidationIntegrationTest() : test_message_receiver_(nullptr) {}
+
+ ~ValidationIntegrationTest() override {}
+
+ void SetUp() override {
+ ScopedMessagePipeHandle tester_endpoint;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateMessagePipe(nullptr, &tester_endpoint, &testee_endpoint_));
+ test_message_receiver_ =
+ new TestMessageReceiver(this, std::move(tester_endpoint));
+ }
+
+ void TearDown() override {
+ delete test_message_receiver_;
+ test_message_receiver_ = nullptr;
+
+ // Make sure that the other end receives the OnConnectionError()
+ // notification.
+ PumpMessages();
+ }
+
+ MessageReceiver* test_message_receiver() { return test_message_receiver_; }
+
+ ScopedMessagePipeHandle testee_endpoint() {
+ return std::move(testee_endpoint_);
+ }
+
+ private:
+ class TestMessageReceiver : public MessageReceiver {
+ public:
+ TestMessageReceiver(ValidationIntegrationTest* owner,
+ ScopedMessagePipeHandle handle)
+ : owner_(owner),
+ connector_(std::move(handle),
+ mojo::Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get()) {
+ connector_.set_enforce_errors_from_incoming_receiver(false);
+ }
+ ~TestMessageReceiver() override {}
+
+ bool Accept(Message* message) override {
+ return connector_.Accept(message);
+ }
+
+ public:
+ ValidationIntegrationTest* owner_;
+ mojo::Connector connector_;
+ };
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ TestMessageReceiver* test_message_receiver_;
+ ScopedMessagePipeHandle testee_endpoint_;
+};
+
+class IntegrationTestInterfaceImpl : public IntegrationTestInterface {
+ public:
+ ~IntegrationTestInterfaceImpl() override {}
+
+ void Method0(BasicStructPtr param0,
+ const Method0Callback& callback) override {
+ callback.Run(std::vector<uint8_t>());
+ }
+};
+
+TEST_F(ValidationTest, InputParser) {
+ {
+ // The parser, as well as Append() defined above, assumes that this code is
+ // running on a little-endian platform. Test whether that is true.
+ uint16_t x = 1;
+ ASSERT_EQ(1, *(reinterpret_cast<char*>(&x)));
+ }
+ {
+ // Test empty input.
+ std::string input;
+ std::vector<uint8_t> expected;
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ // Test input that only consists of comments and whitespaces.
+ std::string input = " \t // hello world \n\r \t// the answer is 42 ";
+ std::vector<uint8_t> expected;
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input =
+ "[u1]0x10// hello world !! \n\r \t [u2]65535 \n"
+ "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(0x10));
+ Append(&expected, static_cast<uint16_t>(65535));
+ Append(&expected, static_cast<uint32_t>(65536));
+ Append(&expected, static_cast<uint64_t>(0xffffffffffffffff));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint8_t>(0xff));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40";
+ std::vector<uint8_t> expected;
+ Append(&expected, -static_cast<int64_t>(0x800));
+ Append(&expected, static_cast<int8_t>(-128));
+ Append(&expected, static_cast<int16_t>(0));
+ Append(&expected, static_cast<int32_t>(-40));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(11));
+ Append(&expected, static_cast<uint8_t>(128));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[f]+.3e9 [d]-10.03";
+ std::vector<uint8_t> expected;
+ Append(&expected, +.3e9f);
+ Append(&expected, -10.03);
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint32_t>(14));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint64_t>(9));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "// This message has handles! \n[handles]50 [u8]2";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint64_t>(2));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 50));
+ }
+
+ // Test some failure cases.
+ {
+ const char* error_inputs[] = {"/ hello world",
+ "[u1]x",
+ "[u2]-1000",
+ "[u1]0x100",
+ "[s2]-0x8001",
+ "[b]1",
+ "[b]1111111k",
+ "[dist4]unmatched",
+ "[anchr]hello [dist8]hello",
+ "[dist4]a [dist4]a [anchr]a",
+ "[dist4]a [anchr]a [dist4]a [anchr]a",
+ "0 [handles]50",
+ nullptr};
+
+ for (size_t i = 0; error_inputs[i]; ++i) {
+ std::vector<uint8_t> expected;
+ if (!TestInputParser(error_inputs[i], false, expected, 0))
+ ADD_FAILURE() << "Unexpected test result for: " << error_inputs[i];
+ }
+ }
+}
+
+TEST_F(ValidationTest, Conformance) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("conformance_", &validators);
+}
+
+TEST_F(ValidationTest, AssociatedConformace) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<AssociatedConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("associated_conformance_", &validators);
+}
+
+// This test is similar to Conformance test but its goal is specifically
+// do bounds-check testing of message validation. For example we test the
+// detection of off-by-one errors in method ordinals.
+TEST_F(ValidationTest, BoundsCheck) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<BoundsCheckTestInterface::RequestValidator_>();
+
+ RunValidationTests("boundscheck_", &validators);
+}
+
+// This test is similar to the Conformance test but for responses.
+TEST_F(ValidationTest, ResponseConformance) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::ResponseValidator_>();
+
+ RunValidationTests("resp_conformance_", &validators);
+}
+
+// This test is similar to the BoundsCheck test but for responses.
+TEST_F(ValidationTest, ResponseBoundsCheck) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<BoundsCheckTestInterface::ResponseValidator_>();
+
+ RunValidationTests("resp_boundscheck_", &validators);
+}
+
+// Test that InterfacePtr<X> applies the correct validators and they don't
+// conflict with each other:
+// - MessageHeaderValidator
+// - X::ResponseValidator_
+TEST_F(ValidationIntegrationTest, InterfacePtr) {
+ IntegrationTestInterfacePtr interface_ptr = MakeProxy(
+ InterfacePtrInfo<IntegrationTestInterface>(testee_endpoint(), 0u));
+ interface_ptr.internal_state()->EnableTestingMode();
+
+ RunValidationTests("integration_intf_resp", test_message_receiver());
+ RunValidationTests("integration_msghdr", test_message_receiver());
+}
+
+// Test that Binding<X> applies the correct validators and they don't
+// conflict with each other:
+// - MessageHeaderValidator
+// - X::RequestValidator_
+TEST_F(ValidationIntegrationTest, Binding) {
+ IntegrationTestInterfaceImpl interface_impl;
+ Binding<IntegrationTestInterface> binding(
+ &interface_impl,
+ MakeRequest<IntegrationTestInterface>(testee_endpoint()));
+ binding.EnableTestingMode();
+
+ RunValidationTests("integration_intf_rqst", test_message_receiver());
+ RunValidationTests("integration_msghdr", test_message_receiver());
+}
+
+// Test pointer validation (specifically, that the encoded offset is 32-bit)
+TEST_F(ValidationTest, ValidateEncodedPointer) {
+ uint64_t offset;
+
+ offset = 0ULL;
+ EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset));
+
+ offset = 1ULL;
+ EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset));
+
+ // offset must be <= 32-bit.
+ offset = std::numeric_limits<uint32_t>::max() + 1ULL;
+ EXPECT_FALSE(mojo::internal::ValidateEncodedPointer(&offset));
+}
+
+// Tests the IsKnownEnumValue() function generated for BasicEnum.
+TEST(EnumValueValidationTest, BasicEnum) {
+ // BasicEnum can have -3,0,1,10 as possible integral values.
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-4)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(-3)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-2)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(0)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(1)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(2)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(9)));
+ // In the mojom, we represent this value as hex (0xa).
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(10)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(11)));
+}
+
+// Tests the IsKnownEnumValue() method generated for StructWithEnum.
+TEST(EnumValueValidationTest, EnumWithin) {
+ // StructWithEnum::EnumWithin can have [0,4] as possible integral values.
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(-1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(0)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(2)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(3)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(4)));
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/variant_test_util.h b/mojo/public/cpp/bindings/tests/variant_test_util.h
new file mode 100644
index 0000000000..3f6b1f1b5f
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/variant_test_util.h
@@ -0,0 +1,32 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_
+
+#include <string.h>
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+
+namespace mojo {
+namespace test {
+
+// Converts a request of Interface1 to a request of Interface0. Interface0 and
+// Interface1 are expected to be two variants of the same mojom interface.
+// In real-world use cases, users shouldn't need to worry about this. Because it
+// is rare to deal with two variants of the same interface in the same app.
+template <typename Interface0, typename Interface1>
+InterfaceRequest<Interface0> ConvertInterfaceRequest(
+ InterfaceRequest<Interface1> request) {
+ DCHECK_EQ(0, strcmp(Interface0::Name_, Interface1::Name_));
+ InterfaceRequest<Interface0> result;
+ result.Bind(request.PassMessagePipe());
+ return result;
+}
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_
diff --git a/mojo/public/cpp/bindings/tests/versioning_apptest.cc b/mojo/public/cpp/bindings/tests/versioning_apptest.cc
new file mode 100644
index 0000000000..95a89c0114
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/versioning_apptest.cc
@@ -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.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "mojo/public/interfaces/bindings/tests/versioning_test_client.mojom.h"
+#include "services/service_manager/public/cpp/application_test_base.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace mojo {
+namespace test {
+namespace versioning {
+
+class VersioningApplicationTest : public ApplicationTestBase {
+ public:
+ VersioningApplicationTest() : ApplicationTestBase() {}
+ ~VersioningApplicationTest() override {}
+
+ protected:
+ // ApplicationTestBase overrides.
+ void SetUp() override {
+ ApplicationTestBase::SetUp();
+
+ connector()->BindInterface("versioning_test_service", &database_);
+ }
+
+ HumanResourceDatabasePtr database_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VersioningApplicationTest);
+};
+
+TEST_F(VersioningApplicationTest, Struct) {
+ // The service side uses a newer version of Employee defintion.
+ // The returned struct should be truncated.
+ EmployeePtr employee(Employee::New());
+ employee->employee_id = 1;
+ employee->name = "Homer Simpson";
+ employee->department = DEPARTMENT_DEV;
+
+ database_->QueryEmployee(1, true,
+ [&employee](EmployeePtr returned_employee,
+ Array<uint8_t> returned_finger_print) {
+ EXPECT_TRUE(employee->Equals(*returned_employee));
+ EXPECT_FALSE(returned_finger_print.is_null());
+ });
+ database_.WaitForIncomingResponse();
+
+ // Passing a struct of older version to the service side works.
+ EmployeePtr new_employee(Employee::New());
+ new_employee->employee_id = 2;
+ new_employee->name = "Marge Simpson";
+ new_employee->department = DEPARTMENT_SALES;
+
+ database_->AddEmployee(new_employee.Clone(),
+ [](bool success) { EXPECT_TRUE(success); });
+ database_.WaitForIncomingResponse();
+
+ database_->QueryEmployee(
+ 2, false, [&new_employee](EmployeePtr returned_employee,
+ Array<uint8_t> returned_finger_print) {
+ EXPECT_TRUE(new_employee->Equals(*returned_employee));
+ EXPECT_TRUE(returned_finger_print.is_null());
+ });
+ database_.WaitForIncomingResponse();
+}
+
+TEST_F(VersioningApplicationTest, QueryVersion) {
+ EXPECT_EQ(0u, database_.version());
+ database_.QueryVersion([](uint32_t version) { EXPECT_EQ(1u, version); });
+ database_.WaitForIncomingResponse();
+ EXPECT_EQ(1u, database_.version());
+}
+
+TEST_F(VersioningApplicationTest, RequireVersion) {
+ EXPECT_EQ(0u, database_.version());
+
+ database_.RequireVersion(1);
+ EXPECT_EQ(1u, database_.version());
+ database_->QueryEmployee(3, false,
+ [](EmployeePtr returned_employee,
+ Array<uint8_t> returned_finger_print) {});
+ database_.WaitForIncomingResponse();
+ EXPECT_FALSE(database_.encountered_error());
+
+ // Requiring a version higher than what the service side implements will close
+ // the pipe.
+ database_.RequireVersion(3);
+ EXPECT_EQ(3u, database_.version());
+ database_->QueryEmployee(1, false,
+ [](EmployeePtr returned_employee,
+ Array<uint8_t> returned_finger_print) {});
+ database_.WaitForIncomingResponse();
+ EXPECT_TRUE(database_.encountered_error());
+}
+
+TEST_F(VersioningApplicationTest, CallNonexistentMethod) {
+ EXPECT_EQ(0u, database_.version());
+
+ Array<uint8_t> new_finger_print(128);
+ for (size_t i = 0; i < 128; ++i)
+ new_finger_print[i] = i + 13;
+
+ // Although the client side doesn't know whether the service side supports
+ // version 1, calling a version 1 method succeeds as long as the service side
+ // supports version 1.
+ database_->AttachFingerPrint(1, new_finger_print.Clone(),
+ [](bool success) { EXPECT_TRUE(success); });
+ database_.WaitForIncomingResponse();
+
+ // Calling a version 2 method (which the service side doesn't support) closes
+ // the pipe.
+ database_->ListEmployeeIds([](Array<uint64_t> ids) { EXPECT_TRUE(false); });
+ database_.WaitForIncomingResponse();
+ EXPECT_TRUE(database_.encountered_error());
+}
+
+} // namespace versioning
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/versioning_test_service.cc b/mojo/public/cpp/bindings/tests/versioning_test_service.cc
new file mode 100644
index 0000000000..313a6249e2
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/versioning_test_service.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 <stdint.h>
+
+#include <map>
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/interfaces/bindings/tests/versioning_test_service.mojom.h"
+#include "services/service_manager/public/c/main.h"
+#include "services/service_manager/public/cpp/interface_factory.h"
+#include "services/service_manager/public/cpp/service.h"
+#include "services/service_manager/public/cpp/service_runner.h"
+
+namespace mojo {
+namespace test {
+namespace versioning {
+
+struct EmployeeInfo {
+ public:
+ EmployeeInfo() {}
+
+ EmployeePtr employee;
+ Array<uint8_t> finger_print;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmployeeInfo);
+};
+
+class HumanResourceDatabaseImpl : public HumanResourceDatabase {
+ public:
+ explicit HumanResourceDatabaseImpl(
+ InterfaceRequest<HumanResourceDatabase> request)
+ : strong_binding_(this, std::move(request)) {
+ // Pretend that there is already some data in the system.
+ EmployeeInfo* info = new EmployeeInfo();
+ employees_[1] = info;
+ info->employee = Employee::New();
+ info->employee->employee_id = 1;
+ info->employee->name = "Homer Simpson";
+ info->employee->department = DEPARTMENT_DEV;
+ info->employee->birthday = Date::New();
+ info->employee->birthday->year = 1955;
+ info->employee->birthday->month = 5;
+ info->employee->birthday->day = 12;
+ info->finger_print.resize(1024);
+ for (uint32_t i = 0; i < 1024; ++i)
+ info->finger_print[i] = i;
+ }
+
+ ~HumanResourceDatabaseImpl() override {
+ for (auto iter = employees_.begin(); iter != employees_.end(); ++iter)
+ delete iter->second;
+ }
+
+ void AddEmployee(EmployeePtr employee,
+ const AddEmployeeCallback& callback) override {
+ uint64_t id = employee->employee_id;
+ if (employees_.find(id) == employees_.end())
+ employees_[id] = new EmployeeInfo();
+ employees_[id]->employee = std::move(employee);
+ callback.Run(true);
+ }
+
+ void QueryEmployee(uint64_t id,
+ bool retrieve_finger_print,
+ const QueryEmployeeCallback& callback) override {
+ if (employees_.find(id) == employees_.end()) {
+ callback.Run(nullptr, Array<uint8_t>());
+ return;
+ }
+ callback.Run(employees_[id]->employee.Clone(),
+ retrieve_finger_print ? employees_[id]->finger_print.Clone()
+ : Array<uint8_t>());
+ }
+
+ void AttachFingerPrint(uint64_t id,
+ Array<uint8_t> finger_print,
+ const AttachFingerPrintCallback& callback) override {
+ if (employees_.find(id) == employees_.end()) {
+ callback.Run(false);
+ return;
+ }
+ employees_[id]->finger_print = std::move(finger_print);
+ callback.Run(true);
+ }
+
+ private:
+ std::map<uint64_t, EmployeeInfo*> employees_;
+
+ StrongBinding<HumanResourceDatabase> strong_binding_;
+};
+
+class HumanResourceSystemServer
+ : public service_manager::Service,
+ public InterfaceFactory<HumanResourceDatabase> {
+ public:
+ HumanResourceSystemServer() {}
+
+ // service_manager::Service implementation.
+ bool OnConnect(Connection* connection) override {
+ connection->AddInterface<HumanResourceDatabase>(this);
+ return true;
+ }
+
+ // InterfaceFactory<HumanResourceDatabase> implementation.
+ void Create(Connection* connection,
+ InterfaceRequest<HumanResourceDatabase> request) override {
+ // It will be deleted automatically when the underlying pipe encounters a
+ // connection error.
+ new HumanResourceDatabaseImpl(std::move(request));
+ }
+};
+
+} // namespace versioning
+} // namespace test
+} // namespace mojo
+
+MojoResult ServiceMain(MojoHandle request) {
+ mojo::ServiceRunner runner(
+ new mojo::test::versioning::HumanResourceSystemServer());
+
+ return runner.Run(request);
+}
diff --git a/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc
new file mode 100644
index 0000000000..959d25b368
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc
@@ -0,0 +1,60 @@
+// 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 "mojo/public/cpp/bindings/lib/wtf_hash_util.h"
+
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h"
+#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/Source/wtf/HashFunctions.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using WTFHashTest = testing::Test;
+
+TEST_F(WTFHashTest, NestedStruct) {
+ // Just check that this template instantiation compiles.
+ ASSERT_EQ(::mojo::internal::Hash(
+ ::mojo::internal::kHashSeed,
+ blink::SimpleNestedStruct::New(blink::ContainsOther::New(1))),
+ ::mojo::internal::Hash(
+ ::mojo::internal::kHashSeed,
+ blink::SimpleNestedStruct::New(blink::ContainsOther::New(1))));
+}
+
+TEST_F(WTFHashTest, UnmappedNativeStruct) {
+ // Just check that this template instantiation compiles.
+ ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed,
+ blink::UnmappedNativeStruct::New()),
+ ::mojo::internal::Hash(::mojo::internal::kHashSeed,
+ blink::UnmappedNativeStruct::New()));
+}
+
+TEST_F(WTFHashTest, Enum) {
+ // Just check that this template instantiation compiles.
+
+ // Top-level.
+ ASSERT_EQ(WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash(
+ blink::TopLevelEnum::E0),
+ WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash(
+ blink::TopLevelEnum::E0));
+
+ // Nested in struct.
+ ASSERT_EQ(WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash(
+ blink::TestWTFStruct::NestedEnum::E0),
+ WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash(
+ blink::TestWTFStruct::NestedEnum::E0));
+
+ // Nested in interface.
+ ASSERT_EQ(WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash(
+ blink::TestWTF::NestedEnum::E0),
+ WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash(
+ blink::TestWTF::NestedEnum::E0));
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc
new file mode 100644
index 0000000000..dc40143168
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/tests/rect_blink.h"
+#include "mojo/public/interfaces/bindings/tests/rect.mojom-blink.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+TEST(WTFMapTest, StructKey) {
+ WTF::HashMap<blink::RectPtr, int32_t> map;
+ map.insert(blink::Rect::New(1, 2, 3, 4), 123);
+
+ blink::RectPtr key = blink::Rect::New(1, 2, 3, 4);
+ ASSERT_NE(map.end(), map.find(key));
+ ASSERT_EQ(123, map.find(key)->value);
+
+ map.erase(key);
+ ASSERT_EQ(0u, map.size());
+}
+
+TEST(WTFMapTest, TypemappedStructKey) {
+ WTF::HashMap<blink::ContainsHashablePtr, int32_t> map;
+ map.insert(blink::ContainsHashable::New(RectBlink(1, 2, 3, 4)), 123);
+
+ blink::ContainsHashablePtr key =
+ blink::ContainsHashable::New(RectBlink(1, 2, 3, 4));
+ ASSERT_NE(map.end(), map.find(key));
+ ASSERT_EQ(123, map.find(key)->value);
+
+ map.erase(key);
+ ASSERT_EQ(0u, map.size());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc
new file mode 100644
index 0000000000..363ef7cdab
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc
@@ -0,0 +1,245 @@
+// 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/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/wtf_serialization.h"
+#include "mojo/public/cpp/bindings/tests/variant_test_util.h"
+#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h"
+#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/Source/wtf/text/StringHash.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+const char kHelloWorld[] = "hello world";
+
+// Replace the "o"s in "hello world" with "o"s with acute.
+const char kUTF8HelloWorld[] = "hell\xC3\xB3 w\xC3\xB3rld";
+
+class TestWTFImpl : public TestWTF {
+ public:
+ explicit TestWTFImpl(TestWTFRequest request)
+ : binding_(this, std::move(request)) {}
+
+ // mojo::test::TestWTF implementation:
+ void EchoString(const base::Optional<std::string>& str,
+ const EchoStringCallback& callback) override {
+ callback.Run(str);
+ }
+
+ void EchoStringArray(
+ const base::Optional<std::vector<base::Optional<std::string>>>& arr,
+ const EchoStringArrayCallback& callback) override {
+ callback.Run(std::move(arr));
+ }
+
+ void EchoStringMap(
+ const base::Optional<
+ std::unordered_map<std::string, base::Optional<std::string>>>&
+ str_map,
+ const EchoStringMapCallback& callback) override {
+ callback.Run(std::move(str_map));
+ }
+
+ private:
+ Binding<TestWTF> binding_;
+};
+
+class WTFTypesTest : public testing::Test {
+ public:
+ WTFTypesTest() {}
+
+ private:
+ base::MessageLoop loop_;
+};
+
+WTF::Vector<WTF::String> ConstructStringArray() {
+ WTF::Vector<WTF::String> strs(4);
+ // strs[0] is null.
+ // strs[1] is empty.
+ strs[1] = "";
+ strs[2] = kHelloWorld;
+ strs[3] = WTF::String::fromUTF8(kUTF8HelloWorld);
+
+ return strs;
+}
+
+WTF::HashMap<WTF::String, WTF::String> ConstructStringMap() {
+ WTF::HashMap<WTF::String, WTF::String> str_map;
+ // A null string as value.
+ str_map.insert("0", WTF::String());
+ str_map.insert("1", kHelloWorld);
+ str_map.insert("2", WTF::String::fromUTF8(kUTF8HelloWorld));
+
+ return str_map;
+}
+
+void ExpectString(const WTF::String& expected_string,
+ const base::Closure& closure,
+ const WTF::String& string) {
+ EXPECT_EQ(expected_string, string);
+ closure.Run();
+}
+
+void ExpectStringArray(WTF::Optional<WTF::Vector<WTF::String>>* expected_arr,
+ const base::Closure& closure,
+ const WTF::Optional<WTF::Vector<WTF::String>>& arr) {
+ EXPECT_EQ(*expected_arr, arr);
+ closure.Run();
+}
+
+void ExpectStringMap(
+ WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>* expected_map,
+ const base::Closure& closure,
+ const WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>& map) {
+ EXPECT_EQ(*expected_map, map);
+ closure.Run();
+}
+
+} // namespace
+
+TEST_F(WTFTypesTest, Serialization_WTFVectorToWTFVector) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ WTF::Vector<WTF::String> strs = ConstructStringArray();
+ auto cloned_strs = strs;
+
+ mojo::internal::SerializationContext context;
+ size_t size =
+ mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data,
+ &validate_params, &context);
+
+ WTF::Vector<WTF::String> strs2;
+ mojo::internal::Deserialize<MojomType>(data, &strs2, &context);
+
+ EXPECT_EQ(strs, strs2);
+}
+
+TEST_F(WTFTypesTest, Serialization_WTFVectorToStlVector) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ WTF::Vector<WTF::String> strs = ConstructStringArray();
+ auto cloned_strs = strs;
+
+ mojo::internal::SerializationContext context;
+ size_t size =
+ mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data,
+ &validate_params, &context);
+
+ std::vector<base::Optional<std::string>> strs2;
+ mojo::internal::Deserialize<MojomType>(data, &strs2, &context);
+
+ ASSERT_EQ(4u, strs2.size());
+ EXPECT_FALSE(strs2[0]);
+ EXPECT_EQ("", *strs2[1]);
+ EXPECT_EQ(kHelloWorld, *strs2[2]);
+ EXPECT_EQ(kUTF8HelloWorld, *strs2[3]);
+}
+
+TEST_F(WTFTypesTest, Serialization_PublicAPI) {
+ blink::TestWTFStructPtr input(blink::TestWTFStruct::New(kHelloWorld, 42));
+
+ blink::TestWTFStructPtr cloned_input = input.Clone();
+
+ auto data = blink::TestWTFStruct::Serialize(&input);
+
+ blink::TestWTFStructPtr output;
+ ASSERT_TRUE(blink::TestWTFStruct::Deserialize(std::move(data), &output));
+ EXPECT_TRUE(cloned_input.Equals(output));
+}
+
+TEST_F(WTFTypesTest, SendString) {
+ blink::TestWTFPtr ptr;
+ TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr)));
+
+ WTF::Vector<WTF::String> strs = ConstructStringArray();
+
+ for (size_t i = 0; i < strs.size(); ++i) {
+ base::RunLoop loop;
+ // Test that a WTF::String is unchanged after the following conversion:
+ // - serialized;
+ // - deserialized as base::Optional<std::string>;
+ // - serialized;
+ // - deserialized as WTF::String.
+ ptr->EchoString(strs[i],
+ base::Bind(&ExpectString, strs[i], loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(WTFTypesTest, SendStringArray) {
+ blink::TestWTFPtr ptr;
+ TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr)));
+
+ WTF::Optional<WTF::Vector<WTF::String>> arrs[3];
+ // arrs[0] is empty.
+ arrs[0].emplace();
+ // arrs[1] is null.
+ arrs[2] = ConstructStringArray();
+
+ for (size_t i = 0; i < arraysize(arrs); ++i) {
+ base::RunLoop loop;
+ // Test that a WTF::Optional<WTF::Vector<WTF::String>> is unchanged after
+ // the following conversion:
+ // - serialized;
+ // - deserialized as
+ // base::Optional<std::vector<base::Optional<std::string>>>;
+ // - serialized;
+ // - deserialized as WTF::Optional<WTF::Vector<WTF::String>>.
+ ptr->EchoStringArray(
+ arrs[i], base::Bind(&ExpectStringArray, base::Unretained(&arrs[i]),
+ loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+TEST_F(WTFTypesTest, SendStringMap) {
+ blink::TestWTFPtr ptr;
+ TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr)));
+
+ WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> maps[3];
+ // maps[0] is empty.
+ maps[0].emplace();
+ // maps[1] is null.
+ maps[2] = ConstructStringMap();
+
+ for (size_t i = 0; i < arraysize(maps); ++i) {
+ base::RunLoop loop;
+ // Test that a WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> is
+ // unchanged after the following conversion:
+ // - serialized;
+ // - deserialized as base::Optional<
+ // std::unordered_map<std::string, base::Optional<std::string>>>;
+ // - serialized;
+ // - deserialized as WTF::Optional<WTF::HashMap<WTF::String,
+ // WTF::String>>.
+ ptr->EchoStringMap(maps[i],
+ base::Bind(&ExpectStringMap, base::Unretained(&maps[i]),
+ loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
new file mode 100644
index 0000000000..740687f379
--- /dev/null
+++ b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
@@ -0,0 +1,394 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_THREAD_SAFE_INTERFACE_PTR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_THREAD_SAFE_INTERFACE_PTR_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+// ThreadSafeInterfacePtr wraps a non-thread-safe InterfacePtr and proxies
+// messages to it. Async calls are posted to the thread that the InteracePtr is
+// bound to, and the responses are posted back. Sync calls are dispatched
+// directly if the call is made on the thread that the wrapped InterfacePtr is
+// bound to, or posted otherwise. It's important to be aware that sync calls
+// block both the calling thread and the InterfacePtr thread. That means that
+// you cannot make sync calls through a ThreadSafeInterfacePtr if the
+// underlying InterfacePtr is bound to a thread that cannot block, like the IO
+// thread.
+
+namespace mojo {
+
+// Instances of this class may be used from any thread to serialize |Interface|
+// messages and forward them elsewhere. In general you should use one of the
+// ThreadSafeInterfacePtrBase helper aliases defined below, but this type may be
+// useful if you need/want to manually manage the lifetime of the underlying
+// proxy object which will be used to ultimately send messages.
+template <typename Interface>
+class ThreadSafeForwarder : public MessageReceiverWithResponder {
+ public:
+ using ProxyType = typename Interface::Proxy_;
+ using ForwardMessageCallback = base::Callback<void(Message)>;
+ using ForwardMessageWithResponderCallback =
+ base::Callback<void(Message, std::unique_ptr<MessageReceiver>)>;
+
+ // Constructs a ThreadSafeForwarder through which Messages are forwarded to
+ // |forward| or |forward_with_responder| by posting to |task_runner|.
+ //
+ // Any message sent through this forwarding interface will dispatch its reply,
+ // if any, back to the thread which called the corresponding interface method.
+ ThreadSafeForwarder(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ const ForwardMessageCallback& forward,
+ const ForwardMessageWithResponderCallback& forward_with_responder,
+ const AssociatedGroup& associated_group)
+ : proxy_(this),
+ task_runner_(task_runner),
+ forward_(forward),
+ forward_with_responder_(forward_with_responder),
+ associated_group_(associated_group),
+ sync_calls_(new InProgressSyncCalls()) {}
+
+ ~ThreadSafeForwarder() override {
+ // If there are ongoing sync calls signal their completion now.
+ base::AutoLock l(sync_calls_->lock);
+ for (const auto& pending_response : sync_calls_->pending_responses)
+ pending_response->event.Signal();
+ }
+
+ ProxyType& proxy() { return proxy_; }
+
+ private:
+ // MessageReceiverWithResponder implementation:
+ bool Accept(Message* message) override {
+ if (!message->associated_endpoint_handles()->empty()) {
+ // If this DCHECK fails, it is likely because:
+ // - This is a non-associated interface pointer setup using
+ // PtrWrapper::BindOnTaskRunner(
+ // InterfacePtrInfo<InterfaceType> ptr_info);
+ // Please see the TODO in that method.
+ // - This is an associated interface which hasn't been associated with a
+ // message pipe. In other words, the corresponding
+ // AssociatedInterfaceRequest hasn't been sent.
+ DCHECK(associated_group_.GetController());
+ message->SerializeAssociatedEndpointHandles(
+ associated_group_.GetController());
+ }
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(forward_, base::Passed(message)));
+ return true;
+ }
+
+ bool AcceptWithResponder(
+ Message* message,
+ std::unique_ptr<MessageReceiver> responder) override {
+ if (!message->associated_endpoint_handles()->empty()) {
+ // Please see comment for the DCHECK in the previous method.
+ DCHECK(associated_group_.GetController());
+ message->SerializeAssociatedEndpointHandles(
+ associated_group_.GetController());
+ }
+
+ // Async messages are always posted (even if |task_runner_| runs tasks on
+ // this thread) to guarantee that two async calls can't be reordered.
+ if (!message->has_flag(Message::kFlagIsSync)) {
+ auto reply_forwarder =
+ base::MakeUnique<ForwardToCallingThread>(std::move(responder));
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message),
+ base::Passed(&reply_forwarder)));
+ return true;
+ }
+
+ SyncCallRestrictions::AssertSyncCallAllowed();
+
+ // If the InterfacePtr is bound to this thread, dispatch it directly.
+ if (task_runner_->RunsTasksOnCurrentThread()) {
+ forward_with_responder_.Run(std::move(*message), std::move(responder));
+ return true;
+ }
+
+ // If the InterfacePtr is bound on another thread, post the call.
+ // TODO(yzshen, watk): We block both this thread and the InterfacePtr
+ // thread. Ideally only this thread would block.
+ auto response = make_scoped_refptr(new SyncResponseInfo());
+ auto response_signaler = base::MakeUnique<SyncResponseSignaler>(response);
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message),
+ base::Passed(&response_signaler)));
+
+ // Save the pending SyncResponseInfo so that if the sync call deletes
+ // |this|, we can signal the completion of the call to return from
+ // SyncWatch().
+ auto sync_calls = sync_calls_;
+ {
+ base::AutoLock l(sync_calls->lock);
+ sync_calls->pending_responses.push_back(response.get());
+ }
+
+ auto assign_true = [](bool* b) { *b = true; };
+ bool event_signaled = false;
+ SyncEventWatcher watcher(&response->event,
+ base::Bind(assign_true, &event_signaled));
+ watcher.SyncWatch(&event_signaled);
+
+ {
+ base::AutoLock l(sync_calls->lock);
+ base::Erase(sync_calls->pending_responses, response.get());
+ }
+
+ if (response->received)
+ ignore_result(responder->Accept(&response->message));
+
+ return true;
+ }
+
+ // Data that we need to share between the threads involved in a sync call.
+ struct SyncResponseInfo
+ : public base::RefCountedThreadSafe<SyncResponseInfo> {
+ Message message;
+ bool received = false;
+ base::WaitableEvent event{base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+ private:
+ friend class base::RefCountedThreadSafe<SyncResponseInfo>;
+ };
+
+ // A MessageReceiver that signals |response| when it either accepts the
+ // response message, or is destructed.
+ class SyncResponseSignaler : public MessageReceiver {
+ public:
+ explicit SyncResponseSignaler(scoped_refptr<SyncResponseInfo> response)
+ : response_(response) {}
+
+ ~SyncResponseSignaler() override {
+ // If Accept() was not called we must still notify the waiter that the
+ // sync call is finished.
+ if (response_)
+ response_->event.Signal();
+ }
+
+ bool Accept(Message* message) {
+ response_->message = std::move(*message);
+ response_->received = true;
+ response_->event.Signal();
+ response_ = nullptr;
+ return true;
+ }
+
+ private:
+ scoped_refptr<SyncResponseInfo> response_;
+ };
+
+ // A record of the pending sync responses for canceling pending sync calls
+ // when the owning ThreadSafeForwarder is destructed.
+ struct InProgressSyncCalls
+ : public base::RefCountedThreadSafe<InProgressSyncCalls> {
+ // |lock| protects access to |pending_responses|.
+ base::Lock lock;
+ std::vector<SyncResponseInfo*> pending_responses;
+ };
+
+ class ForwardToCallingThread : public MessageReceiver {
+ public:
+ explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder)
+ : responder_(std::move(responder)),
+ caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+
+ private:
+ bool Accept(Message* message) {
+ // The current instance will be deleted when this method returns, so we
+ // have to relinquish the responder's ownership so it does not get
+ // deleted.
+ caller_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ForwardToCallingThread::CallAcceptAndDeleteResponder,
+ base::Passed(std::move(responder_)),
+ base::Passed(std::move(*message))));
+ return true;
+ }
+
+ static void CallAcceptAndDeleteResponder(
+ std::unique_ptr<MessageReceiver> responder,
+ Message message) {
+ ignore_result(responder->Accept(&message));
+ }
+
+ std::unique_ptr<MessageReceiver> responder_;
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
+ };
+
+ ProxyType proxy_;
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ const ForwardMessageCallback forward_;
+ const ForwardMessageWithResponderCallback forward_with_responder_;
+ AssociatedGroup associated_group_;
+ scoped_refptr<InProgressSyncCalls> sync_calls_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadSafeForwarder);
+};
+
+template <typename InterfacePtrType>
+class ThreadSafeInterfacePtrBase
+ : public base::RefCountedThreadSafe<
+ ThreadSafeInterfacePtrBase<InterfacePtrType>> {
+ public:
+ using InterfaceType = typename InterfacePtrType::InterfaceType;
+ using PtrInfoType = typename InterfacePtrType::PtrInfoType;
+
+ explicit ThreadSafeInterfacePtrBase(
+ std::unique_ptr<ThreadSafeForwarder<InterfaceType>> forwarder)
+ : forwarder_(std::move(forwarder)) {}
+
+ // Creates a ThreadSafeInterfacePtrBase wrapping an underlying non-thread-safe
+ // InterfacePtrType which is bound to the calling thread. All messages sent
+ // via this thread-safe proxy will internally be sent by first posting to this
+ // (the calling) thread's TaskRunner.
+ static scoped_refptr<ThreadSafeInterfacePtrBase> Create(
+ InterfacePtrType interface_ptr) {
+ scoped_refptr<PtrWrapper> wrapper =
+ new PtrWrapper(std::move(interface_ptr));
+ return new ThreadSafeInterfacePtrBase(wrapper->CreateForwarder());
+ }
+
+ // Creates a ThreadSafeInterfacePtrBase which binds the underlying
+ // non-thread-safe InterfacePtrType on the specified TaskRunner. All messages
+ // sent via this thread-safe proxy will internally be sent by first posting to
+ // that TaskRunner.
+ static scoped_refptr<ThreadSafeInterfacePtrBase> Create(
+ PtrInfoType ptr_info,
+ const scoped_refptr<base::SingleThreadTaskRunner>& bind_task_runner) {
+ scoped_refptr<PtrWrapper> wrapper = new PtrWrapper(bind_task_runner);
+ wrapper->BindOnTaskRunner(std::move(ptr_info));
+ return new ThreadSafeInterfacePtrBase(wrapper->CreateForwarder());
+ }
+
+ InterfaceType* get() { return &forwarder_->proxy(); }
+ InterfaceType* operator->() { return get(); }
+ InterfaceType& operator*() { return *get(); }
+
+ private:
+ friend class base::RefCountedThreadSafe<
+ ThreadSafeInterfacePtrBase<InterfacePtrType>>;
+
+ struct PtrWrapperDeleter;
+
+ // Helper class which owns an |InterfacePtrType| instance on an appropriate
+ // thread. This is kept alive as long its bound within some
+ // ThreadSafeForwarder's callbacks.
+ class PtrWrapper
+ : public base::RefCountedThreadSafe<PtrWrapper, PtrWrapperDeleter> {
+ public:
+ explicit PtrWrapper(InterfacePtrType ptr)
+ : PtrWrapper(base::ThreadTaskRunnerHandle::Get()) {
+ ptr_ = std::move(ptr);
+ associated_group_ = *ptr_.internal_state()->associated_group();
+ }
+
+ explicit PtrWrapper(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ : task_runner_(task_runner) {}
+
+ void BindOnTaskRunner(AssociatedInterfacePtrInfo<InterfaceType> ptr_info) {
+ associated_group_ = AssociatedGroup(ptr_info.handle());
+ task_runner_->PostTask(FROM_HERE, base::Bind(&PtrWrapper::Bind, this,
+ base::Passed(&ptr_info)));
+ }
+
+ void BindOnTaskRunner(InterfacePtrInfo<InterfaceType> ptr_info) {
+ // TODO(yzhsen): At the momment we don't have a group controller
+ // available. That means the user won't be able to pass associated
+ // endpoints on this interface (at least not immediately). In order to fix
+ // this, we need to create a MultiplexRouter immediately and bind it to
+ // the interface pointer on the |task_runner_|. Therefore, MultiplexRouter
+ // should be able to be created on a thread different than the one that it
+ // is supposed to listen on. crbug.com/682334
+ task_runner_->PostTask(FROM_HERE, base::Bind(&PtrWrapper::Bind, this,
+ base::Passed(&ptr_info)));
+ }
+
+ std::unique_ptr<ThreadSafeForwarder<InterfaceType>> CreateForwarder() {
+ return base::MakeUnique<ThreadSafeForwarder<InterfaceType>>(
+ task_runner_, base::Bind(&PtrWrapper::Accept, this),
+ base::Bind(&PtrWrapper::AcceptWithResponder, this),
+ associated_group_);
+ }
+
+ private:
+ friend struct PtrWrapperDeleter;
+
+ ~PtrWrapper() {}
+
+ void Bind(PtrInfoType ptr_info) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ ptr_.Bind(std::move(ptr_info));
+ }
+
+ void Accept(Message message) {
+ ptr_.internal_state()->ForwardMessage(std::move(message));
+ }
+
+ void AcceptWithResponder(Message message,
+ std::unique_ptr<MessageReceiver> responder) {
+ ptr_.internal_state()->ForwardMessageWithResponder(std::move(message),
+ std::move(responder));
+ }
+
+ void DeleteOnCorrectThread() const {
+ if (!task_runner_->RunsTasksOnCurrentThread()) {
+ // NOTE: This is only called when there are no more references to
+ // |this|, so binding it unretained is both safe and necessary.
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&PtrWrapper::DeleteOnCorrectThread,
+ base::Unretained(this)));
+ } else {
+ delete this;
+ }
+ }
+
+ InterfacePtrType ptr_;
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ AssociatedGroup associated_group_;
+
+ DISALLOW_COPY_AND_ASSIGN(PtrWrapper);
+ };
+
+ struct PtrWrapperDeleter {
+ static void Destruct(const PtrWrapper* interface_ptr) {
+ interface_ptr->DeleteOnCorrectThread();
+ }
+ };
+
+ ~ThreadSafeInterfacePtrBase() {}
+
+ const std::unique_ptr<ThreadSafeForwarder<InterfaceType>> forwarder_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadSafeInterfacePtrBase);
+};
+
+template <typename Interface>
+using ThreadSafeAssociatedInterfacePtr =
+ ThreadSafeInterfacePtrBase<AssociatedInterfacePtr<Interface>>;
+
+template <typename Interface>
+using ThreadSafeInterfacePtr =
+ ThreadSafeInterfacePtrBase<InterfacePtr<Interface>>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_THREAD_SAFE_INTERFACE_PTR_H_
diff --git a/mojo/public/cpp/bindings/type_converter.h b/mojo/public/cpp/bindings/type_converter.h
new file mode 100644
index 0000000000..395eeb4ffe
--- /dev/null
+++ b/mojo/public/cpp/bindings/type_converter.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 MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+namespace mojo {
+
+// NOTE: TypeConverter is deprecated. Please consider StructTraits /
+// UnionTraits / EnumTraits / ArrayTraits / MapTraits / StringTraits if you
+// would like to convert between custom types and the wire format of mojom
+// types.
+//
+// Specialize the following class:
+// template <typename T, typename U> struct TypeConverter;
+// to perform type conversion for Mojom-defined structs and arrays. Here, T is
+// the target type; U is the input type.
+//
+// Specializations should implement the following interfaces:
+// namespace mojo {
+// template <>
+// struct TypeConverter<X, Y> {
+// static X Convert(const Y& input);
+// };
+// template <>
+// struct TypeConverter<Y, X> {
+// static Y Convert(const X& input);
+// };
+// }
+//
+// EXAMPLE:
+//
+// Suppose you have the following Mojom-defined struct:
+//
+// module geometry {
+// struct Point {
+// int32_t x;
+// int32_t y;
+// };
+// }
+//
+// Now, imagine you wanted to write a TypeConverter specialization for
+// gfx::Point. It might look like this:
+//
+// namespace mojo {
+// template <>
+// struct TypeConverter<geometry::PointPtr, gfx::Point> {
+// static geometry::PointPtr Convert(const gfx::Point& input) {
+// geometry::PointPtr result;
+// result->x = input.x();
+// result->y = input.y();
+// return result;
+// }
+// };
+// template <>
+// struct TypeConverter<gfx::Point, geometry::PointPtr> {
+// static gfx::Point Convert(const geometry::PointPtr& input) {
+// return input ? gfx::Point(input->x, input->y) : gfx::Point();
+// }
+// };
+// }
+//
+// With the above TypeConverter defined, it is possible to write code like this:
+//
+// void AcceptPoint(const geometry::PointPtr& input) {
+// // With an explicit cast using the .To<> method.
+// gfx::Point pt = input.To<gfx::Point>();
+//
+// // With an explicit cast using the static From() method.
+// geometry::PointPtr output = geometry::Point::From(pt);
+//
+// // Inferring the input type using the ConvertTo helper function.
+// gfx::Point pt2 = ConvertTo<gfx::Point>(input);
+// }
+//
+template <typename T, typename U>
+struct TypeConverter;
+
+template <typename T, typename U>
+inline T ConvertTo(const U& obj);
+
+// The following specialization is useful when you are converting between
+// Array<POD> and std::vector<POD>.
+template <typename T>
+struct TypeConverter<T, T> {
+ static T Convert(const T& obj) { return obj; }
+};
+
+template <typename T, typename Container>
+struct TypeConverter<std::vector<T>, Container> {
+ static std::vector<T> Convert(const Container& container) {
+ std::vector<T> output;
+ output.reserve(container.size());
+ for (const auto& obj : container) {
+ output.push_back(ConvertTo<T>(obj));
+ }
+ return output;
+ }
+};
+
+// The following helper function is useful for shorthand. The compiler can infer
+// the input type, so you can write:
+// OutputType out = ConvertTo<OutputType>(input);
+template <typename T, typename U>
+inline T ConvertTo(const U& obj) {
+ return TypeConverter<T, U>::Convert(obj);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_
diff --git a/mojo/public/cpp/bindings/union_traits.h b/mojo/public/cpp/bindings/union_traits.h
new file mode 100644
index 0000000000..292ee58f27
--- /dev/null
+++ b/mojo/public/cpp/bindings/union_traits.h
@@ -0,0 +1,39 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_
+
+namespace mojo {
+
+// This must be specialized for any type |T| to be serialized/deserialized as
+// a mojom union. |DataViewType| is the corresponding data view type of the
+// mojom union. For example, if the mojom union is example.Foo, |DataViewType|
+// will be example::FooDataView, which can also be referred to by
+// example::Foo::DataView (in chromium) and example::blink::Foo::DataView (in
+// blink).
+//
+// Similar to StructTraits, each specialization of UnionTraits implements the
+// following methods:
+// 1. Getters for each field in the Mojom type.
+// 2. Read() method.
+// 3. [Optional] IsNull() and SetToNull().
+// 4. [Optional] SetUpContext() and TearDownContext().
+// Please see the documentation of StructTraits for details of these methods.
+//
+// Unlike StructTraits, there is one more method to implement:
+// 5. A static GetTag() method indicating which field is the current active
+// field for serialization:
+//
+// static DataViewType::Tag GetTag(const T& input);
+//
+// During serialization, only the field getter corresponding to this tag
+// will be called.
+//
+template <typename DataViewType, typename T>
+struct UnionTraits;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h b/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h
new file mode 100644
index 0000000000..f1ac097396
--- /dev/null
+++ b/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_UNIQUE_PTR_IMPL_REF_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_UNIQUE_PTR_IMPL_REF_TRAITS_H_
+
+namespace mojo {
+
+// Traits for a binding's implementation reference type.
+// This corresponds to a unique_ptr reference type.
+template <typename Interface>
+struct UniquePtrImplRefTraits {
+ using PointerType = std::unique_ptr<Interface>;
+
+ static bool IsNull(const PointerType& ptr) { return !ptr; }
+ static Interface* GetRawPointer(PointerType* ptr) { return ptr->get(); }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_UNIQUE_PTR_IMPL_REF_TRAITS_H_
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn
new file mode 100644
index 0000000000..35087ef6f1
--- /dev/null
+++ b/mojo/public/cpp/system/BUILD.gn
@@ -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.
+
+# Deletes libsystem.dylib from the build dir, since it shadows
+# /usr/lib/libSystem.dylib on macOS.
+# TODO(thakis): Remove this after a while.
+action("clean_up_old_dylib") {
+ script = "//build/rm.py"
+ stamp = "$target_gen_dir/clean_up_stamp"
+ outputs = [
+ stamp,
+ ]
+ args = [
+ "--stamp",
+ rebase_path(stamp, root_build_dir),
+ "-f",
+ "libsystem.dylib",
+ ]
+}
+
+component("system") {
+ output_name = "mojo_public_system_cpp"
+
+ sources = [
+ "buffer.cc",
+ "buffer.h",
+ "core.h",
+ "data_pipe.h",
+ "functions.h",
+ "handle.h",
+ "handle_signals_state.h",
+ "message.h",
+ "message_pipe.h",
+ "platform_handle.cc",
+ "platform_handle.h",
+ "simple_watcher.cc",
+ "simple_watcher.h",
+ "system_export.h",
+ "wait.cc",
+ "wait.h",
+ "wait_set.cc",
+ "wait_set.h",
+ "watcher.cc",
+ "watcher.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//mojo/public/c/system",
+ ]
+ deps = [
+ ":clean_up_old_dylib",
+ ]
+
+ defines = [ "MOJO_CPP_SYSTEM_IMPLEMENTATION" ]
+}
diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md
new file mode 100644
index 0000000000..782744f0b1
--- /dev/null
+++ b/mojo/public/cpp/system/README.md
@@ -0,0 +1,396 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ System API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+The Mojo C++ System API provides a convenient set of helper classes and
+functions for working with Mojo primitives. Unlike the low-level
+[C API](/mojo/public/c/system) (upon which this is built) this library takes
+advantage of C++ language features and common STL and `//base` types to provide
+a slightly more idiomatic interface to the Mojo system layer, making it
+generally easier to use.
+
+This document provides a brief guide to API usage with example code snippets.
+For a detailed API references please consult the headers in
+[//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/).
+
+Note that all API symbols referenced in this document are implicitly in the
+top-level `mojo` namespace.
+
+## Scoped, Typed Handles
+
+All types of Mojo handles in the C API are simply opaque, integral `MojoHandle`
+values. The C++ API has more strongly typed wrappers defined for different
+handle types: `MessagePipeHandle`, `SharedBufferHandle`,
+`DataPipeConsumerHandle`, `DataPipeProducerHandle`, and `WatcherHandle`.
+
+Each of these also has a corresponding, move-only, scoped type for safer usage:
+`ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped
+handle type is destroyed, its handle is automatically closed via `MojoClose`.
+When working with raw handles you should **always** prefer to use one of the
+scoped types for ownership.
+
+Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get
+at the underlying unscoped handle type as well as the `->` operator to
+dereference the scoper and make calls directly on the underlying handle type.
+
+## Message Pipes
+
+There are two ways to create a new message pipe using the C++ API. You may
+construct a `MessagePipe` object:
+
+``` cpp
+mojo::MessagePipe pipe;
+
+// NOTE: Because pipes are bi-directional there is no implicit semantic
+// difference between |handle0| or |handle1| here. They're just two ends of a
+// pipe. The choice to treat one as a "client" and one as a "server" is entirely
+// a the API user's decision.
+mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0);
+mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1);
+```
+
+or you may call `CreateMessagePipe`:
+
+``` cpp
+mojo::ScopedMessagePipeHandle client;
+mojo::ScopedMessagePipeHandle server;
+mojo::CreateMessagePipe(nullptr, &client, &server);
+```
+
+There are also some helper functions for constructing message objects and
+reading/writing them on pipes using the library's more strongly-typed C++
+handles:
+
+``` cpp
+mojo::ScopedMessageHandle message;
+mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message);
+
+void *buffer;
+mojo::GetMessageBuffer(message.get(), &buffer);
+
+const std::string kMessage = "hello";
+std::copy(kMessage.begin(), kMessage.end(), static_cast<char*>(buffer));
+
+mojo::WriteMessageNew(client.get(), std::move(message),
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+
+// Some time later...
+
+mojo::ScopedMessageHandle received_message;
+uint32_t num_bytes;
+mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h)
+for detailed C++ message pipe API documentation.
+
+## Data Pipes
+
+Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple
+helpers for more strongly-typed data pipe usage:
+
+``` cpp
+mojo::DataPipe pipe;
+mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer);
+mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer);
+
+// Or alternatively:
+mojo::ScopedDataPipeProducerHandle producer;
+mojo::ScopedDataPipeConsumerHandle consumer;
+mojo::CreateDataPipe(null, &producer, &consumer);
+```
+
+// Reads from a data pipe. See |MojoReadData()| for complete documentation.
+inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
+ void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) {
+ return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags);
+}
+
+// Begins a two-phase read
+C++ helpers which correspond directly to the
+[Data Pipe C API](/mojo/public/c/system#Data-Pipes) for immediate and two-phase
+I/O are provided as well. For example:
+
+``` cpp
+uint32_t num_bytes = 7;
+mojo::WriteDataRaw(producer.get(), "hihihi",
+ &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
+
+// Some time later...
+
+char buffer[64];
+uint32_t num_bytes = 64;
+mojo::ReadDataRaw(consumer.get(), buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+```
+
+See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h)
+for detailed C++ data pipe API documentation.
+
+## Shared Buffers
+
+A new shared buffers can be allocated like so:
+
+``` cpp
+mojo::ScopedSharedBufferHandle buffer =
+ mojo::ScopedSharedBufferHandle::Create(4096);
+```
+
+This new handle can be cloned arbitrarily many times by using the underlying
+handle's `Clone` method:
+
+``` cpp
+mojo::ScopedSharedBufferHandle another_handle = buffer->Clone();
+mojo::ScopedSharedBufferHandle read_only_handle =
+ buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY);
+```
+
+And finally the library also provides a scoper for mapping the shared buffer's
+memory:
+
+``` cpp
+mojo::ScopedSharedBufferMapping mapping = buffer->Map(64);
+static_cast<int*>(mapping.get()) = 42;
+
+mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4);
+static_cast<int*>(mapping.get()) = 43;
+```
+
+When `mapping` and `another_mapping` are destroyed, they automatically unmap
+their respective memory regions.
+
+See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h)
+for detailed C++ shared buffer API documentation.
+
+## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
+
+The C++ library provides several helpers for wrapping system handle types.
+These are specifically useful when working with a few `//base` types, namely
+`base::PlatformFile` and `base::SharedMemoryHandle`. See
+[platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h)
+for detailed C++ platform handle API documentation.
+
+## Signals & Watchers
+
+For an introduction to the concepts of handle signals and watchers, check out
+the C API's documentation on [Signals & Watchers](/mojo/public/c/system#Signals-Watchers).
+
+### Querying Signals
+
+Any C++ handle type's last known signaling state can be queried by calling the
+`QuerySignalsState` method on the handle:
+
+``` cpp
+mojo::MessagePipe message_pipe;
+mojo::DataPipe data_pipe;
+mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState();
+mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState();
+```
+
+The `HandleSignalsState` is a thin wrapper interface around the C API's
+`MojoHandleSignalsState` structure with convenient accessors for testing
+the signal bitmasks. Whereas when using the C API you might write:
+
+``` c
+struct MojoHandleSignalsState state;
+MojoQueryHandleSignalsState(handle0, &state);
+if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) {
+ // ...
+}
+```
+
+the C++ API equivalent would be:
+
+``` cpp
+if (message_pipe.handle0->QuerySignalsState().readable()) {
+ // ...
+}
+```
+
+### Watching Handles
+
+The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h)
+class serves as a convenient helper for using the [low-level watcher API](/mojo/public/c/system#Signals-Watchers)
+to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a
+single thread and always dispatches its notifications on a
+`base::SingleThreadTaskRunner`.
+
+`SimpleWatcher` has two possible modes of operation, selected at construction
+time by the `mojo::SimpleWatcher::ArmingPolicy` enum:
+
+* `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify`
+ before any notifications will fire regarding the state of the watched handle.
+ Every time the notification callback is run, the `SimpleWatcher` must be
+ rearmed again before the next one can fire. See
+ [Arming a Watcher](/mojo/public/c/system#Arming-a-Watcher) and the
+ documentation in `SimpleWatcher`'s header.
+
+* `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or
+ has a pending notification task queued for execution.
+
+`AUTOMATIC` mode is more convenient but can result in redundant notification
+tasks, especially if the provided callback does not make a strong effort to
+return the watched handle to an uninteresting signaling state (by *e.g.*,
+reading all its available messages when notified of readability.)
+
+Example usage:
+
+``` cpp
+class PipeReader {
+ public:
+ PipeReader(mojo::ScopedMessagePipeHandle pipe)
+ : pipe_(std::move(pipe)),
+ watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {
+ // NOTE: base::Unretained is safe because the callback can never be run
+ // after SimpleWatcher destruction.
+ watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&PipeReader::OnReadable, base::Unretained(this)));
+ }
+
+ ~PipeReader() {}
+
+ private:
+ void OnReadable(MojoResult result) {
+ while (result == MOJO_RESULT_OK) {
+ mojo::ScopedMessageHandle message;
+ uint32_t num_bytes;
+ result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+ DCHECK_EQ(result, MOJO_RESULT_OK);
+ messages_.emplace_back(std::move(message));
+ }
+ }
+
+ mojo::ScopedMessagePipeHandle pipe_;
+ mojo::SimpleWatcher watcher_;
+ std::vector<mojo::ScopedMessageHandle> messages_;
+};
+
+mojo::MessagePipe pipe;
+PipeReader reader(std::move(pipe.handle0));
+
+// Written messages will asynchronously end up in |reader.messages_|.
+WriteABunchOfStuff(pipe.handle1.get());
+```
+
+## Synchronous Waiting
+
+The C++ System API defines some utilities to block a calling thread while
+waiting for one or more handles to change signaling state in an interesting way.
+These threads combine usage of the [low-level Watcher API](/mojo/public/c/system#Signals-Watchers)
+with common synchronization primitives (namely `base::WaitableEvent`.)
+
+While these API features should be used sparingly, they are sometimes necessary.
+
+See the documentation in
+[wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h)
+and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
+for a more detailed API reference.
+
+### Waiting On a Single Handle
+
+The `mojo::Wait` function simply blocks the calling thread until a given signal
+mask is either partially satisfied or fully unsatisfiable on a given handle.
+
+``` cpp
+mojo::MessagePipe pipe;
+mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+DCHECK_EQ(result, MOJO_RESULT_OK);
+
+// Guaranteed to succeed because we know |handle1| is readable now.
+mojo::ScopedMessageHandle message;
+uint32_t num_bytes;
+mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+`mojo::Wait` is most typically useful in limited testing scenarios.
+
+### Waiting On Multiple Handles
+
+`mojo::WaitMany` provides a simple API to wait on multiple handles
+simultaneously, returning when any handle's given signal mask is either
+partially satisfied or fully unsatisfiable.
+
+``` cpp
+mojo::MessagePipe a, b;
+GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1));
+
+mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()};
+MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE};
+size_t ready_index;
+MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index);
+if (ready_index == 0) {
+ // a.handle0 was ready.
+} else {
+ // b.handle0 was ready.
+}
+```
+
+Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When
+waiting on multiple handles in production code, you should almost always instead
+use a more efficient and more flexible `mojo::WaitSet` as described in the next
+section.
+
+### Waiting On Handles and Events Simultaneously
+
+Typically when waiting on one or more handles to signal, the set of handles and
+conditions being waited upon do not change much between consecutive blocking
+waits. It's also often useful to be able to interrupt the blocking operation
+as efficiently as possible.
+
+[`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
+is designed with these conditions in mind. A `WaitSet` maintains a persistent
+set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be
+explicitly added to or removed from the set at any time.
+
+The `WaitSet` may be waited upon repeatedly, each time blocking the calling
+thread until either one of the handles attains an interesting signaling state or
+one of the events is signaled. For example let's suppose we want to wait up to 5
+seconds for either one of two handles to become readable:
+
+``` cpp
+base::WaitableEvent timeout_event(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+mojo::MessagePipe a, b;
+
+GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1));
+
+mojo::WaitSet wait_set;
+wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+wait_set.AddEvent(&timeout_event);
+
+// Ensure the Wait() lasts no more than 5 seconds.
+bg_thread->task_runner()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event);
+ base::TimeDelta::FromSeconds(5));
+
+base::WaitableEvent* ready_event = nullptr;
+size_t num_ready_handles = 1;
+mojo::Handle ready_handle;
+MojoResult ready_result;
+wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+
+// The apex of thread-safety.
+bg_thread->Stop();
+
+if (ready_event) {
+ // The event signaled...
+}
+
+if (num_ready_handles > 0) {
+ // At least one of the handles signaled...
+ // NOTE: This and the above condition are not mutually exclusive. If handle
+ // signaling races with timeout, both things might be true.
+}
+```
diff --git a/mojo/public/cpp/system/buffer.cc b/mojo/public/cpp/system/buffer.cc
new file mode 100644
index 0000000000..49f45d8498
--- /dev/null
+++ b/mojo/public/cpp/system/buffer.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 "mojo/public/cpp/system/buffer.h"
+
+namespace mojo {
+
+// static
+ScopedSharedBufferHandle SharedBufferHandle::Create(uint64_t num_bytes) {
+ MojoCreateSharedBufferOptions options = {
+ sizeof(options), MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE};
+ SharedBufferHandle handle;
+ MojoCreateSharedBuffer(&options, num_bytes, handle.mutable_value());
+ return MakeScopedHandle(handle);
+}
+
+ScopedSharedBufferHandle SharedBufferHandle::Clone(
+ SharedBufferHandle::AccessMode access_mode) const {
+ ScopedSharedBufferHandle result;
+ if (!is_valid())
+ return result;
+
+ MojoDuplicateBufferHandleOptions options = {
+ sizeof(options), MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE};
+ if (access_mode == AccessMode::READ_ONLY)
+ options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
+ SharedBufferHandle result_handle;
+ MojoDuplicateBufferHandle(value(), &options, result_handle.mutable_value());
+ result.reset(result_handle);
+ return result;
+}
+
+ScopedSharedBufferMapping SharedBufferHandle::Map(uint64_t size) const {
+ return MapAtOffset(size, 0);
+}
+
+ScopedSharedBufferMapping SharedBufferHandle::MapAtOffset(
+ uint64_t size,
+ uint64_t offset) const {
+ void* buffer = nullptr;
+ MojoMapBuffer(value(), offset, size, &buffer, MOJO_MAP_BUFFER_FLAG_NONE);
+ return ScopedSharedBufferMapping(buffer);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/buffer.h b/mojo/public/cpp/system/buffer.h
new file mode 100644
index 0000000000..1ae923cb75
--- /dev/null
+++ b/mojo/public/cpp/system/buffer.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.
+
+// This file provides a C++ wrapping around the Mojo C API for shared buffers,
+// replacing the prefix of "Mojo" with a "mojo" namespace, and using more
+// strongly-typed representations of |MojoHandle|s.
+//
+// Please see "mojo/public/c/system/buffer.h" for complete documentation of the
+// API.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+namespace internal {
+
+struct Unmapper {
+ void operator()(void* buffer) {
+ MojoResult result = MojoUnmapBuffer(buffer);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ }
+};
+
+} // namespace internal
+
+using ScopedSharedBufferMapping = std::unique_ptr<void, internal::Unmapper>;
+
+class SharedBufferHandle;
+
+typedef ScopedHandleBase<SharedBufferHandle> ScopedSharedBufferHandle;
+
+// A strongly-typed representation of a |MojoHandle| referring to a shared
+// buffer.
+class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle
+ : NON_EXPORTED_BASE(public Handle) {
+ public:
+ enum class AccessMode {
+ READ_WRITE,
+ READ_ONLY,
+ };
+
+ SharedBufferHandle() {}
+ explicit SharedBufferHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+
+ // Creates a new SharedBufferHandle. Returns an invalid handle on failure.
+ static ScopedSharedBufferHandle Create(uint64_t num_bytes);
+
+ // Clones this shared buffer handle. If |access_mode| is READ_ONLY or this is
+ // a read-only handle, the new handle will be read-only. On failure, this will
+ // return an empty result.
+ ScopedSharedBufferHandle Clone(AccessMode = AccessMode::READ_WRITE) const;
+
+ // Maps |size| bytes of this shared buffer. On failure, this will return a
+ // null mapping.
+ ScopedSharedBufferMapping Map(uint64_t size) const;
+
+ // Maps |size| bytes of this shared buffer, starting |offset| bytes into the
+ // buffer. On failure, this will return a null mapping.
+ ScopedSharedBufferMapping MapAtOffset(uint64_t size, uint64_t offset) const;
+};
+
+static_assert(sizeof(SharedBufferHandle) == sizeof(Handle),
+ "Bad size for C++ SharedBufferHandle");
+static_assert(sizeof(ScopedSharedBufferHandle) == sizeof(SharedBufferHandle),
+ "Bad size for C++ ScopedSharedBufferHandle");
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_
diff --git a/mojo/public/cpp/system/core.h b/mojo/public/cpp/system/core.h
new file mode 100644
index 0000000000..f1d18d977f
--- /dev/null
+++ b/mojo/public/cpp/system/core.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_CORE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_CORE_H_
+
+#include "mojo/public/cpp/system/buffer.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/functions.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_CORE_H_
diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h
new file mode 100644
index 0000000000..0dbc3c74e5
--- /dev/null
+++ b/mojo/public/cpp/system/data_pipe.h
@@ -0,0 +1,163 @@
+// Copyright 2014 The Chromium 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 C++ wrapping around the Mojo C API for data pipes,
+// replacing the prefix of "Mojo" with a "mojo" namespace, and using more
+// strongly-typed representations of |MojoHandle|s.
+//
+// Please see "mojo/public/c/system/data_pipe.h" for complete documentation of
+// the API.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/cpp/system/handle.h"
+
+namespace mojo {
+
+// A strongly-typed representation of a |MojoHandle| to the producer end of a
+// data pipe.
+class DataPipeProducerHandle : public Handle {
+ public:
+ DataPipeProducerHandle() {}
+ explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+};
+
+static_assert(sizeof(DataPipeProducerHandle) == sizeof(Handle),
+ "Bad size for C++ DataPipeProducerHandle");
+
+typedef ScopedHandleBase<DataPipeProducerHandle> ScopedDataPipeProducerHandle;
+static_assert(sizeof(ScopedDataPipeProducerHandle) ==
+ sizeof(DataPipeProducerHandle),
+ "Bad size for C++ ScopedDataPipeProducerHandle");
+
+// A strongly-typed representation of a |MojoHandle| to the consumer end of a
+// data pipe.
+class DataPipeConsumerHandle : public Handle {
+ public:
+ DataPipeConsumerHandle() {}
+ explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+};
+
+static_assert(sizeof(DataPipeConsumerHandle) == sizeof(Handle),
+ "Bad size for C++ DataPipeConsumerHandle");
+
+typedef ScopedHandleBase<DataPipeConsumerHandle> ScopedDataPipeConsumerHandle;
+static_assert(sizeof(ScopedDataPipeConsumerHandle) ==
+ sizeof(DataPipeConsumerHandle),
+ "Bad size for C++ ScopedDataPipeConsumerHandle");
+
+// Creates a new data pipe. See |MojoCreateDataPipe()| for complete
+// documentation.
+inline MojoResult CreateDataPipe(
+ const MojoCreateDataPipeOptions* options,
+ ScopedDataPipeProducerHandle* data_pipe_producer,
+ ScopedDataPipeConsumerHandle* data_pipe_consumer) {
+ DCHECK(data_pipe_producer);
+ DCHECK(data_pipe_consumer);
+ DataPipeProducerHandle producer_handle;
+ DataPipeConsumerHandle consumer_handle;
+ MojoResult rv = MojoCreateDataPipe(options,
+ producer_handle.mutable_value(),
+ consumer_handle.mutable_value());
+ // Reset even on failure (reduces the chances that a "stale"/incorrect handle
+ // will be used).
+ data_pipe_producer->reset(producer_handle);
+ data_pipe_consumer->reset(consumer_handle);
+ return rv;
+}
+
+// Writes to a data pipe. See |MojoWriteData| for complete documentation.
+inline MojoResult WriteDataRaw(DataPipeProducerHandle data_pipe_producer,
+ const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags) {
+ return MojoWriteData(data_pipe_producer.value(), elements, num_bytes, flags);
+}
+
+// Begins a two-phase write to a data pipe. See |MojoBeginWriteData()| for
+// complete documentation.
+inline MojoResult BeginWriteDataRaw(DataPipeProducerHandle data_pipe_producer,
+ void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags) {
+ return MojoBeginWriteData(
+ data_pipe_producer.value(), buffer, buffer_num_bytes, flags);
+}
+
+// Completes a two-phase write to a data pipe. See |MojoEndWriteData()| for
+// complete documentation.
+inline MojoResult EndWriteDataRaw(DataPipeProducerHandle data_pipe_producer,
+ uint32_t num_bytes_written) {
+ return MojoEndWriteData(data_pipe_producer.value(), num_bytes_written);
+}
+
+// Reads from a data pipe. See |MojoReadData()| for complete documentation.
+inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
+ void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) {
+ return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags);
+}
+
+// Begins a two-phase read from a data pipe. See |MojoBeginReadData()| for
+// complete documentation.
+inline MojoResult BeginReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
+ const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags) {
+ return MojoBeginReadData(
+ data_pipe_consumer.value(), buffer, buffer_num_bytes, flags);
+}
+
+// Completes a two-phase read from a data pipe. See |MojoEndReadData()| for
+// complete documentation.
+inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
+ uint32_t num_bytes_read) {
+ return MojoEndReadData(data_pipe_consumer.value(), num_bytes_read);
+}
+
+// A wrapper class that automatically creates a data pipe and owns both handles.
+// TODO(vtl): Make an even more friendly version? (Maybe templatized for a
+// particular type instead of some "element"? Maybe functions that take
+// vectors?)
+class DataPipe {
+ public:
+ DataPipe();
+ explicit DataPipe(const MojoCreateDataPipeOptions& options);
+ ~DataPipe();
+
+ ScopedDataPipeProducerHandle producer_handle;
+ ScopedDataPipeConsumerHandle consumer_handle;
+};
+
+inline DataPipe::DataPipe() {
+ MojoResult result =
+ CreateDataPipe(nullptr, &producer_handle, &consumer_handle);
+ ALLOW_UNUSED_LOCAL(result);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+}
+
+inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) {
+ MojoResult result =
+ CreateDataPipe(&options, &producer_handle, &consumer_handle);
+ ALLOW_UNUSED_LOCAL(result);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+}
+
+inline DataPipe::~DataPipe() {
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_
diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h
new file mode 100644
index 0000000000..31edf57ab5
--- /dev/null
+++ b/mojo/public/cpp/system/functions.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.
+
+// This file provides a C++ wrapping around the standalone functions of the Mojo
+// C API, replacing the prefix of "Mojo" with a "mojo" namespace.
+//
+// Please see "mojo/public/c/system/functions.h" for complete documentation of
+// the API.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_
+
+#include "mojo/public/c/system/functions.h"
+
+namespace mojo {
+
+// Returns the current |MojoTimeTicks| value. See |MojoGetTimeTicksNow()| for
+// complete documentation.
+inline MojoTimeTicks GetTimeTicksNow() {
+ return MojoGetTimeTicksNow();
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_
diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h
new file mode 100644
index 0000000000..781944eb76
--- /dev/null
+++ b/mojo/public/cpp/system/handle.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_
+
+#include <stdint.h>
+#include <limits>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
+
+namespace mojo {
+
+// OVERVIEW
+//
+// |Handle| and |...Handle|:
+//
+// |Handle| is a simple, copyable wrapper for the C type |MojoHandle| (which is
+// just an integer). Its purpose is to increase type-safety, not provide
+// lifetime management. For the same purpose, we have trivial *subclasses* of
+// |Handle|, e.g., |MessagePipeHandle| and |DataPipeProducerHandle|. |Handle|
+// and its subclasses impose *no* extra overhead over using |MojoHandle|s
+// directly.
+//
+// Note that though we provide constructors for |Handle|/|...Handle| from a
+// |MojoHandle|, we do not provide, e.g., a constructor for |MessagePipeHandle|
+// from a |Handle|. This is for type safety: If we did, you'd then be able to
+// construct a |MessagePipeHandle| from, e.g., a |DataPipeProducerHandle| (since
+// it's a |Handle|).
+//
+// |ScopedHandleBase| and |Scoped...Handle|:
+//
+// |ScopedHandleBase<HandleType>| is a templated scoped wrapper, for the handle
+// types above (in the same sense that a C++11 |unique_ptr<T>| is a scoped
+// wrapper for a |T*|). It provides lifetime management, closing its owned
+// handle on destruction. It also provides (emulated) move semantics, again
+// along the lines of C++11's |unique_ptr| (and exactly like Chromium's
+// |scoped_ptr|).
+//
+// |ScopedHandle| is just (a typedef of) a |ScopedHandleBase<Handle>|.
+// Similarly, |ScopedMessagePipeHandle| is just a
+// |ScopedHandleBase<MessagePipeHandle>|. Etc. Note that a
+// |ScopedMessagePipeHandle| is *not* a (subclass of) |ScopedHandle|.
+//
+// Wrapper functions:
+//
+// We provide simple wrappers for the |Mojo...()| functions (in
+// mojo/public/c/system/core.h -- see that file for details on individual
+// functions).
+//
+// The general guideline is functions that imply ownership transfer of a handle
+// should take (or produce) an appropriate |Scoped...Handle|, while those that
+// don't take a |...Handle|. For example, |CreateMessagePipe()| has two
+// |ScopedMessagePipe| "out" parameters, whereas |Wait()| and |WaitMany()| take
+// |Handle| parameters. Some, have both: e.g., |DuplicatedBuffer()| takes a
+// suitable (unscoped) handle (e.g., |SharedBufferHandle|) "in" parameter and
+// produces a suitable scoped handle (e.g., |ScopedSharedBufferHandle| a.k.a.
+// |ScopedHandleBase<SharedBufferHandle>|) as an "out" parameter.
+//
+// An exception are some of the |...Raw()| functions. E.g., |CloseRaw()| takes a
+// |Handle|, leaving the user to discard the wrapper.
+//
+// ScopedHandleBase ------------------------------------------------------------
+
+// Scoper for the actual handle types defined further below. It's move-only,
+// like the C++11 |unique_ptr|.
+template <class HandleType>
+class ScopedHandleBase {
+ public:
+ using RawHandleType = HandleType;
+
+ ScopedHandleBase() {}
+ explicit ScopedHandleBase(HandleType handle) : handle_(handle) {}
+ ~ScopedHandleBase() { CloseIfNecessary(); }
+
+ template <class CompatibleHandleType>
+ explicit ScopedHandleBase(ScopedHandleBase<CompatibleHandleType> other)
+ : handle_(other.release()) {}
+
+ // Move-only constructor and operator=.
+ ScopedHandleBase(ScopedHandleBase&& other) : handle_(other.release()) {}
+ ScopedHandleBase& operator=(ScopedHandleBase&& other) {
+ if (&other != this) {
+ CloseIfNecessary();
+ handle_ = other.release();
+ }
+ return *this;
+ }
+
+ const HandleType& get() const { return handle_; }
+ const HandleType* operator->() const { return &handle_; }
+
+ template <typename PassedHandleType>
+ static ScopedHandleBase<HandleType> From(
+ ScopedHandleBase<PassedHandleType> other) {
+ static_assert(
+ sizeof(static_cast<PassedHandleType*>(static_cast<HandleType*>(0))),
+ "HandleType is not a subtype of PassedHandleType");
+ return ScopedHandleBase<HandleType>(
+ static_cast<HandleType>(other.release().value()));
+ }
+
+ void swap(ScopedHandleBase& other) { handle_.swap(other.handle_); }
+
+ HandleType release() WARN_UNUSED_RESULT {
+ HandleType rv;
+ rv.swap(handle_);
+ return rv;
+ }
+
+ void reset(HandleType handle = HandleType()) {
+ CloseIfNecessary();
+ handle_ = handle;
+ }
+
+ bool is_valid() const { return handle_.is_valid(); }
+
+ bool operator==(const ScopedHandleBase& other) const {
+ return handle_.value() == other.get().value();
+ }
+
+ private:
+ void CloseIfNecessary() {
+ if (handle_.is_valid())
+ handle_.Close();
+ }
+
+ HandleType handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedHandleBase);
+};
+
+template <typename HandleType>
+inline ScopedHandleBase<HandleType> MakeScopedHandle(HandleType handle) {
+ return ScopedHandleBase<HandleType>(handle);
+}
+
+// Handle ----------------------------------------------------------------------
+
+const MojoHandle kInvalidHandleValue = MOJO_HANDLE_INVALID;
+
+// Wrapper base class for |MojoHandle|.
+class Handle {
+ public:
+ Handle() : value_(kInvalidHandleValue) {}
+ explicit Handle(MojoHandle value) : value_(value) {}
+ ~Handle() {}
+
+ void swap(Handle& other) {
+ MojoHandle temp = value_;
+ value_ = other.value_;
+ other.value_ = temp;
+ }
+
+ bool is_valid() const { return value_ != kInvalidHandleValue; }
+
+ const MojoHandle& value() const { return value_; }
+ MojoHandle* mutable_value() { return &value_; }
+ void set_value(MojoHandle value) { value_ = value; }
+
+ void Close() {
+ DCHECK(is_valid());
+ MojoResult result = MojoClose(value_);
+ ALLOW_UNUSED_LOCAL(result);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ }
+
+ HandleSignalsState QuerySignalsState() const {
+ HandleSignalsState signals_state;
+ MojoResult result = MojoQueryHandleSignalsState(
+ value_, static_cast<MojoHandleSignalsState*>(&signals_state));
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ return signals_state;
+ }
+
+ private:
+ MojoHandle value_;
+
+ // Copying and assignment allowed.
+};
+
+// Should have zero overhead.
+static_assert(sizeof(Handle) == sizeof(MojoHandle), "Bad size for C++ Handle");
+
+// The scoper should also impose no more overhead.
+typedef ScopedHandleBase<Handle> ScopedHandle;
+static_assert(sizeof(ScopedHandle) == sizeof(Handle),
+ "Bad size for C++ ScopedHandle");
+
+// |Close()| takes ownership of the handle, since it'll invalidate it.
+// Note: There's nothing to do, since the argument will be destroyed when it
+// goes out of scope.
+template <class HandleType>
+inline void Close(ScopedHandleBase<HandleType> /*handle*/) {
+}
+
+// Most users should typically use |Close()| (above) instead.
+inline MojoResult CloseRaw(Handle handle) {
+ return MojoClose(handle.value());
+}
+
+// Strict weak ordering, so that |Handle|s can be used as keys in |std::map|s,
+inline bool operator<(const Handle a, const Handle b) {
+ return a.value() < b.value();
+}
+
+// Comparison, so that |Handle|s can be used as keys in hash maps.
+inline bool operator==(const Handle a, const Handle b) {
+ return a.value() == b.value();
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_
diff --git a/mojo/public/cpp/system/handle_signals_state.h b/mojo/public/cpp/system/handle_signals_state.h
new file mode 100644
index 0000000000..9e2430f15a
--- /dev/null
+++ b/mojo/public/cpp/system/handle_signals_state.h
@@ -0,0 +1,83 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_
+
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// A convenience wrapper around the MojoHandleSignalsState struct.
+struct MOJO_CPP_SYSTEM_EXPORT HandleSignalsState final
+ : public MojoHandleSignalsState {
+ HandleSignalsState() {
+ satisfied_signals = MOJO_HANDLE_SIGNAL_NONE;
+ satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE;
+ }
+
+ HandleSignalsState(MojoHandleSignals satisfied,
+ MojoHandleSignals satisfiable) {
+ satisfied_signals = satisfied;
+ satisfiable_signals = satisfiable;
+ }
+
+ bool operator==(const HandleSignalsState& other) const {
+ return satisfied_signals == other.satisfied_signals &&
+ satisfiable_signals == other.satisfiable_signals;
+ }
+
+ // TODO(rockot): Remove this in favor of operator==.
+ bool equals(const HandleSignalsState& other) const {
+ return satisfied_signals == other.satisfied_signals &&
+ satisfiable_signals == other.satisfiable_signals;
+ }
+
+ bool satisfies(MojoHandleSignals signals) const {
+ return !!(satisfied_signals & signals);
+ }
+
+ bool can_satisfy(MojoHandleSignals signals) const {
+ return !!(satisfiable_signals & signals);
+ }
+
+ // The handle is currently readable. May apply to a message pipe handle or
+ // data pipe consumer handle.
+ bool readable() const { return satisfies(MOJO_HANDLE_SIGNAL_READABLE); }
+
+ // The handle is currently writable. May apply to a message pipe handle or
+ // data pipe producer handle.
+ bool writable() const { return satisfies(MOJO_HANDLE_SIGNAL_WRITABLE); }
+
+ // The handle's peer is closed. May apply to any message pipe or data pipe
+ // handle.
+ bool peer_closed() const { return satisfies(MOJO_HANDLE_SIGNAL_PEER_CLOSED); }
+
+ // The handle will never be |readable()| again.
+ bool never_readable() const {
+ return !can_satisfy(MOJO_HANDLE_SIGNAL_READABLE);
+ }
+
+ // The handle will never be |writable()| again.
+ bool never_writable() const {
+ return !can_satisfy(MOJO_HANDLE_SIGNAL_WRITABLE);
+ }
+
+ // The handle can never indicate |peer_closed()|. Never true for message pipe
+ // or data pipe handles (they can always signal peer closure), but always true
+ // for other types of handles (they have no peer.)
+ bool never_peer_closed() const {
+ return !can_satisfy(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ }
+
+ // (Copy and assignment allowed.)
+};
+
+static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState),
+ "HandleSignalsState should add no overhead");
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_
diff --git a/mojo/public/cpp/system/message.cc b/mojo/public/cpp/system/message.cc
new file mode 100644
index 0000000000..09d8d46e6d
--- /dev/null
+++ b/mojo/public/cpp/system/message.cc
@@ -0,0 +1,13 @@
+// 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 "mojo/public/cpp/system/message.h"
+
+namespace mojo {
+
+ScopedMessageHandle::~ScopedMessageHandle() {
+
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/message.h b/mojo/public/cpp/system/message.h
new file mode 100644
index 0000000000..d4406ee808
--- /dev/null
+++ b/mojo/public/cpp/system/message.h
@@ -0,0 +1,83 @@
+// 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 MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_
+
+#include <limits>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/cpp/system/handle.h"
+
+namespace mojo {
+
+const MojoMessageHandle kInvalidMessageHandleValue =
+ MOJO_MESSAGE_HANDLE_INVALID;
+
+// Handle wrapper base class for a |MojoMessageHandle|.
+class MessageHandle {
+ public:
+ MessageHandle() : value_(kInvalidMessageHandleValue) {}
+ explicit MessageHandle(MojoMessageHandle value) : value_(value) {}
+ ~MessageHandle() {}
+
+ void swap(MessageHandle& other) {
+ MojoMessageHandle temp = value_;
+ value_ = other.value_;
+ other.value_ = temp;
+ }
+
+ bool is_valid() const { return value_ != kInvalidMessageHandleValue; }
+
+ const MojoMessageHandle& value() const { return value_; }
+ MojoMessageHandle* mutable_value() { return &value_; }
+ void set_value(MojoMessageHandle value) { value_ = value; }
+
+ void Close() {
+ DCHECK(is_valid());
+ MojoResult result = MojoFreeMessage(value_);
+ ALLOW_UNUSED_LOCAL(result);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ }
+
+ private:
+ MojoMessageHandle value_;
+};
+
+using ScopedMessageHandle = ScopedHandleBase<MessageHandle>;
+
+inline MojoResult AllocMessage(size_t num_bytes,
+ const MojoHandle* handles,
+ size_t num_handles,
+ MojoAllocMessageFlags flags,
+ ScopedMessageHandle* handle) {
+ DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max());
+ DCHECK_LE(num_handles, std::numeric_limits<uint32_t>::max());
+ MojoMessageHandle raw_handle;
+ MojoResult rv = MojoAllocMessage(static_cast<uint32_t>(num_bytes), handles,
+ static_cast<uint32_t>(num_handles), flags,
+ &raw_handle);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ handle->reset(MessageHandle(raw_handle));
+ return MOJO_RESULT_OK;
+}
+
+inline MojoResult GetMessageBuffer(MessageHandle message, void** buffer) {
+ DCHECK(message.is_valid());
+ return MojoGetMessageBuffer(message.value(), buffer);
+}
+
+inline MojoResult NotifyBadMessage(MessageHandle message,
+ const base::StringPiece& error) {
+ DCHECK(message.is_valid());
+ return MojoNotifyBadMessage(message.value(), error.data(), error.size());
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_
diff --git a/mojo/public/cpp/system/message_pipe.h b/mojo/public/cpp/system/message_pipe.h
new file mode 100644
index 0000000000..7fbe43f7f4
--- /dev/null
+++ b/mojo/public/cpp/system/message_pipe.h
@@ -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.
+
+// This file provides a C++ wrapping around the Mojo C API for message pipes,
+// replacing the prefix of "Mojo" with a "mojo" namespace, and using more
+// strongly-typed representations of |MojoHandle|s.
+//
+// Please see "mojo/public/c/system/message_pipe.h" for complete documentation
+// of the API.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/message.h"
+
+namespace mojo {
+
+// A strongly-typed representation of a |MojoHandle| to one end of a message
+// pipe.
+class MessagePipeHandle : public Handle {
+ public:
+ MessagePipeHandle() {}
+ explicit MessagePipeHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+};
+
+static_assert(sizeof(MessagePipeHandle) == sizeof(Handle),
+ "Bad size for C++ MessagePipeHandle");
+
+typedef ScopedHandleBase<MessagePipeHandle> ScopedMessagePipeHandle;
+static_assert(sizeof(ScopedMessagePipeHandle) == sizeof(MessagePipeHandle),
+ "Bad size for C++ ScopedMessagePipeHandle");
+
+// Creates a message pipe. See |MojoCreateMessagePipe()| for complete
+// documentation.
+inline MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options,
+ ScopedMessagePipeHandle* message_pipe0,
+ ScopedMessagePipeHandle* message_pipe1) {
+ DCHECK(message_pipe0);
+ DCHECK(message_pipe1);
+ MessagePipeHandle handle0;
+ MessagePipeHandle handle1;
+ MojoResult rv = MojoCreateMessagePipe(
+ options, handle0.mutable_value(), handle1.mutable_value());
+ // Reset even on failure (reduces the chances that a "stale"/incorrect handle
+ // will be used).
+ message_pipe0->reset(handle0);
+ message_pipe1->reset(handle1);
+ return rv;
+}
+
+// The following "...Raw" versions fully expose the underlying API, and don't
+// help with ownership of handles (especially when writing messages). It is
+// expected that in most cases these methods will be called through generated
+// bindings anyway.
+// TODO(vtl): Write friendlier versions of these functions (using scoped
+// handles and/or vectors) if there is a demonstrated need for them.
+
+// Writes to a message pipe. If handles are attached, on success the handles
+// will no longer be valid (the receiver will receive equivalent, but logically
+// different, handles). See |MojoWriteMessage()| for complete documentation.
+inline MojoResult WriteMessageRaw(MessagePipeHandle message_pipe,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ return MojoWriteMessage(
+ message_pipe.value(), bytes, num_bytes, handles, num_handles, flags);
+}
+
+// Reads from a message pipe. See |MojoReadMessage()| for complete
+// documentation.
+inline MojoResult ReadMessageRaw(MessagePipeHandle message_pipe,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ return MojoReadMessage(
+ message_pipe.value(), bytes, num_bytes, handles, num_handles, flags);
+}
+
+// Writes to a message pipe. Takes ownership of |message| and any attached
+// handles.
+inline MojoResult WriteMessageNew(MessagePipeHandle message_pipe,
+ ScopedMessageHandle message,
+ MojoWriteMessageFlags flags) {
+ return MojoWriteMessageNew(
+ message_pipe.value(), message.release().value(), flags);
+}
+
+// Reads from a message pipe. See |MojoReadMessageNew()| for complete
+// documentation.
+inline MojoResult ReadMessageNew(MessagePipeHandle message_pipe,
+ ScopedMessageHandle* message,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ MojoMessageHandle raw_message;
+ MojoResult rv = MojoReadMessageNew(message_pipe.value(), &raw_message,
+ num_bytes, handles, num_handles, flags);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ message->reset(MessageHandle(raw_message));
+ return MOJO_RESULT_OK;
+}
+
+// Fuses two message pipes together at the given handles. See
+// |MojoFuseMessagePipes()| for complete documentation.
+inline MojoResult FuseMessagePipes(ScopedMessagePipeHandle message_pipe0,
+ ScopedMessagePipeHandle message_pipe1) {
+ return MojoFuseMessagePipes(message_pipe0.release().value(),
+ message_pipe1.release().value());
+}
+
+// A wrapper class that automatically creates a message pipe and owns both
+// handles.
+class MessagePipe {
+ public:
+ MessagePipe();
+ explicit MessagePipe(const MojoCreateMessagePipeOptions& options);
+ ~MessagePipe();
+
+ ScopedMessagePipeHandle handle0;
+ ScopedMessagePipeHandle handle1;
+};
+
+inline MessagePipe::MessagePipe() {
+ MojoResult result = CreateMessagePipe(nullptr, &handle0, &handle1);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ DCHECK(handle0.is_valid());
+ DCHECK(handle1.is_valid());
+}
+
+inline MessagePipe::MessagePipe(const MojoCreateMessagePipeOptions& options) {
+ MojoResult result = CreateMessagePipe(&options, &handle0, &handle1);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ DCHECK(handle0.is_valid());
+ DCHECK(handle1.is_valid());
+}
+
+inline MessagePipe::~MessagePipe() {
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_
diff --git a/mojo/public/cpp/system/platform_handle.cc b/mojo/public/cpp/system/platform_handle.cc
new file mode 100644
index 0000000000..42e4abac83
--- /dev/null
+++ b/mojo/public/cpp/system/platform_handle.cc
@@ -0,0 +1,178 @@
+// 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 "mojo/public/cpp/system/platform_handle.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach.h>
+#include "base/mac/mach_logging.h"
+#endif
+
+namespace mojo {
+
+namespace {
+
+uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) {
+#if defined(OS_WIN)
+ return reinterpret_cast<uint64_t>(file);
+#else
+ return static_cast<uint64_t>(file);
+#endif
+}
+
+base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) {
+#if defined(OS_WIN)
+ return reinterpret_cast<base::PlatformFile>(value);
+#else
+ return static_cast<base::PlatformFile>(value);
+#endif
+}
+
+} // namespace
+
+ScopedHandle WrapPlatformFile(base::PlatformFile platform_file) {
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(MojoPlatformHandle);
+ platform_handle.type = kPlatformFileHandleType;
+ platform_handle.value = PlatformHandleValueFromPlatformFile(platform_file);
+
+ MojoHandle mojo_handle;
+ MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle);
+ CHECK_EQ(result, MOJO_RESULT_OK);
+
+ return ScopedHandle(Handle(mojo_handle));
+}
+
+MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file) {
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(MojoPlatformHandle);
+ MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
+ &platform_handle);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) {
+ *file = base::kInvalidPlatformFile;
+ } else {
+ CHECK_EQ(platform_handle.type, kPlatformFileHandleType);
+ *file = PlatformFileFromPlatformHandleValue(platform_handle.value);
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+ScopedSharedBufferHandle WrapSharedMemoryHandle(
+ const base::SharedMemoryHandle& memory_handle,
+ size_t size,
+ bool read_only) {
+#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS))
+ if (memory_handle.fd == base::kInvalidPlatformFile)
+ return ScopedSharedBufferHandle();
+#else
+ if (!memory_handle.IsValid())
+ return ScopedSharedBufferHandle();
+#endif
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(MojoPlatformHandle);
+ platform_handle.type = kPlatformSharedBufferHandleType;
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ platform_handle.value =
+ static_cast<uint64_t>(memory_handle.GetMemoryObject());
+#elif defined(OS_POSIX)
+ platform_handle.value = PlatformHandleValueFromPlatformFile(memory_handle.fd);
+#elif defined(OS_WIN)
+ platform_handle.value =
+ PlatformHandleValueFromPlatformFile(memory_handle.GetHandle());
+#endif
+
+ MojoPlatformSharedBufferHandleFlags flags =
+ MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE;
+ if (read_only)
+ flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY;
+
+ MojoHandle mojo_handle;
+ MojoResult result = MojoWrapPlatformSharedBufferHandle(
+ &platform_handle, size, flags, &mojo_handle);
+ CHECK_EQ(result, MOJO_RESULT_OK);
+
+ return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle));
+}
+
+MojoResult UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle,
+ base::SharedMemoryHandle* memory_handle,
+ size_t* size,
+ bool* read_only) {
+ if (!handle.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(MojoPlatformHandle);
+
+ MojoPlatformSharedBufferHandleFlags flags;
+ size_t num_bytes;
+ MojoResult result = MojoUnwrapPlatformSharedBufferHandle(
+ handle.release().value(), &platform_handle, &num_bytes, &flags);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ if (size)
+ *size = num_bytes;
+
+ if (read_only)
+ *read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT);
+ *memory_handle = base::SharedMemoryHandle(
+ static_cast<mach_port_t>(platform_handle.value), num_bytes,
+ base::GetCurrentProcId());
+#elif defined(OS_POSIX)
+ CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR);
+ *memory_handle = base::SharedMemoryHandle(
+ static_cast<int>(platform_handle.value), false);
+#elif defined(OS_WIN)
+ CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE);
+ *memory_handle = base::SharedMemoryHandle(
+ reinterpret_cast<HANDLE>(platform_handle.value),
+ base::GetCurrentProcId());
+#endif
+
+ return MOJO_RESULT_OK;
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ScopedHandle WrapMachPort(mach_port_t port) {
+ kern_return_t kr =
+ mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1);
+ MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
+ << "MachPortAttachmentMac mach_port_mod_refs";
+ if (kr != KERN_SUCCESS)
+ return ScopedHandle();
+
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(MojoPlatformHandle);
+ platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
+ platform_handle.value = static_cast<uint64_t>(port);
+
+ MojoHandle mojo_handle;
+ MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle);
+ CHECK_EQ(result, MOJO_RESULT_OK);
+
+ return ScopedHandle(Handle(mojo_handle));
+}
+
+MojoResult UnwrapMachPort(ScopedHandle handle, mach_port_t* port) {
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(MojoPlatformHandle);
+ MojoResult result =
+ MojoUnwrapPlatformHandle(handle.release().value(), &platform_handle);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT);
+ *port = static_cast<mach_port_t>(platform_handle.value);
+ return MOJO_RESULT_OK;
+}
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/platform_handle.h b/mojo/public/cpp/system/platform_handle.h
new file mode 100644
index 0000000000..801264efce
--- /dev/null
+++ b/mojo/public/cpp/system/platform_handle.h
@@ -0,0 +1,92 @@
+// 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.
+
+// This file provides a C++ wrapping around the Mojo C API for platform handles,
+// replacing the prefix of "Mojo" with a "mojo" namespace.
+//
+// Please see "mojo/public/c/system/platform_handle.h" for complete
+// documentation of the API.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/files/file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/shared_memory_handle.h"
+#include "base/process/process_handle.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/cpp/system/buffer.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace mojo {
+
+#if defined(OS_POSIX)
+const MojoPlatformHandleType kPlatformFileHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+const MojoPlatformHandleType kPlatformSharedBufferHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
+#else
+const MojoPlatformHandleType kPlatformSharedBufferHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+#elif defined(OS_WIN)
+const MojoPlatformHandleType kPlatformFileHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+
+const MojoPlatformHandleType kPlatformSharedBufferHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+#endif // defined(OS_POSIX)
+
+// Wraps a PlatformFile as a Mojo handle. Takes ownership of the file object.
+MOJO_CPP_SYSTEM_EXPORT
+ScopedHandle WrapPlatformFile(base::PlatformFile platform_file);
+
+// Unwraps a PlatformFile from a Mojo handle.
+MOJO_CPP_SYSTEM_EXPORT
+MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file);
+
+// Wraps a base::SharedMemoryHandle as a Mojo handle. Takes ownership of the
+// SharedMemoryHandle. Note that |read_only| is only an indicator of whether
+// |memory_handle| only supports read-only mapping. It does NOT have any
+// influence on the access control of the shared buffer object.
+MOJO_CPP_SYSTEM_EXPORT
+ScopedSharedBufferHandle WrapSharedMemoryHandle(
+ const base::SharedMemoryHandle& memory_handle,
+ size_t size,
+ bool read_only);
+
+// Unwraps a base::SharedMemoryHandle from a Mojo handle. The caller assumes
+// responsibility for the lifetime of the SharedMemoryHandle.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle,
+ base::SharedMemoryHandle* memory_handle,
+ size_t* size,
+ bool* read_only);
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Wraps a mach_port_t as a Mojo handle. This takes a reference to the
+// Mach port.
+MOJO_CPP_SYSTEM_EXPORT ScopedHandle WrapMachPort(mach_port_t port);
+
+// Unwraps a mach_port_t from a Mojo handle. The caller gets ownership of the
+// Mach port.
+MOJO_CPP_SYSTEM_EXPORT MojoResult UnwrapMachPort(ScopedHandle handle,
+ mach_port_t* port);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_
diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc
new file mode 100644
index 0000000000..ae96faa395
--- /dev/null
+++ b/mojo/public/cpp/system/simple_watcher.cc
@@ -0,0 +1,279 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/system/simple_watcher.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/heap_profiler.h"
+#include "mojo/public/c/system/watcher.h"
+
+namespace mojo {
+
+// Thread-safe Context object used to dispatch watch notifications from a
+// arbitrary threads.
+class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
+ public:
+ // Creates a |Context| instance for a new watch on |watcher|, to watch
+ // |handle| for |signals|.
+ static scoped_refptr<Context> Create(
+ base::WeakPtr<SimpleWatcher> watcher,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ WatcherHandle watcher_handle,
+ Handle handle,
+ MojoHandleSignals signals,
+ int watch_id,
+ MojoResult* watch_result) {
+ scoped_refptr<Context> context =
+ new Context(watcher, task_runner, watch_id);
+
+ // If MojoWatch succeeds, it assumes ownership of a reference to |context|.
+ // In that case, this reference is balanced in CallNotify() when |result| is
+ // |MOJO_RESULT_CANCELLED|.
+ context->AddRef();
+
+ *watch_result = MojoWatch(watcher_handle.value(), handle.value(), signals,
+ context->value());
+ if (*watch_result != MOJO_RESULT_OK) {
+ // Balanced by the AddRef() above since watching failed.
+ context->Release();
+ return nullptr;
+ }
+
+ return context;
+ }
+
+ static void CallNotify(uintptr_t context_value,
+ MojoResult result,
+ MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags) {
+ auto* context = reinterpret_cast<Context*>(context_value);
+ context->Notify(result, signals_state, flags);
+
+ // That was the last notification for the context. We can release the ref
+ // owned by the watch, which may in turn delete the Context.
+ if (result == MOJO_RESULT_CANCELLED)
+ context->Release();
+ }
+
+ uintptr_t value() const { return reinterpret_cast<uintptr_t>(this); }
+
+ void DisableCancellationNotifications() {
+ base::AutoLock lock(lock_);
+ enable_cancellation_notifications_ = false;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Context>;
+
+ Context(base::WeakPtr<SimpleWatcher> weak_watcher,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ int watch_id)
+ : weak_watcher_(weak_watcher),
+ task_runner_(task_runner),
+ watch_id_(watch_id) {}
+ ~Context() {}
+
+ void Notify(MojoResult result,
+ MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags) {
+ if (result == MOJO_RESULT_CANCELLED) {
+ // The SimpleWatcher may have explicitly cancelled this watch, so we don't
+ // bother dispatching the notification - it would be ignored anyway.
+ //
+ // TODO(rockot): This shouldn't really be necessary, but there are already
+ // instances today where bindings object may be bound and subsequently
+ // closed due to pipe error, all before the thread's TaskRunner has been
+ // properly initialized.
+ base::AutoLock lock(lock_);
+ if (!enable_cancellation_notifications_)
+ return;
+ }
+
+ if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) &&
+ task_runner_->RunsTasksOnCurrentThread() && weak_watcher_ &&
+ weak_watcher_->is_default_task_runner_) {
+ // System notifications will trigger from the task runner passed to
+ // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the
+ // default task runner for the IO thread.
+ weak_watcher_->OnHandleReady(watch_id_, result);
+ } else {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_,
+ watch_id_, result));
+ }
+ }
+
+ const base::WeakPtr<SimpleWatcher> weak_watcher_;
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ const int watch_id_;
+
+ base::Lock lock_;
+ bool enable_cancellation_notifications_ = true;
+
+ DISALLOW_COPY_AND_ASSIGN(Context);
+};
+
+SimpleWatcher::SimpleWatcher(const tracked_objects::Location& from_here,
+ ArmingPolicy arming_policy,
+ scoped_refptr<base::SingleThreadTaskRunner> runner)
+ : arming_policy_(arming_policy),
+ task_runner_(std::move(runner)),
+ is_default_task_runner_(task_runner_ ==
+ base::ThreadTaskRunnerHandle::Get()),
+ heap_profiler_tag_(from_here.file_name()),
+ weak_factory_(this) {
+ MojoResult rv = CreateWatcher(&Context::CallNotify, &watcher_handle_);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+}
+
+SimpleWatcher::~SimpleWatcher() {
+ if (IsWatching())
+ Cancel();
+}
+
+bool SimpleWatcher::IsWatching() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return context_ != nullptr;
+}
+
+MojoResult SimpleWatcher::Watch(Handle handle,
+ MojoHandleSignals signals,
+ const ReadyCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!IsWatching());
+ DCHECK(!callback.is_null());
+
+ callback_ = callback;
+ handle_ = handle;
+ watch_id_ += 1;
+
+ MojoResult watch_result = MOJO_RESULT_UNKNOWN;
+ context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_,
+ watcher_handle_.get(), handle_, signals, watch_id_,
+ &watch_result);
+ if (!context_) {
+ handle_.set_value(kInvalidHandleValue);
+ callback_.Reset();
+ DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, watch_result);
+ return watch_result;
+ }
+
+ if (arming_policy_ == ArmingPolicy::AUTOMATIC)
+ ArmOrNotify();
+
+ return MOJO_RESULT_OK;
+}
+
+void SimpleWatcher::Cancel() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // The watcher may have already been cancelled if the handle was closed.
+ if (!context_)
+ return;
+
+ // Prevent the cancellation notification from being dispatched to
+ // OnHandleReady() when cancellation is explicit. See the note in the
+ // implementation of DisableCancellationNotifications() above.
+ context_->DisableCancellationNotifications();
+
+ handle_.set_value(kInvalidHandleValue);
+ callback_.Reset();
+
+ // Ensure |context_| is unset by the time we call MojoCancelWatch, as may
+ // re-enter the notification callback and we want to ensure |context_| is
+ // unset by then.
+ scoped_refptr<Context> context;
+ std::swap(context, context_);
+ MojoResult rv =
+ MojoCancelWatch(watcher_handle_.get().value(), context->value());
+
+ // It's possible this cancellation could race with a handle closure
+ // notification, in which case the watch may have already been implicitly
+ // cancelled.
+ DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
+}
+
+MojoResult SimpleWatcher::Arm(MojoResult* ready_result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ uint32_t num_ready_contexts = 1;
+ uintptr_t ready_context;
+ MojoResult local_ready_result;
+ MojoHandleSignalsState ready_state;
+ MojoResult rv =
+ MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts,
+ &ready_context, &local_ready_result, &ready_state);
+ if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+ DCHECK(context_);
+ DCHECK_EQ(1u, num_ready_contexts);
+ DCHECK_EQ(context_->value(), ready_context);
+ if (ready_result)
+ *ready_result = local_ready_result;
+ }
+
+ return rv;
+}
+
+void SimpleWatcher::ArmOrNotify() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Already cancelled, nothing to do.
+ if (!IsWatching())
+ return;
+
+ MojoResult ready_result;
+ MojoResult rv = Arm(&ready_result);
+ if (rv == MOJO_RESULT_OK)
+ return;
+
+ DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv);
+ task_runner_->PostTask(FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady,
+ weak_factory_.GetWeakPtr(),
+ watch_id_, ready_result));
+}
+
+void SimpleWatcher::OnHandleReady(int watch_id, MojoResult result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // This notification may be for a previously watched context, in which case
+ // we just ignore it.
+ if (watch_id != watch_id_)
+ return;
+
+ ReadyCallback callback = callback_;
+ if (result == MOJO_RESULT_CANCELLED) {
+ // Implicit cancellation due to someone closing the watched handle. We clear
+ // the SimppleWatcher's state before dispatching this.
+ context_ = nullptr;
+ handle_.set_value(kInvalidHandleValue);
+ callback_.Reset();
+ }
+
+ // NOTE: It's legal for |callback| to delete |this|.
+ if (!callback.is_null()) {
+ TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_);
+
+ base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr();
+ callback.Run(result);
+ if (!weak_self)
+ return;
+
+ if (unsatisfiable_)
+ return;
+
+ // Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying
+ // at most once in AUTOMATIC arming mode.
+ if (result == MOJO_RESULT_FAILED_PRECONDITION)
+ unsatisfiable_ = true;
+
+ if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching())
+ ArmOrNotify();
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/simple_watcher.h b/mojo/public/cpp/system/simple_watcher.h
new file mode 100644
index 0000000000..9001884c97
--- /dev/null
+++ b/mojo/public/cpp/system/simple_watcher.h
@@ -0,0 +1,215 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
+
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/system_export.h"
+#include "mojo/public/cpp/system/watcher.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace mojo {
+
+// This provides a convenient thread-bound watcher implementation to safely
+// watch a single handle, dispatching state change notifications to an arbitrary
+// SingleThreadTaskRunner running on the same thread as the SimpleWatcher.
+//
+// SimpleWatcher exposes the concept of "arming" from the low-level Watcher API.
+// In general, a SimpleWatcher must be "armed" in order to dispatch a single
+// notification, and must then be rearmed before it will dispatch another. For
+// more details, see the documentation for ArmingPolicy and the Arm() and
+// ArmOrNotify() methods below.
+class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
+ public:
+ // A callback to be called any time a watched handle changes state in some
+ // interesting way. The |result| argument indicates one of the following
+ // conditions depending on its value:
+ //
+ // |MOJO_RESULT_OK|: One or more of the signals being watched is satisfied.
+ //
+ // |MOJO_RESULT_FAILED_PRECONDITION|: None of the signals being watched can
+ // ever be satisfied again.
+ //
+ // |MOJO_RESULT_CANCELLED|: The watched handle has been closed. No further
+ // notifications will be fired, as this equivalent to an implicit
+ // CancelWatch().
+ //
+ // Note that unlike the first two conditions, this callback may be invoked
+ // with |MOJO_RESULT_CANCELLED| even while the SimpleWatcher is disarmed.
+ using ReadyCallback = base::Callback<void(MojoResult result)>;
+
+ // Selects how this SimpleWatcher is to be armed.
+ enum class ArmingPolicy {
+ // The SimpleWatcher is armed automatically on Watch() and rearmed again
+ // after every invocation of the ReadyCallback. There is no need to manually
+ // call Arm() on a SimpleWatcher using this policy. This mode is equivalent
+ // to calling ArmOrNotify() once after Watch() and once again after every
+ // dispatched notification in MANUAL mode.
+ //
+ // This provides a reasonable approximation of edge-triggered behavior,
+ // mitigating (but not completely eliminating) the potential for redundant
+ // notifications.
+ //
+ // NOTE: It is important when using AUTOMATIC policy that your ReadyCallback
+ // always attempt to change the state of the handle (e.g. read available
+ // messages on a message pipe.) Otherwise this will result in a potentially
+ // large number of avoidable redundant tasks.
+ //
+ // For perfect edge-triggered behavior, use MANUAL policy and manually Arm()
+ // the SimpleWatcher as soon as it becomes possible to do so again.
+ AUTOMATIC,
+
+ // The SimpleWatcher is never armed automatically. Arm() or ArmOrNotify()
+ // must be called manually before any non-cancellation notification can be
+ // dispatched to the ReadyCallback. See the documentation for Arm() and
+ // ArmNotify() methods below for more details.
+ MANUAL,
+ };
+
+ SimpleWatcher(const tracked_objects::Location& from_here,
+ ArmingPolicy arming_policy,
+ scoped_refptr<base::SingleThreadTaskRunner> runner =
+ base::ThreadTaskRunnerHandle::Get());
+ ~SimpleWatcher();
+
+ // Indicates if the SimpleWatcher is currently watching a handle.
+ bool IsWatching() const;
+
+ // Starts watching |handle|. A SimpleWatcher may only watch one handle at a
+ // time, but it is safe to call this more than once as long as the previous
+ // watch has been cancelled (i.e. |IsWatching()| returns |false|.)
+ //
+ // If |handle| is not a valid watchable (message or data pipe) handle or
+ // |signals| is not a valid set of signals to watch, this returns
+ // |MOJO_RESULT_INVALID_ARGUMENT|.
+ //
+ // Otherwise |MOJO_RESULT_OK| is returned and the handle will be watched until
+ // either |handle| is closed, the SimpleWatcher is destroyed, or Cancel() is
+ // explicitly called.
+ //
+ // Once the watch is started, |callback| may be called at any time on the
+ // current thread until |Cancel()| is called or the handle is closed. Note
+ // that |callback| can be called for results other than
+ // |MOJO_RESULT_CANCELLED| only if the SimpleWatcher is currently armed. Use
+ // ArmingPolicy to configure how a SimpleWatcher is armed.
+ //
+ // |MOJO_RESULT_CANCELLED| may be dispatched even while the SimpleWatcher
+ // is disarmed, and no further notifications will be dispatched after that.
+ //
+ // Destroying the SimpleWatcher implicitly calls |Cancel()|.
+ MojoResult Watch(Handle handle,
+ MojoHandleSignals signals,
+ const ReadyCallback& callback);
+
+ // Cancels the current watch. Once this returns, the ReadyCallback previously
+ // passed to |Watch()| will never be called again for this SimpleWatcher.
+ //
+ // Note that when cancelled with an explicit call to |Cancel()| the
+ // ReadyCallback will not be invoked with a |MOJO_RESULT_CANCELLED| result.
+ void Cancel();
+
+ // Manually arms the SimpleWatcher.
+ //
+ // Arming the SimpleWatcher allows it to fire a single notification regarding
+ // some future relevant change in the watched handle's state. It's only valid
+ // to call Arm() while a handle is being watched (see Watch() above.)
+ //
+ // SimpleWatcher is always disarmed immediately before invoking its
+ // ReadyCallback and must be rearmed again before another notification can
+ // fire.
+ //
+ // If the watched handle already meets the watched signaling conditions -
+ // i.e., if it would have notified immediately once armed - the SimpleWatcher
+ // is NOT armed, and this call fails with a return value of
+ // |MOJO_RESULT_FAILED_PRECONDITION|. In that case, what would have been the
+ // result code for that immediate notification is instead placed in
+ // |*ready_result| if |ready_result| is non-null.
+ //
+ // If the watcher is successfully armed, this returns |MOJO_RESULT_OK| and
+ // |ready_result| is ignored.
+ MojoResult Arm(MojoResult* ready_result = nullptr);
+
+ // Manually arms the SimpleWatcher OR posts a task to invoke the ReadyCallback
+ // with the ready result of the failed arming attempt.
+ //
+ // This is meant as a convenient helper for a common usage of Arm(), and it
+ // ensures that the ReadyCallback will be invoked asynchronously again as soon
+ // as the watch's conditions are satisfied, assuming the SimpleWatcher isn't
+ // cancelled first.
+ //
+ // Unlike Arm() above, this can never fail.
+ void ArmOrNotify();
+
+ Handle handle() const { return handle_; }
+ ReadyCallback ready_callback() const { return callback_; }
+
+ // Sets the tag used by the heap profiler.
+ // |tag| must be a const string literal.
+ void set_heap_profiler_tag(const char* heap_profiler_tag) {
+ heap_profiler_tag_ = heap_profiler_tag;
+ }
+
+ private:
+ class Context;
+
+ void OnHandleReady(int watch_id, MojoResult result);
+
+ base::ThreadChecker thread_checker_;
+
+ // The policy used to determine how this SimpleWatcher is armed.
+ const ArmingPolicy arming_policy_;
+
+ // The TaskRunner of this SimpleWatcher's owning thread. This field is safe to
+ // access from any thread.
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get()
+ // for the thread.
+ const bool is_default_task_runner_;
+
+ ScopedWatcherHandle watcher_handle_;
+
+ // A thread-safe context object corresponding to the currently active watch,
+ // if any.
+ scoped_refptr<Context> context_;
+
+ // Fields below must only be accessed on the SimpleWatcher's owning thread.
+
+ // The handle currently under watch. Not owned.
+ Handle handle_;
+
+ // A simple counter to disambiguate notifications from multiple watch contexts
+ // in the event that this SimpleWatcher cancels and watches multiple times.
+ int watch_id_ = 0;
+
+ // The callback to call when the handle is signaled.
+ ReadyCallback callback_;
+
+ // Tracks if the SimpleWatcher has already notified of unsatisfiability. This
+ // is used to prevent redundant notifications in AUTOMATIC mode.
+ bool unsatisfiable_ = false;
+
+ // Tag used to ID memory allocations that originated from notifications in
+ // this watcher.
+ const char* heap_profiler_tag_ = nullptr;
+
+ base::WeakPtrFactory<SimpleWatcher> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleWatcher);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
diff --git a/mojo/public/cpp/system/system_export.h b/mojo/public/cpp/system/system_export.h
new file mode 100644
index 0000000000..c9bb140db3
--- /dev/null
+++ b/mojo/public/cpp/system/system_export.h
@@ -0,0 +1,34 @@
+// 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 MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(MOJO_CPP_SYSTEM_IMPLEMENTATION)
+#define MOJO_CPP_SYSTEM_EXPORT __declspec(dllexport)
+#else
+#define MOJO_CPP_SYSTEM_EXPORT __declspec(dllimport)
+#endif
+
+#else // !defined(WIN32)
+
+#if defined(MOJO_CPP_SYSTEM_IMPLEMENTATION)
+#define MOJO_CPP_SYSTEM_EXPORT __attribute((visibility("default")))
+#else
+#define MOJO_CPP_SYSTEM_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+#else // !defined(COMPONENT_BUILD)
+
+#define MOJO_CPP_SYSTEM_EXPORT
+
+#endif // defined(COMPONENT_BUILD)
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_
diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn
new file mode 100644
index 0000000000..705d009c9c
--- /dev/null
+++ b/mojo/public/cpp/system/tests/BUILD.gn
@@ -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.
+
+source_set("tests") {
+ testonly = true
+
+ sources = [
+ "core_unittest.cc",
+ "handle_signals_state_unittest.cc",
+ "simple_watcher_unittest.cc",
+ "wait_set_unittest.cc",
+ "wait_unittest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/public/c/system/tests",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc
new file mode 100644
index 0000000000..40a94f008f
--- /dev/null
+++ b/mojo/public/cpp/system/tests/core_unittest.cc
@@ -0,0 +1,510 @@
+// Copyright 2014 The Chromium 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 C++ Mojo system core wrappers.
+// TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets
+// compiled into a different binary from the C API tests.
+
+#include "mojo/public/cpp/system/core.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <map>
+#include <utility>
+
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const MojoHandleSignals kSignalReadableWritable =
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
+
+const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+
+TEST(CoreCppTest, GetTimeTicksNow) {
+ const MojoTimeTicks start = GetTimeTicksNow();
+ EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
+ << "GetTimeTicksNow should return nonzero value";
+}
+
+TEST(CoreCppTest, Basic) {
+ // Basic |Handle| implementation:
+ {
+ EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue);
+
+ Handle h0;
+ EXPECT_EQ(kInvalidHandleValue, h0.value());
+ EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value());
+ EXPECT_FALSE(h0.is_valid());
+
+ Handle h1(static_cast<MojoHandle>(123));
+ EXPECT_EQ(static_cast<MojoHandle>(123), h1.value());
+ EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value());
+ EXPECT_TRUE(h1.is_valid());
+ *h1.mutable_value() = static_cast<MojoHandle>(456);
+ EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
+ EXPECT_TRUE(h1.is_valid());
+
+ h1.swap(h0);
+ EXPECT_EQ(static_cast<MojoHandle>(456), h0.value());
+ EXPECT_TRUE(h0.is_valid());
+ EXPECT_FALSE(h1.is_valid());
+
+ h1.set_value(static_cast<MojoHandle>(789));
+ h0.swap(h1);
+ EXPECT_EQ(static_cast<MojoHandle>(789), h0.value());
+ EXPECT_TRUE(h0.is_valid());
+ EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
+ EXPECT_TRUE(h1.is_valid());
+
+ // Make sure copy constructor works.
+ Handle h2(h0);
+ EXPECT_EQ(static_cast<MojoHandle>(789), h2.value());
+ // And assignment.
+ h2 = h1;
+ EXPECT_EQ(static_cast<MojoHandle>(456), h2.value());
+
+ // Make sure that we can put |Handle|s into |std::map|s.
+ h0 = Handle(static_cast<MojoHandle>(987));
+ h1 = Handle(static_cast<MojoHandle>(654));
+ h2 = Handle(static_cast<MojoHandle>(321));
+ Handle h3;
+ std::map<Handle, int> handle_to_int;
+ handle_to_int[h0] = 0;
+ handle_to_int[h1] = 1;
+ handle_to_int[h2] = 2;
+ handle_to_int[h3] = 3;
+
+ EXPECT_EQ(4u, handle_to_int.size());
+ EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end());
+ EXPECT_EQ(0, handle_to_int[h0]);
+ EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end());
+ EXPECT_EQ(1, handle_to_int[h1]);
+ EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end());
+ EXPECT_EQ(2, handle_to_int[h2]);
+ EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end());
+ EXPECT_EQ(3, handle_to_int[h3]);
+ EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) ==
+ handle_to_int.end());
+
+ // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out
+ // how to support the variations of |hash_map|.)
+ }
+
+ // |Handle|/|ScopedHandle| functions:
+ {
+ ScopedHandle h;
+
+ EXPECT_EQ(kInvalidHandleValue, h.get().value());
+
+ // This should be a no-op.
+ Close(std::move(h));
+
+ // It should still be invalid.
+ EXPECT_EQ(kInvalidHandleValue, h.get().value());
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE));
+
+ std::vector<Handle> wh;
+ wh.push_back(h.get());
+ std::vector<MojoHandleSignals> sigs;
+ sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE);
+ size_t result_index;
+ MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index);
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, rv);
+ }
+
+ // |MakeScopedHandle| (just compilation tests):
+ {
+ EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid());
+ }
+
+ // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions:
+ {
+ MessagePipeHandle h_invalid;
+ EXPECT_FALSE(h_invalid.is_valid());
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ WriteMessageRaw(
+ h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ char buffer[10] = {0};
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WriteMessageRaw(h_invalid,
+ buffer,
+ sizeof(buffer),
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ ReadMessageRaw(h_invalid,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ ReadMessageRaw(h_invalid,
+ buffer,
+ &buffer_size,
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ // Basic tests of waiting and closing.
+ MojoHandle hv0 = kInvalidHandleValue;
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ EXPECT_FALSE(h0.get().is_valid());
+ EXPECT_FALSE(h1.get().is_valid());
+
+ CreateMessagePipe(nullptr, &h0, &h1);
+ EXPECT_TRUE(h0.get().is_valid());
+ EXPECT_TRUE(h1.get().is_valid());
+ EXPECT_NE(h0.get().value(), h1.get().value());
+ // Save the handle values, so we can check that things got closed
+ // correctly.
+ hv0 = h0.get().value();
+ MojoHandle hv1 = h1.get().value();
+ MojoHandleSignalsState state = h0->QuerySignalsState();
+
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ std::vector<Handle> wh;
+ wh.push_back(h0.get());
+ wh.push_back(h1.get());
+ std::vector<MojoHandleSignals> sigs;
+ sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE);
+ sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE);
+ std::vector<MojoHandleSignalsState> states(sigs.size());
+
+ size_t result_index;
+ MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index,
+ states.data());
+ EXPECT_EQ(MOJO_RESULT_OK, rv);
+ EXPECT_EQ(1u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals);
+ EXPECT_EQ(kSignalAll, states[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals);
+ EXPECT_EQ(kSignalAll, states[1].satisfiable_signals);
+
+ // Test closing |h1| explicitly.
+ Close(std::move(h1));
+ EXPECT_FALSE(h1.get().is_valid());
+
+ // Make sure |h1| is closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
+
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
+ }
+ // |hv0| should have been closed when |h0| went out of scope, so this close
+ // should fail.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
+
+ // Actually test writing/reading messages.
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ CreateMessagePipe(nullptr, &h0, &h1);
+
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h0.get(),
+ kHello,
+ kHelloSize,
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
+ EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ char buffer[10] = {0};
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(h1.get(),
+ buffer,
+ &buffer_size,
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+
+ // Send a handle over the previously-establish message pipe. Use the
+ // |MessagePipe| wrapper (to test it), which automatically creates a
+ // message pipe.
+ MessagePipe mp;
+
+ // Write a message to |mp.handle0|, before we send |mp.handle1|.
+ const char kWorld[] = "world!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(mp.handle0.get(),
+ kWorld,
+ kWorldSize,
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Send |mp.handle1| over |h1| to |h0|.
+ MojoHandle handles[5];
+ handles[0] = mp.handle1.release().value();
+ EXPECT_NE(kInvalidHandleValue, handles[0]);
+ EXPECT_FALSE(mp.handle1.get().is_valid());
+ uint32_t handles_count = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h1.get(),
+ kHello,
+ kHelloSize,
+ handles,
+ handles_count,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // |handles[0]| should actually be invalid now.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0]));
+
+ // Read "hello" and the sent handle.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
+ EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ for (size_t i = 0; i < arraysize(handles); i++)
+ handles[i] = kInvalidHandleValue;
+ handles_count = static_cast<uint32_t>(arraysize(handles));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(h0.get(),
+ buffer,
+ &buffer_size,
+ handles,
+ &handles_count,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(1u, handles_count);
+ EXPECT_NE(kInvalidHandleValue, handles[0]);
+
+ // Read from the sent/received handle.
+ mp.handle1.reset(MessagePipeHandle(handles[0]));
+ // Save |handles[0]| to check that it gets properly closed.
+ hv0 = handles[0];
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
+ EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ for (size_t i = 0; i < arraysize(handles); i++)
+ handles[i] = kInvalidHandleValue;
+ handles_count = static_cast<uint32_t>(arraysize(handles));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(mp.handle1.get(),
+ buffer,
+ &buffer_size,
+ handles,
+ &handles_count,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kWorldSize, buffer_size);
+ EXPECT_STREQ(kWorld, buffer);
+ EXPECT_EQ(0u, handles_count);
+ }
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
+ }
+
+ // TODO(vtl): Test |CloseRaw()|.
+ // TODO(vtl): Test |reset()| more thoroughly?
+}
+
+TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
+ // Tear down a message pipe which still has a message enqueued, with the
+ // message also having a valid message pipe handle.
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ CreateMessagePipe(nullptr, &h0, &h1);
+
+ // Send a handle over the previously-establish message pipe.
+ ScopedMessagePipeHandle h2;
+ ScopedMessagePipeHandle h3;
+ if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK)
+ CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK.
+
+ // Write a message to |h2|, before we send |h3|.
+ const char kWorld[] = "world!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h2.get(),
+ kWorld,
+ kWorldSize,
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // And also a message to |h3|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h3.get(),
+ kWorld,
+ kWorldSize,
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Send |h3| over |h1| to |h0|.
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ MojoHandle h3_value;
+ h3_value = h3.release().value();
+ EXPECT_NE(kInvalidHandleValue, h3_value);
+ EXPECT_FALSE(h3.get().is_valid());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h1.get(),
+ kHello,
+ kHelloSize,
+ &h3_value,
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // |h3_value| should actually be invalid now.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
+ }
+
+ // Do this in a different order: make the enqueued message pipe handle only
+ // half-alive.
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ CreateMessagePipe(nullptr, &h0, &h1);
+
+ // Send a handle over the previously-establish message pipe.
+ ScopedMessagePipeHandle h2;
+ ScopedMessagePipeHandle h3;
+ if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK)
+ CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK.
+
+ // Write a message to |h2|, before we send |h3|.
+ const char kWorld[] = "world!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h2.get(),
+ kWorld,
+ kWorldSize,
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // And also a message to |h3|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h3.get(),
+ kWorld,
+ kWorldSize,
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Send |h3| over |h1| to |h0|.
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ MojoHandle h3_value;
+ h3_value = h3.release().value();
+ EXPECT_NE(kInvalidHandleValue, h3_value);
+ EXPECT_FALSE(h3.get().is_valid());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h1.get(),
+ kHello,
+ kHelloSize,
+ &h3_value,
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // |h3_value| should actually be invalid now.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
+ }
+}
+
+TEST(CoreCppTest, ScopedHandleMoveCtor) {
+ ScopedSharedBufferHandle buffer1 = SharedBufferHandle::Create(1024);
+ EXPECT_TRUE(buffer1.is_valid());
+
+ ScopedSharedBufferHandle buffer2 = SharedBufferHandle::Create(1024);
+ EXPECT_TRUE(buffer2.is_valid());
+
+ // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will
+ // assert.
+ buffer1 = std::move(buffer2);
+
+ EXPECT_TRUE(buffer1.is_valid());
+ EXPECT_FALSE(buffer2.is_valid());
+}
+
+TEST(CoreCppTest, BasicSharedBuffer) {
+ ScopedSharedBufferHandle h0 = SharedBufferHandle::Create(100);
+ ASSERT_TRUE(h0.is_valid());
+
+ // Map everything.
+ ScopedSharedBufferMapping mapping = h0->Map(100);
+ ASSERT_TRUE(mapping);
+ static_cast<char*>(mapping.get())[50] = 'x';
+
+ // Duplicate |h0| to |h1|.
+ ScopedSharedBufferHandle h1 =
+ h0->Clone(SharedBufferHandle::AccessMode::READ_ONLY);
+ ASSERT_TRUE(h1.is_valid());
+
+ // Close |h0|.
+ h0.reset();
+
+ // The mapping should still be good.
+ static_cast<char*>(mapping.get())[51] = 'y';
+
+ // Unmap it.
+ mapping.reset();
+
+ // Map half of |h1|.
+ mapping = h1->MapAtOffset(50, 50);
+ ASSERT_TRUE(mapping);
+
+ // It should have what we wrote.
+ EXPECT_EQ('x', static_cast<char*>(mapping.get())[0]);
+ EXPECT_EQ('y', static_cast<char*>(mapping.get())[1]);
+
+ // Unmap it.
+ mapping.reset();
+ h1.reset();
+
+ // Creating a 1 EB shared buffer should fail without crashing.
+ EXPECT_FALSE(SharedBufferHandle::Create(1ULL << 60).is_valid());
+}
+
+// TODO(vtl): Write data pipe tests.
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
new file mode 100644
index 0000000000..82f538e17a
--- /dev/null
+++ b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/system/handle_signals_state.h"
+
+#include "mojo/public/c/system/types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+using HandleSignalsStateTest = testing::Test;
+
+TEST_F(HandleSignalsStateTest, SanityCheck) {
+ // There's not much to test here. Just a quick sanity check to make sure the
+ // code compiles and the helper methods do what they're supposed to do.
+
+ HandleSignalsState empty_signals(MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_HANDLE_SIGNAL_NONE);
+ EXPECT_FALSE(empty_signals.readable());
+ EXPECT_FALSE(empty_signals.writable());
+ EXPECT_FALSE(empty_signals.peer_closed());
+ EXPECT_TRUE(empty_signals.never_readable());
+ EXPECT_TRUE(empty_signals.never_writable());
+ EXPECT_TRUE(empty_signals.never_peer_closed());
+
+ HandleSignalsState all_signals(
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ EXPECT_TRUE(all_signals.readable());
+ EXPECT_TRUE(all_signals.writable());
+ EXPECT_TRUE(all_signals.peer_closed());
+ EXPECT_FALSE(all_signals.never_readable());
+ EXPECT_FALSE(all_signals.never_writable());
+ EXPECT_FALSE(all_signals.never_peer_closed());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/simple_watcher_unittest.cc b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
new file mode 100644
index 0000000000..795f262c4e
--- /dev/null
+++ b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
@@ -0,0 +1,277 @@
+// 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 "mojo/public/cpp/system/simple_watcher.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+template <typename Handler>
+void RunResultHandler(Handler f, MojoResult result) {
+ f(result);
+}
+
+template <typename Handler>
+SimpleWatcher::ReadyCallback OnReady(Handler f) {
+ return base::Bind(&RunResultHandler<Handler>, f);
+}
+
+SimpleWatcher::ReadyCallback NotReached() {
+ return OnReady([](MojoResult) { NOTREACHED(); });
+}
+
+class SimpleWatcherTest : public testing::Test {
+ public:
+ SimpleWatcherTest() {}
+ ~SimpleWatcherTest() override {}
+
+ private:
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleWatcherTest);
+};
+
+TEST_F(SimpleWatcherTest, WatchBasic) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ bool notified = false;
+ base::RunLoop run_loop;
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ OnReady([&](MojoResult result) {
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ notified = true;
+ run_loop.Quit();
+ })));
+ EXPECT_TRUE(b_watcher.IsWatching());
+
+ EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ run_loop.Run();
+ EXPECT_TRUE(notified);
+
+ b_watcher.Cancel();
+}
+
+TEST_F(SimpleWatcherTest, WatchUnsatisfiable) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+ a.reset();
+
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, b_watcher.Arm());
+}
+
+TEST_F(SimpleWatcherTest, WatchInvalidHandle) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+ a.reset();
+ b.reset();
+
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+ EXPECT_FALSE(b_watcher.IsWatching());
+}
+
+TEST_F(SimpleWatcherTest, Cancel) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ base::RunLoop run_loop;
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+ EXPECT_TRUE(b_watcher.IsWatching());
+ b_watcher.Cancel();
+ EXPECT_FALSE(b_watcher.IsWatching());
+
+ // This should never trigger the watcher.
+ EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, CancelOnClose) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ base::RunLoop run_loop;
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ OnReady([&](MojoResult result) {
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ run_loop.Quit();
+ })));
+ EXPECT_TRUE(b_watcher.IsWatching());
+
+ // This should trigger the watcher above.
+ b.reset();
+
+ run_loop.Run();
+
+ EXPECT_FALSE(b_watcher.IsWatching());
+}
+
+TEST_F(SimpleWatcherTest, CancelOnDestruction) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+ base::RunLoop run_loop;
+ {
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+ EXPECT_TRUE(b_watcher.IsWatching());
+
+ // |b_watcher| should be cancelled when it goes out of scope.
+ }
+
+ // This should never trigger the watcher above.
+ EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, CloseAndCancel) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ OnReady([](MojoResult result) { FAIL(); })));
+ EXPECT_TRUE(b_watcher.IsWatching());
+
+ // This should trigger the watcher above...
+ b.reset();
+ // ...but the watcher is cancelled first.
+ b_watcher.Cancel();
+
+ EXPECT_FALSE(b_watcher.IsWatching());
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(SimpleWatcherTest, UnarmedCancel) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ base::RunLoop loop;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(
+ [](base::RunLoop* loop, MojoResult result) {
+ EXPECT_EQ(result, MOJO_RESULT_CANCELLED);
+ loop->Quit();
+ },
+ &loop)));
+
+ // This message write will not wake up the watcher since the watcher isn't
+ // armed. Instead, the cancellation will dispatch due to the reset below.
+ EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ b.reset();
+ loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, ManualArming) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ base::RunLoop loop;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(
+ [](base::RunLoop* loop, MojoResult result) {
+ EXPECT_EQ(result, MOJO_RESULT_OK);
+ loop->Quit();
+ },
+ &loop)));
+ EXPECT_EQ(MOJO_RESULT_OK, b_watcher.Arm());
+
+ EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, ManualArmOrNotifyWhileSignaled) {
+ ScopedMessagePipeHandle a, b;
+ CreateMessagePipe(nullptr, &a, &b);
+
+ base::RunLoop loop1;
+ SimpleWatcher b_watcher1(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ bool notified1 = false;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher1.Watch(
+ b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(
+ [](base::RunLoop* loop, bool* notified, MojoResult result) {
+ EXPECT_EQ(result, MOJO_RESULT_OK);
+ *notified = true;
+ loop->Quit();
+ },
+ &loop1, &notified1)));
+
+ base::RunLoop loop2;
+ SimpleWatcher b_watcher2(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ bool notified2 = false;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ b_watcher2.Watch(
+ b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(
+ [](base::RunLoop* loop, bool* notified, MojoResult result) {
+ EXPECT_EQ(result, MOJO_RESULT_OK);
+ *notified = true;
+ loop->Quit();
+ },
+ &loop2, &notified2)));
+
+ // First ensure that |b| is readable.
+ EXPECT_EQ(MOJO_RESULT_OK, b_watcher1.Arm());
+ EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ loop1.Run();
+
+ EXPECT_TRUE(notified1);
+ EXPECT_FALSE(notified2);
+ notified1 = false;
+
+ // Now verify that ArmOrNotify results in a notification.
+ b_watcher2.ArmOrNotify();
+ loop2.Run();
+
+ EXPECT_FALSE(notified1);
+ EXPECT_TRUE(notified2);
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/wait_set_unittest.cc b/mojo/public/cpp/system/tests/wait_set_unittest.cc
new file mode 100644
index 0000000000..d60cb45924
--- /dev/null
+++ b/mojo/public/cpp/system/tests/wait_set_unittest.cc
@@ -0,0 +1,376 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/system/wait_set.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+using WaitSetTest = testing::Test;
+
+void WriteMessage(const ScopedMessagePipeHandle& handle,
+ const base::StringPiece& message) {
+ MojoResult rv = WriteMessageRaw(handle.get(), message.data(),
+ static_cast<uint32_t>(message.size()),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ CHECK_EQ(MOJO_RESULT_OK, rv);
+}
+
+std::string ReadMessage(const ScopedMessagePipeHandle& handle) {
+ uint32_t num_bytes = 0;
+ uint32_t num_handles = 0;
+ MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr,
+ &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+ CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv);
+ CHECK_EQ(0u, num_handles);
+
+ std::vector<char> buffer(num_bytes);
+ rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr,
+ &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+ CHECK_EQ(MOJO_RESULT_OK, rv);
+ return std::string(buffer.data(), buffer.size());
+}
+
+class ThreadedRunner : public base::SimpleThread {
+ public:
+ explicit ThreadedRunner(const base::Closure& callback)
+ : SimpleThread("ThreadedRunner"), callback_(callback) {}
+ ~ThreadedRunner() override { Join(); }
+
+ void Run() override { callback_.Run(); }
+
+ private:
+ const base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
+};
+
+TEST_F(WaitSetTest, Satisfied) {
+ WaitSet wait_set;
+ MessagePipe p;
+
+ const char kTestMessage1[] = "hello wake up";
+
+ // Watch only one handle and write to the other.
+
+ wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ WriteMessage(p.handle0, kTestMessage1);
+
+ size_t num_ready_handles = 2;
+ Handle ready_handles[2];
+ MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+ HandleSignalsState hss[2];
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(p.handle1.get(), ready_handles[0]);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+ EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+
+ wait_set.RemoveHandle(p.handle1.get());
+
+ // Now watch only the other handle and write to the first one.
+
+ wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ WriteMessage(p.handle1, kTestMessage1);
+
+ num_ready_handles = 2;
+ ready_results[0] = MOJO_RESULT_UNKNOWN;
+ ready_results[1] = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(p.handle0.get(), ready_handles[0]);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+ EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+
+ // Now wait on both of them.
+ wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ num_ready_handles = 2;
+ ready_results[0] = MOJO_RESULT_UNKNOWN;
+ ready_results[1] = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+ EXPECT_EQ(2u, num_ready_handles);
+ EXPECT_TRUE((ready_handles[0] == p.handle0.get() &&
+ ready_handles[1] == p.handle1.get()) ||
+ (ready_handles[0] == p.handle1.get() &&
+ ready_handles[1] == p.handle0.get()));
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]);
+ EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+ EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed());
+
+ // Wait on both again, but with only enough output space for one result.
+ num_ready_handles = 1;
+ ready_results[0] = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_TRUE(ready_handles[0] == p.handle0.get() ||
+ ready_handles[0] == p.handle1.get());
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+ // Remove the ready handle from the set and wait one more time.
+ EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0]));
+
+ num_ready_handles = 1;
+ ready_results[0] = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_TRUE(ready_handles[0] == p.handle0.get() ||
+ ready_handles[0] == p.handle1.get());
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+ EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0]));
+
+ // The wait set should be empty now. Nothing to wait on.
+ num_ready_handles = 2;
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+ EXPECT_EQ(0u, num_ready_handles);
+}
+
+TEST_F(WaitSetTest, Unsatisfiable) {
+ MessagePipe p, q;
+ WaitSet wait_set;
+
+ wait_set.AddHandle(q.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ wait_set.AddHandle(q.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ size_t num_ready_handles = 2;
+ Handle ready_handles[2];
+ MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+
+ p.handle1.reset();
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(p.handle0.get(), ready_handles[0]);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]);
+}
+
+TEST_F(WaitSetTest, CloseWhileWaiting) {
+ MessagePipe p;
+ WaitSet wait_set;
+
+ wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ const Handle handle0_value = p.handle0.get();
+ ThreadedRunner close_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ // Wait a little while, then close the handle.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ handle->reset();
+ },
+ &p.handle0));
+ close_after_delay.Start();
+
+ size_t num_ready_handles = 2;
+ Handle ready_handles[2];
+ MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(handle0_value, ready_handles[0]);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_results[0]);
+
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value));
+}
+
+TEST_F(WaitSetTest, CloseBeforeWaiting) {
+ MessagePipe p;
+ WaitSet wait_set;
+
+ wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ Handle handle0_value = p.handle0.get();
+ Handle handle1_value = p.handle1.get();
+
+ p.handle0.reset();
+ p.handle1.reset();
+
+ // Ensure that the WaitSet user is always made aware of all cancellations even
+ // if they happen while not waiting, or they have to be returned over the span
+ // of multiple Wait() calls due to insufficient output storage.
+
+ size_t num_ready_handles = 1;
+ Handle ready_handle;
+ MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result);
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value));
+
+ wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result);
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value));
+
+ // Nothing more to wait on.
+ wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result);
+ EXPECT_EQ(0u, num_ready_handles);
+}
+
+TEST_F(WaitSetTest, SatisfiedThenUnsatisfied) {
+ MessagePipe p;
+ WaitSet wait_set;
+
+ wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ const char kTestMessage1[] = "testing testing testing";
+ WriteMessage(p.handle0, kTestMessage1);
+
+ size_t num_ready_handles = 2;
+ Handle ready_handles[2];
+ MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(p.handle1.get(), ready_handles[0]);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+ EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1));
+
+ ThreadedRunner write_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ // Wait a little while, then write a message.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ WriteMessage(*handle, "wakey wakey");
+ },
+ &p.handle1));
+ write_after_delay.Start();
+
+ num_ready_handles = 2;
+ wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(p.handle0.get(), ready_handles[0]);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+}
+
+TEST_F(WaitSetTest, EventOnly) {
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ WaitSet wait_set;
+ wait_set.AddEvent(&event);
+
+ base::WaitableEvent* ready_event = nullptr;
+ size_t num_ready_handles = 1;
+ Handle ready_handle;
+ MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+ EXPECT_EQ(0u, num_ready_handles);
+ EXPECT_EQ(&event, ready_event);
+}
+
+TEST_F(WaitSetTest, EventAndHandle) {
+ const char kTestMessage[] = "hello hello";
+
+ MessagePipe p;
+ WriteMessage(p.handle0, kTestMessage);
+
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitSet wait_set;
+ wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ wait_set.AddEvent(&event);
+
+ base::WaitableEvent* ready_event = nullptr;
+ size_t num_ready_handles = 1;
+ Handle ready_handle;
+ MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(nullptr, ready_event);
+ EXPECT_EQ(p.handle1.get(), ready_handle);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_result);
+
+ EXPECT_EQ(kTestMessage, ReadMessage(p.handle1));
+
+ ThreadedRunner signal_after_delay(base::Bind(
+ [](base::WaitableEvent* event) {
+ // Wait a little while, then close the handle.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ event->Signal();
+ },
+ &event));
+ signal_after_delay.Start();
+
+ wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+ EXPECT_EQ(0u, num_ready_handles);
+ EXPECT_EQ(&event, ready_event);
+}
+
+TEST_F(WaitSetTest, NoStarvation) {
+ const char kTestMessage[] = "wait for it";
+ const size_t kNumTestPipes = 50;
+ const size_t kNumTestEvents = 10;
+
+ // Create a bunch of handles and events which are always ready and add them
+ // to a shared WaitSet.
+
+ WaitSet wait_set;
+
+ MessagePipe pipes[kNumTestPipes];
+ for (size_t i = 0; i < kNumTestPipes; ++i) {
+ WriteMessage(pipes[i].handle0, kTestMessage);
+ Wait(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ WriteMessage(pipes[i].handle1, kTestMessage);
+ Wait(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+ wait_set.AddHandle(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ wait_set.AddHandle(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ }
+
+ std::vector<std::unique_ptr<base::WaitableEvent>> events(kNumTestEvents);
+ for (auto& event_ptr : events) {
+ event_ptr = base::MakeUnique<base::WaitableEvent>(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ event_ptr->Signal();
+ wait_set.AddEvent(event_ptr.get());
+ }
+
+ // Now verify that all handle and event signals are deteceted within a finite
+ // number of consecutive Wait() calls. Do it a few times for good measure.
+ for (size_t i = 0; i < 3; ++i) {
+ std::set<base::WaitableEvent*> ready_events;
+ std::set<Handle> ready_handles;
+ while (ready_events.size() < kNumTestEvents ||
+ ready_handles.size() < kNumTestPipes * 2) {
+ base::WaitableEvent* ready_event = nullptr;
+ size_t num_ready_handles = 1;
+ Handle ready_handle;
+ MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+ wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle,
+ &ready_result);
+ if (ready_event)
+ ready_events.insert(ready_event);
+
+ if (num_ready_handles) {
+ EXPECT_EQ(1u, num_ready_handles);
+ EXPECT_EQ(MOJO_RESULT_OK, ready_result);
+ ready_handles.insert(ready_handle);
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/wait_unittest.cc b/mojo/public/cpp/system/tests/wait_unittest.cc
new file mode 100644
index 0000000000..1d9d3c69bc
--- /dev/null
+++ b/mojo/public/cpp/system/tests/wait_unittest.cc
@@ -0,0 +1,321 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/system/wait.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+using WaitTest = testing::Test;
+using WaitManyTest = testing::Test;
+
+void WriteMessage(const ScopedMessagePipeHandle& handle,
+ const base::StringPiece& message) {
+ MojoResult rv = WriteMessageRaw(handle.get(), message.data(),
+ static_cast<uint32_t>(message.size()),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ CHECK_EQ(MOJO_RESULT_OK, rv);
+}
+
+std::string ReadMessage(const ScopedMessagePipeHandle& handle) {
+ uint32_t num_bytes = 0;
+ uint32_t num_handles = 0;
+ MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr,
+ &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+ CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv);
+ CHECK_EQ(0u, num_handles);
+
+ std::vector<char> buffer(num_bytes);
+ rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr,
+ &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+ CHECK_EQ(MOJO_RESULT_OK, rv);
+ return std::string(buffer.data(), buffer.size());
+}
+
+class ThreadedRunner : public base::SimpleThread {
+ public:
+ explicit ThreadedRunner(const base::Closure& callback)
+ : SimpleThread("ThreadedRunner"), callback_(callback) {}
+ ~ThreadedRunner() override { Join(); }
+
+ void Run() override { callback_.Run(); }
+
+ private:
+ const base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
+};
+
+TEST_F(WaitTest, InvalidArguments) {
+ Handle invalid_handle;
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ Wait(invalid_handle, MOJO_HANDLE_SIGNAL_READABLE));
+
+ MessagePipe p;
+ Handle valid_handles[2] = {p.handle0.get(), p.handle1.get()};
+ Handle invalid_handles[2];
+ MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_HANDLE_SIGNAL_NONE};
+ size_t result_index = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WaitMany(invalid_handles, signals, 2, &result_index));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WaitMany(nullptr, signals, 2, &result_index));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WaitMany(valid_handles, nullptr, 2, &result_index));
+}
+
+TEST_F(WaitTest, Basic) {
+ MessagePipe p;
+
+ // Write to one end of the pipe and wait on the other.
+ const char kTestMessage1[] = "how about a nice game of chess?";
+ WriteMessage(p.handle0, kTestMessage1);
+ EXPECT_EQ(MOJO_RESULT_OK, Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE));
+
+ // And make sure we can also grab the handle signals state (with both the C
+ // and C++ library structs.)
+
+ MojoHandleSignalsState c_hss = {0, 0};
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &c_hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ c_hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ c_hss.satisfiable_signals);
+
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed());
+ EXPECT_FALSE(hss.never_readable() || hss.never_writable() ||
+ hss.never_peer_closed());
+
+ // Now close the writing end and wait for peer closure.
+
+ p.handle0.reset();
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
+
+ // Still readable as there's still a message queued. No longer writable as
+ // peer closure has been detected.
+ EXPECT_TRUE(hss.readable() && hss.peer_closed() && !hss.writable());
+ EXPECT_TRUE(hss.never_writable() && !hss.never_readable() &&
+ !hss.never_peer_closed());
+
+ // Read the message and wait for readable again. Waiting should fail since
+ // there are no more messages and the peer is closed.
+ EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1));
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+
+ // Sanity check the signals state again.
+ EXPECT_TRUE(hss.peer_closed() && !hss.readable() && !hss.writable());
+ EXPECT_TRUE(hss.never_readable() && hss.never_writable() &&
+ !hss.never_peer_closed());
+}
+
+TEST_F(WaitTest, DelayedWrite) {
+ MessagePipe p;
+
+ ThreadedRunner write_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ // Wait a little while, then write a message.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ WriteMessage(*handle, "wakey wakey");
+ },
+ &p.handle0));
+ write_after_delay.Start();
+
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed());
+ EXPECT_TRUE(!hss.never_readable() && !hss.never_writable() &&
+ !hss.never_peer_closed());
+}
+
+TEST_F(WaitTest, DelayedPeerClosure) {
+ MessagePipe p;
+
+ ThreadedRunner close_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ // Wait a little while, then close the handle.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ handle->reset();
+ },
+ &p.handle0));
+ close_after_delay.Start();
+
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_TRUE(!hss.readable() && !hss.writable() && hss.peer_closed());
+ EXPECT_TRUE(hss.never_readable() && hss.never_writable() &&
+ !hss.never_peer_closed());
+}
+
+TEST_F(WaitTest, CloseWhileWaiting) {
+ MessagePipe p;
+ ThreadedRunner close_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ handle->reset();
+ },
+ &p.handle0));
+ close_after_delay.Start();
+ EXPECT_EQ(MOJO_RESULT_CANCELLED,
+ Wait(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE));
+}
+
+TEST_F(WaitManyTest, Basic) {
+ MessagePipe p;
+
+ const char kTestMessage1[] = "hello";
+ WriteMessage(p.handle0, kTestMessage1);
+
+ // Wait for either handle to become readable. Wait twice, just to verify that
+ // we can use either the C or C++ signaling state structure for the last
+ // argument.
+
+ Handle handles[2] = {p.handle0.get(), p.handle1.get()};
+ MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE};
+ size_t result_index = 0;
+ MojoHandleSignalsState c_hss[2];
+ HandleSignalsState hss[2];
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitMany(handles, signals, 2, &result_index, c_hss));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, c_hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ c_hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ c_hss[1].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ c_hss[1].satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+ EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() &&
+ !hss[0].never_peer_closed());
+ EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed());
+ EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() &&
+ !hss[1].never_peer_closed());
+
+ // Close the writer and read the message. Try to wait again, and it should
+ // fail due to the conditions being unsatisfiable.
+
+ EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1));
+ p.handle0.reset();
+
+ // handles[0] is invalid.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WaitMany(handles, signals, 2, &result_index, hss));
+ handles[0] = handles[1];
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ WaitMany(handles, signals, 1, &result_index, hss));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_TRUE(!hss[0].readable() && !hss[0].writable() && hss[0].peer_closed());
+ EXPECT_TRUE(hss[0].never_readable() && hss[0].never_writable() &&
+ !hss[0].never_peer_closed());
+}
+
+TEST_F(WaitManyTest, CloseWhileWaiting) {
+ MessagePipe p, q;
+
+ Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()};
+ MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE};
+
+ ThreadedRunner close_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ handle->reset();
+ },
+ &p.handle1));
+ close_after_delay.Start();
+
+ size_t result_index = 0;
+ EXPECT_EQ(MOJO_RESULT_CANCELLED,
+ WaitMany(handles, signals, 3, &result_index));
+ EXPECT_EQ(2u, result_index);
+}
+
+TEST_F(WaitManyTest, DelayedWrite) {
+ MessagePipe p;
+
+ ThreadedRunner write_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ // Wait a little while, then write a message.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ WriteMessage(*handle, "wakey wakey");
+ },
+ &p.handle0));
+ write_after_delay.Start();
+
+ Handle handles[2] = {p.handle0.get(), p.handle1.get()};
+ MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE};
+ size_t result_index = 0;
+ HandleSignalsState hss[2];
+ EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+ EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() &&
+ !hss[0].never_peer_closed());
+ EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed());
+ EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() &&
+ !hss[1].never_peer_closed());
+}
+
+TEST_F(WaitManyTest, DelayedPeerClosure) {
+ MessagePipe p, q;
+
+ ThreadedRunner close_after_delay(base::Bind(
+ [](ScopedMessagePipeHandle* handle) {
+ // Wait a little while, then close the handle.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+ handle->reset();
+ },
+ &p.handle0));
+ close_after_delay.Start();
+
+ Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()};
+ MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE};
+ size_t result_index = 0;
+ HandleSignalsState hss[3];
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ WaitMany(handles, signals, 3, &result_index, hss));
+ EXPECT_EQ(2u, result_index);
+ EXPECT_TRUE(!hss[2].readable() && !hss[2].writable() && hss[2].peer_closed());
+ EXPECT_TRUE(hss[2].never_readable() && hss[2].never_writable() &&
+ !hss[2].never_peer_closed());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/wait.cc b/mojo/public/cpp/system/wait.cc
new file mode 100644
index 0000000000..e4e124f25c
--- /dev/null
+++ b/mojo/public/cpp/system/wait.cc
@@ -0,0 +1,200 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/system/wait.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "mojo/public/c/system/watcher.h"
+#include "mojo/public/cpp/system/watcher.h"
+
+namespace mojo {
+namespace {
+
+class WatchContext : public base::RefCountedThreadSafe<WatchContext> {
+ public:
+ WatchContext()
+ : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ base::WaitableEvent& event() { return event_; }
+ MojoResult wait_result() const { return wait_result_; }
+ MojoHandleSignalsState wait_state() const { return wait_state_; }
+ uintptr_t context_value() const { return reinterpret_cast<uintptr_t>(this); }
+
+ static void OnNotification(uintptr_t context_value,
+ MojoResult result,
+ MojoHandleSignalsState state,
+ MojoWatcherNotificationFlags flags) {
+ auto* context = reinterpret_cast<WatchContext*>(context_value);
+ context->Notify(result, state);
+ if (result == MOJO_RESULT_CANCELLED) {
+ // Balanced in Wait() or WaitMany().
+ context->Release();
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<WatchContext>;
+
+ ~WatchContext() {}
+
+ void Notify(MojoResult result, MojoHandleSignalsState state) {
+ if (wait_result_ == MOJO_RESULT_UNKNOWN) {
+ wait_result_ = result;
+ wait_state_ = state;
+ }
+ event_.Signal();
+ }
+
+ base::WaitableEvent event_;
+
+ // NOTE: Although these are modified in Notify() which may be called from any
+ // thread, Notify() is guaranteed to never run concurrently with itself.
+ // Furthermore, they are only modified once, before |event_| signals; so there
+ // is no need for a WatchContext user to synchronize access to these fields
+ // apart from waiting on |event()|.
+ MojoResult wait_result_ = MOJO_RESULT_UNKNOWN;
+ MojoHandleSignalsState wait_state_ = {0, 0};
+
+ DISALLOW_COPY_AND_ASSIGN(WatchContext);
+};
+
+} // namespace
+
+MojoResult Wait(Handle handle,
+ MojoHandleSignals signals,
+ MojoHandleSignalsState* signals_state) {
+ ScopedWatcherHandle watcher;
+ MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ scoped_refptr<WatchContext> context = new WatchContext;
+
+ // Balanced in WatchContext::OnNotification if MojoWatch() is successful.
+ // Otherwise balanced immediately below.
+ context->AddRef();
+
+ rv = MojoWatch(watcher.get().value(), handle.value(), signals,
+ context->context_value());
+ if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
+ // Balanced above.
+ context->Release();
+ return rv;
+ }
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ uint32_t num_ready_contexts = 1;
+ uintptr_t ready_context;
+ MojoResult ready_result;
+ MojoHandleSignalsState ready_state;
+ rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts,
+ &ready_context, &ready_result, &ready_state);
+ if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+ DCHECK_EQ(1u, num_ready_contexts);
+ if (signals_state)
+ *signals_state = ready_state;
+ return ready_result;
+ }
+
+ // Wait for the first notification only.
+ context->event().Wait();
+
+ ready_result = context->wait_result();
+ DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result);
+
+ if (signals_state)
+ *signals_state = context->wait_state();
+
+ return ready_result;
+}
+
+MojoResult WaitMany(const Handle* handles,
+ const MojoHandleSignals* signals,
+ size_t num_handles,
+ size_t* result_index,
+ MojoHandleSignalsState* signals_states) {
+ if (!handles || !signals)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ ScopedWatcherHandle watcher;
+ MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ std::vector<scoped_refptr<WatchContext>> contexts(num_handles);
+ std::vector<base::WaitableEvent*> events(num_handles);
+ for (size_t i = 0; i < num_handles; ++i) {
+ contexts[i] = new WatchContext();
+
+ // Balanced in WatchContext::OnNotification if MojoWatch() is successful.
+ // Otherwise balanced immediately below.
+ contexts[i]->AddRef();
+
+ MojoResult rv = MojoWatch(watcher.get().value(), handles[i].value(),
+ signals[i], contexts[i]->context_value());
+ if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
+ if (result_index)
+ *result_index = i;
+
+ // Balanced above.
+ contexts[i]->Release();
+
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ events[i] = &contexts[i]->event();
+ }
+
+ uint32_t num_ready_contexts = 1;
+ uintptr_t ready_context = 0;
+ MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+ MojoHandleSignalsState ready_state{0, 0};
+ rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts,
+ &ready_context, &ready_result, &ready_state);
+
+ size_t index = num_handles;
+ if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+ DCHECK_EQ(1u, num_ready_contexts);
+
+ // Most commonly we only watch a small number of handles. Just scan for
+ // the right index.
+ for (size_t i = 0; i < num_handles; ++i) {
+ if (contexts[i]->context_value() == ready_context) {
+ index = i;
+ break;
+ }
+ }
+ } else {
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ // Wait for one of the contexts to signal. First one wins.
+ index = base::WaitableEvent::WaitMany(events.data(), events.size());
+ ready_result = contexts[index]->wait_result();
+ ready_state = contexts[index]->wait_state();
+ }
+
+ DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result);
+ DCHECK_LT(index, num_handles);
+
+ if (result_index)
+ *result_index = index;
+
+ if (signals_states) {
+ for (size_t i = 0; i < num_handles; ++i) {
+ if (i == index) {
+ signals_states[i] = ready_state;
+ } else {
+ signals_states[i] = handles[i].QuerySignalsState();
+ }
+ }
+ }
+
+ return ready_result;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/wait.h b/mojo/public/cpp/system/wait.h
new file mode 100644
index 0000000000..808e44fc25
--- /dev/null
+++ b/mojo/public/cpp/system/wait.h
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_
+
+#include <stddef.h>
+
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// Blocks the calling thread, waiting for one or more signals in |signals| to be
+// become satisfied -- or for all of them to become unsatisfiable -- on the
+// given Handle.
+//
+// If |signals_state| is non-null, |handle| is valid, the wait is not cancelled
+// (see return values below), the last known signaling state of |handle| is
+// written to |*signals_state| before returning.
+//
+// Return values:
+// |MOJO_RESULT_OK| if one or more signals in |signals| has been raised on
+// |handle| .
+// |MOJO_RESULT_FAILED_PRECONDITION| if the state of |handle| changes such
+// that no signals in |signals| can ever be raised again.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle.
+// |MOJO_RESULT_CANCELLED| if the wait was cancelled because |handle| was
+// closed by some other thread while waiting.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+Wait(Handle handle,
+ MojoHandleSignals signals,
+ MojoHandleSignalsState* signals_state = nullptr);
+
+// Waits on |handles[0]|, ..., |handles[num_handles-1]| until:
+// - At least one handle satisfies a signal indicated in its respective
+// |signals[0]|, ..., |signals[num_handles-1]|.
+// - It becomes known that no signal in some |signals[i]| will ever be
+// satisfied.
+//
+// This means that |WaitMany()| behaves as if |Wait()| were called on each
+// handle/signals pair simultaneously, completing when the first |Wait()| would
+// complete.
+//
+// If |signals_states| is non-null, all other arguments are valid, and the wait
+// is not cancelled (see return values below), the last known signaling state of
+// each Handle |handles[i]| is written to its corresponding entry in
+// |signals_states[i]| before returning.
+//
+// Returns values:
+// |MOJO_RESULT_OK| if one of the Handles in |handles| had one or more of its
+// correpsonding signals satisfied. |*result_index| contains the index
+// of the Handle in question if |result_index| is non-null.
+// |MOJO_RESULT_FAILED_PRECONDITION| if one of the Handles in |handles|
+// changes state such that its corresponding signals become permanently
+// unsatisfiable. |*result_index| contains the index of the handle in
+// question if |result_index| is non-null.
+// |MOJO_RESULT_INVALID_ARGUMENT| if any Handle in |handles| is invalid,
+// or if either |handles| or |signals| is null.
+// |MOJO_RESULT_CANCELLED| if the wait was cancelled because a handle in
+// |handles| was closed by some other thread while waiting.
+// |*result_index| contains the index of the closed Handle if
+// |result_index| is non-null.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+WaitMany(const Handle* handles,
+ const MojoHandleSignals* signals,
+ size_t num_handles,
+ size_t* result_index = nullptr,
+ MojoHandleSignalsState* signals_states = nullptr);
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_
diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc
new file mode 100644
index 0000000000..1728f81b95
--- /dev/null
+++ b/mojo/public/cpp/system/wait_set.cc
@@ -0,0 +1,371 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/system/wait_set.h"
+
+#include <algorithm>
+#include <limits>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/containers/stack_container.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "mojo/public/cpp/system/watcher.h"
+
+namespace mojo {
+
+class WaitSet::State : public base::RefCountedThreadSafe<State> {
+ public:
+ State()
+ : handle_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {
+ MojoResult rv = CreateWatcher(&Context::OnNotification, &watcher_handle_);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ }
+
+ void ShutDown() {
+ // NOTE: This may immediately invoke Notify for every context.
+ watcher_handle_.reset();
+ }
+
+ MojoResult AddEvent(base::WaitableEvent* event) {
+ auto result = user_events_.insert(event);
+ if (result.second)
+ return MOJO_RESULT_OK;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+
+ MojoResult RemoveEvent(base::WaitableEvent* event) {
+ auto it = user_events_.find(event);
+ if (it == user_events_.end())
+ return MOJO_RESULT_NOT_FOUND;
+ user_events_.erase(it);
+ return MOJO_RESULT_OK;
+ }
+
+ MojoResult AddHandle(Handle handle, MojoHandleSignals signals) {
+ DCHECK(watcher_handle_.is_valid());
+
+ scoped_refptr<Context> context = new Context(this, handle);
+
+ {
+ base::AutoLock lock(lock_);
+
+ if (handle_to_context_.count(handle))
+ return MOJO_RESULT_ALREADY_EXISTS;
+ DCHECK(!contexts_.count(context->context_value()));
+
+ handle_to_context_[handle] = context;
+ contexts_[context->context_value()] = context;
+ }
+
+ // Balanced in State::Notify() with MOJO_RESULT_CANCELLED if
+ // MojoWatch() succeeds. Otherwise balanced immediately below.
+ context->AddRef();
+
+ // This can notify immediately if the watcher is already armed. Don't hold
+ // |lock_| while calling it.
+ MojoResult rv = MojoWatch(watcher_handle_.get().value(), handle.value(),
+ signals, context->context_value());
+ if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
+ base::AutoLock lock(lock_);
+ handle_to_context_.erase(handle);
+ contexts_.erase(context->context_value());
+
+ // Balanced above.
+ context->Release();
+ return rv;
+ }
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ return rv;
+ }
+
+ MojoResult RemoveHandle(Handle handle) {
+ DCHECK(watcher_handle_.is_valid());
+
+ scoped_refptr<Context> context;
+ {
+ base::AutoLock lock(lock_);
+ auto it = handle_to_context_.find(handle);
+ if (it == handle_to_context_.end())
+ return MOJO_RESULT_NOT_FOUND;
+
+ context = std::move(it->second);
+ handle_to_context_.erase(it);
+
+ // Ensure that we never return this handle as a ready result again. Note
+ // that it's removal from |handle_to_context_| above ensures it will never
+ // be added back to this map.
+ ready_handles_.erase(handle);
+ }
+
+ // NOTE: This may enter the notification callback immediately, so don't hold
+ // |lock_| while calling it.
+ MojoResult rv = MojoCancelWatch(watcher_handle_.get().value(),
+ context->context_value());
+
+ // We don't really care whether or not this succeeds. In either case, the
+ // context was or will imminently be cancelled and moved from |contexts_|
+ // to |cancelled_contexts_|.
+ DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
+
+ {
+ // Always clear |cancelled_contexts_| in case it's accumulated any more
+ // entries since the last time we ran.
+ base::AutoLock lock(lock_);
+ cancelled_contexts_.clear();
+ }
+
+ return rv;
+ }
+
+ void Wait(base::WaitableEvent** ready_event,
+ size_t* num_ready_handles,
+ Handle* ready_handles,
+ MojoResult* ready_results,
+ MojoHandleSignalsState* signals_states) {
+ DCHECK(watcher_handle_.is_valid());
+ DCHECK(num_ready_handles);
+ DCHECK(ready_handles);
+ DCHECK(ready_results);
+ {
+ base::AutoLock lock(lock_);
+ if (ready_handles_.empty()) {
+ // No handles are currently in the ready set. Make sure the event is
+ // reset and try to arm the watcher.
+ handle_event_.Reset();
+
+ DCHECK_LE(*num_ready_handles, std::numeric_limits<uint32_t>::max());
+ uint32_t num_ready_contexts = static_cast<uint32_t>(*num_ready_handles);
+
+ base::StackVector<uintptr_t, 4> ready_contexts;
+ ready_contexts.container().resize(num_ready_contexts);
+ base::StackVector<MojoHandleSignalsState, 4> ready_states;
+ MojoHandleSignalsState* out_states = signals_states;
+ if (!out_states) {
+ // If the caller didn't provide a buffer for signal states, we provide
+ // our own locally. MojoArmWatcher() requires one if we want to handle
+ // arming failure properly.
+ ready_states.container().resize(num_ready_contexts);
+ out_states = ready_states.container().data();
+ }
+ MojoResult rv = MojoArmWatcher(
+ watcher_handle_.get().value(), &num_ready_contexts,
+ ready_contexts.container().data(), ready_results, out_states);
+
+ if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+ // Simulate the handles becoming ready. We do this in lieu of
+ // returning the results immediately so as to avoid potentially
+ // starving user events. i.e., we always want to call WaitMany()
+ // below.
+ handle_event_.Signal();
+ for (size_t i = 0; i < num_ready_contexts; ++i) {
+ auto it = contexts_.find(ready_contexts.container()[i]);
+ DCHECK(it != contexts_.end());
+ ready_handles_[it->second->handle()] = {ready_results[i],
+ out_states[i]};
+ }
+ } else if (rv == MOJO_RESULT_NOT_FOUND) {
+ // Nothing to watch. If there are no user events, always signal to
+ // avoid deadlock.
+ if (user_events_.empty())
+ handle_event_.Signal();
+ } else {
+ // Watcher must be armed now. No need to manually signal.
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ }
+ }
+ }
+
+ // Build a local contiguous array of events to wait on. These are rotated
+ // across Wait() calls to avoid starvation, by virtue of the fact that
+ // WaitMany guarantees left-to-right priority when multiple events are
+ // signaled.
+
+ base::StackVector<base::WaitableEvent*, 4> events;
+ events.container().resize(user_events_.size() + 1);
+ if (waitable_index_shift_ > user_events_.size())
+ waitable_index_shift_ = 0;
+
+ size_t dest_index = waitable_index_shift_++;
+ events.container()[dest_index] = &handle_event_;
+ for (auto* e : user_events_) {
+ dest_index = (dest_index + 1) % events.container().size();
+ events.container()[dest_index] = e;
+ }
+
+ size_t index = base::WaitableEvent::WaitMany(events.container().data(),
+ events.container().size());
+ base::AutoLock lock(lock_);
+
+ // Pop as many handles as we can out of the ready set and return them. Note
+ // that we do this regardless of which event signaled, as there may be
+ // ready handles in any case and they may be interesting to the caller.
+ *num_ready_handles = std::min(*num_ready_handles, ready_handles_.size());
+ for (size_t i = 0; i < *num_ready_handles; ++i) {
+ auto it = ready_handles_.begin();
+ ready_handles[i] = it->first;
+ ready_results[i] = it->second.result;
+ if (signals_states)
+ signals_states[i] = it->second.signals_state;
+ ready_handles_.erase(it);
+ }
+
+ // If the caller cares, let them know which user event unblocked us, if any.
+ if (ready_event) {
+ if (events.container()[index] == &handle_event_)
+ *ready_event = nullptr;
+ else
+ *ready_event = events.container()[index];
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<State>;
+
+ class Context : public base::RefCountedThreadSafe<Context> {
+ public:
+ Context(scoped_refptr<State> state, Handle handle)
+ : state_(state), handle_(handle) {}
+
+ Handle handle() const { return handle_; }
+
+ uintptr_t context_value() const {
+ return reinterpret_cast<uintptr_t>(this);
+ }
+
+ static void OnNotification(uintptr_t context,
+ MojoResult result,
+ MojoHandleSignalsState signals_state,
+ MojoWatcherNotificationFlags flags) {
+ reinterpret_cast<Context*>(context)->Notify(result, signals_state);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Context>;
+
+ ~Context() {}
+
+ void Notify(MojoResult result, MojoHandleSignalsState signals_state) {
+ state_->Notify(handle_, result, signals_state, this);
+ }
+
+ const scoped_refptr<State> state_;
+ const Handle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(Context);
+ };
+
+ ~State() {}
+
+ void Notify(Handle handle,
+ MojoResult result,
+ MojoHandleSignalsState signals_state,
+ Context* context) {
+ base::AutoLock lock(lock_);
+
+ // This could be a cancellation notification following an explicit
+ // RemoveHandle(), in which case we really don't care and don't want to
+ // add it to the ready set. Only update and signal if that's not the case.
+ if (!handle_to_context_.count(handle)) {
+ DCHECK_EQ(MOJO_RESULT_CANCELLED, result);
+ } else {
+ ready_handles_[handle] = {result, signals_state};
+ handle_event_.Signal();
+ }
+
+ // Whether it's an implicit or explicit cancellation, erase from |contexts_|
+ // and append to |cancelled_contexts_|.
+ if (result == MOJO_RESULT_CANCELLED) {
+ contexts_.erase(context->context_value());
+ handle_to_context_.erase(handle);
+
+ // NOTE: We retain a context ref in |cancelled_contexts_| to ensure that
+ // this Context's heap address is not reused too soon. For example, it
+ // would otherwise be possible for the user to call AddHandle() from the
+ // WaitSet's thread immediately after this notification has fired on
+ // another thread, potentially reusing the same heap address for the newly
+ // added Context; and then they may call RemoveHandle() for this handle
+ // (not knowing its context has just been implicitly cancelled) and
+ // cause the new Context to be incorrectly removed from |contexts_|.
+ //
+ // This vector is cleared on the WaitSet's own thread every time
+ // RemoveHandle is called.
+ cancelled_contexts_.emplace_back(make_scoped_refptr(context));
+
+ // Balanced in State::AddHandle().
+ context->Release();
+ }
+ }
+
+ struct ReadyState {
+ ReadyState() = default;
+ ReadyState(MojoResult result, MojoHandleSignalsState signals_state)
+ : result(result), signals_state(signals_state) {}
+ ~ReadyState() = default;
+
+ MojoResult result = MOJO_RESULT_UNKNOWN;
+ MojoHandleSignalsState signals_state = {0, 0};
+ };
+
+ // Not guarded by lock. Must only be accessed from the WaitSet's owning
+ // thread.
+ ScopedWatcherHandle watcher_handle_;
+
+ base::Lock lock_;
+ std::map<uintptr_t, scoped_refptr<Context>> contexts_;
+ std::map<Handle, scoped_refptr<Context>> handle_to_context_;
+ std::map<Handle, ReadyState> ready_handles_;
+ std::vector<scoped_refptr<Context>> cancelled_contexts_;
+ std::set<base::WaitableEvent*> user_events_;
+
+ // Event signaled any time a handle notification is received.
+ base::WaitableEvent handle_event_;
+
+ // Offset by which to rotate the current set of waitable objects. This is used
+ // to guard against event starvation, as base::WaitableEvent::WaitMany gives
+ // preference to events in left-to-right order.
+ size_t waitable_index_shift_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+WaitSet::WaitSet() : state_(new State) {}
+
+WaitSet::~WaitSet() {
+ state_->ShutDown();
+}
+
+MojoResult WaitSet::AddEvent(base::WaitableEvent* event) {
+ return state_->AddEvent(event);
+}
+
+MojoResult WaitSet::RemoveEvent(base::WaitableEvent* event) {
+ return state_->RemoveEvent(event);
+}
+
+MojoResult WaitSet::AddHandle(Handle handle, MojoHandleSignals signals) {
+ return state_->AddHandle(handle, signals);
+}
+
+MojoResult WaitSet::RemoveHandle(Handle handle) {
+ return state_->RemoveHandle(handle);
+}
+
+void WaitSet::Wait(base::WaitableEvent** ready_event,
+ size_t* num_ready_handles,
+ Handle* ready_handles,
+ MojoResult* ready_results,
+ MojoHandleSignalsState* signals_states) {
+ state_->Wait(ready_event, num_ready_handles, ready_handles, ready_results,
+ signals_states);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/wait_set.h b/mojo/public/cpp/system/wait_set.h
new file mode 100644
index 0000000000..5047a86a48
--- /dev/null
+++ b/mojo/public/cpp/system/wait_set.h
@@ -0,0 +1,124 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace base {
+class WaitableEvent;
+}
+
+namespace mojo {
+
+// WaitSet provides an efficient means of blocking a thread on any number of
+// events and Mojo handle state changes.
+//
+// Unlike WaitMany(), which incurs some extra setup cost for every call, a
+// WaitSet maintains some persistent accounting of the handles added or removed
+// from the set. A blocking wait operation (see the Wait() method below) can
+// then be performed multiple times for the same set of events and handles with
+// minimal additional setup per call.
+//
+// WaitSet is NOT thread-safe, so naturally handles and events may not be added
+// to or removed from the set while waiting.
+class MOJO_CPP_SYSTEM_EXPORT WaitSet {
+ public:
+ WaitSet();
+ ~WaitSet();
+
+ // Adds |event| to the set of events to wait on. If successful, any future
+ // Wait() on this WaitSet will wake up if the event is signaled.
+ //
+ // |event| is not owned.
+ //
+ // Return values:
+ // |MOJO_RESULT_OK| if |event| has been successfully added.
+ // |MOJO_RESULT_ALREADY_EXISTS| if |event| is already in this WaitSet.
+ MojoResult AddEvent(base::WaitableEvent* event);
+
+ // Removes |event| from the set of events to wait on.
+ //
+ // Return values:
+ // |MOJO_RESULT_OK| if |event| has been successfully added.
+ // |MOJO_RESULT_NOT_FOUND| if |event| was not in the set.
+ MojoResult RemoveEvent(base::WaitableEvent* event);
+
+ // Adds |handle| to the set of handles to wait on. If successful, any future
+ // Wait() on this WaitSet will wake up in the event that one or more signals
+ // in |signals| becomes satisfied on |handle| or all of them become
+ // permanently unsatisfiable.
+ //
+ // Return values:
+ // |MOJO_RESULT_OK| if |handle| has been successfully added.
+ // |MOJO_RESULT_ALREADY_EXISTS| if |handle| is already in this WaitSet.
+ // |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle.
+ MojoResult AddHandle(Handle handle, MojoHandleSignals signals);
+
+ // Removes |handle| from the set of handles to wait on. Future calls to
+ // Wait() will be unaffected by the state of this handle.
+ //
+ // Return values:
+ // |MOJO_RESULT_OK| if |handle| has been successfully removed.
+ // |MOJO_RESULT_NOT_FOUND| if |handle| was not in the set.
+ MojoResult RemoveHandle(Handle handle);
+
+ // Waits on the current set of handles, waking up when one more of them meets
+ // the signaling conditions which were specified when they were added via
+ // AddHandle() above.
+ //
+ // |*num_ready_handles| on input must specify the number of entries available
+ // for output storage in |ready_handles| and |ready_result| (which must both
+ // be non-null). If |signals_states| is non-null it must also point to enough
+ // storage for |*num_ready_handles| MojoHandleSignalsState structures.
+ //
+ // Upon return, |*num_ready_handles| will contain the total number of handles
+ // whose information is stored in the given output buffers.
+ //
+ // If |ready_event| is non-null and the Wait() was unblocked by a user event
+ // signaling, the address of the event which signaled will be placed in
+ // |*ready_event|. Note that this is not necessarily exclusive to one or more
+ // handles also being ready. If |ready_event| is non-null and no user event
+ // was signaled for this Wait(), |*ready_event| will be null upon return.
+ //
+ // Every entry in |ready_handles| on output corresponds to one of the handles
+ // whose signaling state termianted the Wait() operation. Every corresponding
+ // entry in |ready_results| indicates the status of a ready handle according
+ // to the following result codes:
+ // |MOJO_RESULT_OK| one or more signals for the handle has been satisfied.
+ // |MOJO_RESULT_FAILED_PRECONDITION| all of the signals for the handle have
+ // become permanently unsatisfiable.
+ // |MOJO_RESULT_CANCELLED| if the handle has been closed from another
+ // thread. NOTE: It is important to recognize that this means the
+ // corresponding value in |ready_handles| is either invalid, or valid
+ // but referring to a different handle (i.e. has already been reused) by
+ // the time Wait() returns. The handle in question is automatically
+ // removed from the WaitSet.
+ void Wait(base::WaitableEvent** ready_event,
+ size_t* num_ready_handles,
+ Handle* ready_handles,
+ MojoResult* ready_results,
+ MojoHandleSignalsState* signals_states = nullptr);
+
+ private:
+ class State;
+
+ // Thread-safe state associated with this WaitSet. Used to aggregate
+ // notifications from watched handles.
+ scoped_refptr<State> state_;
+
+ DISALLOW_COPY_AND_ASSIGN(WaitSet);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_
diff --git a/mojo/public/cpp/system/watcher.cc b/mojo/public/cpp/system/watcher.cc
new file mode 100644
index 0000000000..0c62ba8e20
--- /dev/null
+++ b/mojo/public/cpp/system/watcher.cc
@@ -0,0 +1,20 @@
+// 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 "mojo/public/cpp/system/watcher.h"
+
+#include "mojo/public/c/system/functions.h"
+
+namespace mojo {
+
+MojoResult CreateWatcher(MojoWatcherCallback callback,
+ ScopedWatcherHandle* watcher_handle) {
+ MojoHandle handle;
+ MojoResult rv = MojoCreateWatcher(callback, &handle);
+ if (rv == MOJO_RESULT_OK)
+ watcher_handle->reset(WatcherHandle(handle));
+ return rv;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/watcher.h b/mojo/public/cpp/system/watcher.h
new file mode 100644
index 0000000000..d0a257814d
--- /dev/null
+++ b/mojo/public/cpp/system/watcher.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
+
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/c/system/watcher.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// A strongly-typed representation of a |MojoHandle| for a watcher.
+class WatcherHandle : public Handle {
+ public:
+ WatcherHandle() = default;
+ explicit WatcherHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+};
+
+static_assert(sizeof(WatcherHandle) == sizeof(Handle),
+ "Bad size for C++ WatcherHandle");
+
+typedef ScopedHandleBase<WatcherHandle> ScopedWatcherHandle;
+static_assert(sizeof(ScopedWatcherHandle) == sizeof(WatcherHandle),
+ "Bad size for C++ ScopedWatcherHandle");
+
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+CreateWatcher(MojoWatcherCallback callback,
+ ScopedWatcherHandle* watcher_handle);
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
diff --git a/mojo/public/cpp/test_support/BUILD.gn b/mojo/public/cpp/test_support/BUILD.gn
new file mode 100644
index 0000000000..efa1712fff
--- /dev/null
+++ b/mojo/public/cpp/test_support/BUILD.gn
@@ -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.
+
+static_library("test_utils") {
+ testonly = true
+
+ sources = [
+ "lib/test_support.cc",
+ "lib/test_utils.cc",
+ "test_utils.h",
+ ]
+
+ deps = [
+ "//mojo/public/c/test_support",
+ "//mojo/public/cpp/system",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/cpp/test_support/lib/test_support.cc b/mojo/public/cpp/test_support/lib/test_support.cc
new file mode 100644
index 0000000000..0b6035b9c2
--- /dev/null
+++ b/mojo/public/cpp/test_support/lib/test_support.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 "mojo/public/cpp/test_support/test_support.h"
+
+#include <stdlib.h>
+
+namespace mojo {
+namespace test {
+
+std::vector<std::string> EnumerateSourceRootRelativeDirectory(
+ const std::string& relative_path) {
+ char** names = MojoTestSupportEnumerateSourceRootRelativeDirectory(
+ relative_path.c_str());
+ std::vector<std::string> results;
+ for (char** ptr = names; *ptr != nullptr; ++ptr) {
+ results.push_back(*ptr);
+ free(*ptr);
+ }
+ free(names);
+ return results;
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc
new file mode 100644
index 0000000000..7fe6f02788
--- /dev/null
+++ b/mojo/public/cpp/test_support/lib/test_utils.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 "mojo/public/cpp/test_support/test_utils.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+
+namespace mojo {
+namespace test {
+
+bool WriteTextMessage(const MessagePipeHandle& handle,
+ const std::string& text) {
+ MojoResult rv = WriteMessageRaw(handle,
+ text.data(),
+ static_cast<uint32_t>(text.size()),
+ nullptr,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ return rv == MOJO_RESULT_OK;
+}
+
+bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text) {
+ MojoResult rv;
+ bool did_wait = false;
+
+ uint32_t num_bytes = 0, num_handles = 0;
+ for (;;) {
+ rv = ReadMessageRaw(handle,
+ nullptr,
+ &num_bytes,
+ nullptr,
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ if (rv == MOJO_RESULT_SHOULD_WAIT) {
+ if (did_wait) {
+ assert(false); // Looping endlessly!?
+ return false;
+ }
+ rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE);
+ if (rv != MOJO_RESULT_OK)
+ return false;
+ did_wait = true;
+ } else {
+ assert(!num_handles);
+ break;
+ }
+ }
+
+ text->resize(num_bytes);
+ rv = ReadMessageRaw(handle,
+ &text->at(0),
+ &num_bytes,
+ nullptr,
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ return rv == MOJO_RESULT_OK;
+}
+
+bool DiscardMessage(const MessagePipeHandle& handle) {
+ MojoResult rv = ReadMessageRaw(handle,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
+ return rv == MOJO_RESULT_OK;
+}
+
+void IterateAndReportPerf(const char* test_name,
+ const char* sub_test_name,
+ PerfTestSingleIteration single_iteration,
+ void* closure) {
+ // TODO(vtl): These should be specifiable using command-line flags.
+ static const size_t kGranularity = 100;
+ static const MojoTimeTicks kPerftestTimeMicroseconds = 3 * 1000000;
+
+ const MojoTimeTicks start_time = GetTimeTicksNow();
+ MojoTimeTicks end_time;
+ size_t iterations = 0;
+ do {
+ for (size_t i = 0; i < kGranularity; i++)
+ (*single_iteration)(closure);
+ iterations += kGranularity;
+
+ end_time = GetTimeTicksNow();
+ } while (end_time - start_time < kPerftestTimeMicroseconds);
+
+ MojoTestSupportLogPerfResult(test_name, sub_test_name,
+ 1000000.0 * iterations / (end_time - start_time),
+ "iterations/second");
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/test_support/test_support.h b/mojo/public/cpp/test_support/test_support.h
new file mode 100644
index 0000000000..9a536e649f
--- /dev/null
+++ b/mojo/public/cpp/test_support/test_support.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 MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_
+#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_
+
+#include <string>
+#include <vector>
+
+#include "mojo/public/c/test_support/test_support.h"
+
+namespace mojo {
+namespace test {
+
+inline void LogPerfResult(const char* test_name,
+ const char* sub_test_name,
+ double value,
+ const char* units) {
+ MojoTestSupportLogPerfResult(test_name, sub_test_name, value, units);
+}
+
+// Opens text file relative to the source root for reading.
+inline FILE* OpenSourceRootRelativeFile(const std::string& relative_path) {
+ return MojoTestSupportOpenSourceRootRelativeFile(relative_path.c_str());
+}
+
+// Returns the list of regular files in a directory relative to the source root.
+std::vector<std::string> EnumerateSourceRootRelativeDirectory(
+ const std::string& relative_path);
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_
diff --git a/mojo/public/cpp/test_support/test_utils.h b/mojo/public/cpp/test_support/test_utils.h
new file mode 100644
index 0000000000..6fd5a9ea25
--- /dev/null
+++ b/mojo/public/cpp/test_support/test_utils.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 MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_
+#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_
+
+#include <string>
+
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace test {
+
+// Writes a message to |handle| with message data |text|. Returns true on
+// success.
+bool WriteTextMessage(const MessagePipeHandle& handle, const std::string& text);
+
+// Reads a message from |handle|, putting its contents into |*text|. Returns
+// true on success. (This blocks if necessary and will call |MojoReadMessage()|
+// multiple times, e.g., to query the size of the message.)
+bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text);
+
+// Discards a message from |handle|. Returns true on success. (This does not
+// block. It will fail if no message is available to discard.)
+bool DiscardMessage(const MessagePipeHandle& handle);
+
+// Run |single_iteration| an appropriate number of times and report its
+// performance appropriately. (This actually runs |single_iteration| for a fixed
+// amount of time and reports the number of iterations per unit time.)
+typedef void (*PerfTestSingleIteration)(void* closure);
+void IterateAndReportPerf(const char* test_name,
+ const char* sub_test_name,
+ PerfTestSingleIteration single_iteration,
+ void* closure);
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_
diff --git a/mojo/public/interfaces/BUILD.gn b/mojo/public/interfaces/BUILD.gn
new file mode 100644
index 0000000000..fb11ec2250
--- /dev/null
+++ b/mojo/public/interfaces/BUILD.gn
@@ -0,0 +1,9 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+group("interfaces") {
+ deps = [
+ "bindings",
+ ]
+}
diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn
new file mode 100644
index 0000000000..c2cadcd736
--- /dev/null
+++ b/mojo/public/interfaces/bindings/BUILD.gn
@@ -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.
+
+import("../../tools/bindings/mojom.gni")
+
+mojom("bindings") {
+ visibility = []
+ sources = [
+ "interface_control_messages.mojom",
+ "pipe_control_messages.mojom",
+ ]
+
+ export_class_attribute = "MOJO_CPP_BINDINGS_EXPORT"
+ export_define = "MOJO_CPP_BINDINGS_IMPLEMENTATION"
+ export_header = "mojo/public/cpp/bindings/bindings_export.h"
+}
+
+# TODO(yzshen): Remove this target and use the one above once
+# |use_new_js_bindings| becomes true by default.
+mojom("new_bindings") {
+ visibility = []
+ sources = [
+ "new_bindings/interface_control_messages.mojom",
+ "new_bindings/pipe_control_messages.mojom",
+ ]
+
+ use_new_js_bindings = true
+}
diff --git a/mojo/public/interfaces/bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/interface_control_messages.mojom
new file mode 100644
index 0000000000..0a1904206a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/interface_control_messages.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"]
+module mojo.interface_control;
+
+// For each user-defined interface, some control functions are provided by the
+// interface endpoints at both sides.
+
+////////////////////////////////////////////////////////////////////////////////
+// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output);
+//
+// This control function runs the input command. If the command is not
+// supported, |output| is set to null; otherwise |output| stores the result,
+// whose type depends on the input.
+
+const uint32 kRunMessageId = 0xFFFFFFFF;
+
+struct RunMessageParams {
+ RunInput input;
+};
+union RunInput {
+ QueryVersion query_version;
+ FlushForTesting flush_for_testing;
+};
+
+struct RunResponseMessageParams {
+ RunOutput? output;
+};
+union RunOutput {
+ QueryVersionResult query_version_result;
+};
+
+// Queries the max supported version of the user-defined interface.
+// Sent by the interface client side.
+struct QueryVersion {
+};
+struct QueryVersionResult {
+ uint32 version;
+};
+
+// Sent by either side of the interface.
+struct FlushForTesting {
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
+//
+// This control function runs the input command. If the operation fails or the
+// command is not supported, the message pipe is closed.
+
+const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
+
+struct RunOrClosePipeMessageParams {
+ RunOrClosePipeInput input;
+};
+union RunOrClosePipeInput {
+ RequireVersion require_version;
+};
+
+// If the specified version of the user-defined interface is not supported, the
+// function fails and the pipe is closed.
+// Sent by the interface client side.
+struct RequireVersion {
+ uint32 version;
+};
diff --git a/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom
new file mode 100644
index 0000000000..e03ffd6589
--- /dev/null
+++ b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"]
+module mojo.interface_control2;
+
+// For each user-defined interface, some control functions are provided by the
+// interface endpoints at both sides.
+
+////////////////////////////////////////////////////////////////////////////////
+// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output);
+//
+// This control function runs the input command. If the command is not
+// supported, |output| is set to null; otherwise |output| stores the result,
+// whose type depends on the input.
+
+const uint32 kRunMessageId = 0xFFFFFFFF;
+
+struct RunMessageParams {
+ RunInput input;
+};
+union RunInput {
+ QueryVersion query_version;
+ FlushForTesting flush_for_testing;
+};
+
+struct RunResponseMessageParams {
+ RunOutput? output;
+};
+union RunOutput {
+ QueryVersionResult query_version_result;
+};
+
+// Queries the max supported version of the user-defined interface.
+// Sent by the interface client side.
+struct QueryVersion {
+};
+struct QueryVersionResult {
+ uint32 version;
+};
+
+// Sent by either side of the interface.
+struct FlushForTesting {
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
+//
+// This control function runs the input command. If the operation fails or the
+// command is not supported, the message pipe is closed.
+
+const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
+
+struct RunOrClosePipeMessageParams {
+ RunOrClosePipeInput input;
+};
+union RunOrClosePipeInput {
+ RequireVersion require_version;
+};
+
+// If the specified version of the user-defined interface is not supported, the
+// function fails and the pipe is closed.
+// Sent by the interface client side.
+struct RequireVersion {
+ uint32 version;
+};
diff --git a/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom
new file mode 100644
index 0000000000..69975fc1c0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.pipecontrol"]
+module mojo.pipe_control2;
+
+// For each message pipe running user-defined interfaces, some control
+// functions are provided and used by the routers at both ends of the pipe, so
+// that they can coordinate to manage interface endpoints.
+// All these control messages will have the interface ID field in the message
+// header set to invalid.
+
+////////////////////////////////////////////////////////////////////////////////
+// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
+//
+// This control function runs the input command. If the operation fails or the
+// command is not supported, the message pipe is closed.
+
+const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
+
+struct RunOrClosePipeMessageParams {
+ RunOrClosePipeInput input;
+};
+
+union RunOrClosePipeInput {
+ PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event;
+};
+
+// A user-defined reason about why the interface is disconnected.
+struct DisconnectReason {
+ uint32 custom_reason;
+ string description;
+};
+
+// An event to notify that an interface endpoint set up at the message sender
+// side has been closed.
+//
+// This event is omitted if the endpoint belongs to the master interface and
+// there is no disconnect reason specified.
+struct PeerAssociatedEndpointClosedEvent {
+ // The interface ID.
+ uint32 id;
+ DisconnectReason? disconnect_reason;
+};
+
diff --git a/mojo/public/interfaces/bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/pipe_control_messages.mojom
new file mode 100644
index 0000000000..74e9cc7657
--- /dev/null
+++ b/mojo/public/interfaces/bindings/pipe_control_messages.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.pipecontrol"]
+module mojo.pipe_control;
+
+// For each message pipe running user-defined interfaces, some control
+// functions are provided and used by the routers at both ends of the pipe, so
+// that they can coordinate to manage interface endpoints.
+// All these control messages will have the interface ID field in the message
+// header set to invalid.
+
+////////////////////////////////////////////////////////////////////////////////
+// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
+//
+// This control function runs the input command. If the operation fails or the
+// command is not supported, the message pipe is closed.
+
+const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
+
+struct RunOrClosePipeMessageParams {
+ RunOrClosePipeInput input;
+};
+
+union RunOrClosePipeInput {
+ PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event;
+};
+
+// A user-defined reason about why the interface is disconnected.
+struct DisconnectReason {
+ uint32 custom_reason;
+ string description;
+};
+
+// An event to notify that an interface endpoint set up at the message sender
+// side has been closed.
+//
+// This event is omitted if the endpoint belongs to the master interface and
+// there is no disconnect reason specified.
+struct PeerAssociatedEndpointClosedEvent {
+ // The interface ID.
+ uint32 id;
+ DisconnectReason? disconnect_reason;
+};
+
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
new file mode 100644
index 0000000000..e496eb656c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -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.
+
+import("../../../tools/bindings/mojom.gni")
+
+mojom("test_interfaces") {
+ testonly = true
+ sources = [
+ "math_calculator.mojom",
+ "no_module.mojom",
+ "ping_service.mojom",
+ "rect.mojom",
+ "regression_tests.mojom",
+ "sample_factory.mojom",
+ "sample_interfaces.mojom",
+ "sample_service.mojom",
+ "scoping.mojom",
+ "serialization_test_structs.mojom",
+ "test_bad_messages.mojom",
+ "test_constants.mojom",
+ "test_data_view.mojom",
+ "test_native_types.mojom",
+ "test_structs.mojom",
+ "test_sync_methods.mojom",
+ "test_unions.mojom",
+ "validation_test_interfaces.mojom",
+ ]
+ public_deps = [
+ ":echo",
+ ":test_mojom_import",
+ ":test_mojom_import2",
+ ]
+}
+
+component("test_export_component") {
+ testonly = true
+ deps = [
+ ":test_export",
+ ]
+}
+
+if (!is_ios) {
+ component("test_export_blink_component") {
+ testonly = true
+ deps = [
+ ":test_export_blink",
+ ]
+ }
+}
+
+mojom("test_export") {
+ testonly = true
+ sources = [
+ "test_export.mojom",
+ ]
+ export_class_attribute = "MOJO_TEST_EXPORT"
+ export_define = "MOJO_TEST_IMPLEMENTATION=1"
+ export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h"
+ if (!is_ios) {
+ export_class_attribute_blink = "MOJO_TEST_BLINK_EXPORT"
+ export_define_blink = "MOJO_TEST_BLINK_IMPLEMENTATION=1"
+ export_header_blink =
+ "mojo/public/cpp/bindings/tests/mojo_test_blink_export.h"
+ }
+ visibility = [ ":test_export_component" ]
+ if (!is_ios) {
+ visibility_blink = [ ":test_export_blink_component" ]
+ }
+}
+
+mojom("test_exported_import") {
+ testonly = true
+ sources = [
+ "test_import.mojom",
+ ]
+ public_deps = [
+ ":test_export",
+ ]
+
+ overridden_deps = [ ":test_export" ]
+ component_deps = [ ":test_export_component" ]
+ if (!is_ios) {
+ overridden_deps_blink = [ ":test_export" ]
+ component_deps_blink = [ ":test_export_blink_component" ]
+ }
+}
+
+# Used to test that it is okay to call mojom::Foo::Serialize()/Deserialize()
+# even if the mojom target is linked into another component.
+#
+# We don't use |test_export_component| for this test because
+# //mojo/public/cpp/bindings/tests depends on both |test_export_component| and
+# |test_exported_import| and therefore actually get the shared cpp sources of
+# test_export.mojom from |test_exported_import|.
+component("test_export_component2") {
+ testonly = true
+ public_deps = [
+ ":test_export2",
+ ]
+}
+
+mojom("test_export2") {
+ testonly = true
+ sources = [
+ "test_export2.mojom",
+ ]
+ export_class_attribute = "MOJO_TEST_EXPORT"
+ export_define = "MOJO_TEST_IMPLEMENTATION=1"
+ export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h"
+ visibility = [ ":test_export_component2" ]
+}
+
+mojom("test_mojom_import") {
+ testonly = true
+ sources = [
+ "sample_import.mojom",
+ ]
+}
+
+mojom("test_mojom_import_wrapper") {
+ testonly = true
+ public_deps = [
+ ":test_mojom_import",
+ ]
+}
+
+mojom("test_mojom_import_wrapper_wrapper") {
+ testonly = true
+ public_deps = [
+ ":test_mojom_import_wrapper",
+ ]
+}
+
+mojom("test_mojom_import2") {
+ testonly = true
+ sources = [
+ "sample_import2.mojom",
+ ]
+ public_deps = [
+ ":test_mojom_import",
+ ":test_mojom_import_wrapper_wrapper",
+ ]
+}
+
+mojom("test_struct_traits_interfaces") {
+ testonly = true
+ sources = [
+ "struct_with_traits.mojom",
+ ]
+}
+
+mojom("test_associated_interfaces") {
+ # These files are not included in the test_interfaces target because
+ # associated interfaces are not supported by all bindings languages yet.
+ testonly = true
+ sources = [
+ "test_associated_interfaces.mojom",
+ "validation_test_associated_interfaces.mojom",
+ ]
+
+ public_deps = [
+ ":test_interfaces",
+ ]
+}
+
+mojom("versioning_test_service_interfaces") {
+ testonly = true
+ sources = [
+ "versioning_test_service.mojom",
+ ]
+}
+
+mojom("versioning_test_client_interfaces") {
+ testonly = true
+ sources = [
+ "versioning_test_client.mojom",
+ ]
+}
+
+mojom("test_wtf_types") {
+ testonly = true
+
+ sources = [
+ "test_wtf_types.mojom",
+ ]
+}
+
+mojom("test_no_sources") {
+ testonly = true
+
+ public_deps = [
+ ":test_interfaces",
+ ]
+}
+
+mojom("echo") {
+ testonly = true
+ sources = [
+ "echo.mojom",
+ "echo_import.mojom",
+ ]
+ use_new_js_bindings = true
+}
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data
new file mode 100644
index 0000000000..b797feaa2d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data
@@ -0,0 +1,26 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[u4]1 // associated interface pointer: interface ID index
+[u4]1 // associated interface pointer: version
+[anchr]method0_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]3 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data
new file mode 100644
index 0000000000..a36d8073c0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data
@@ -0,0 +1,24 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[u4]1 // associated interface pointer: interface ID index
+[u4]1 // associated interface pointer: version
+[anchr]method0_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]2 // num_elements
+[u4]3
+[u4]0xFFFFFFFF // Unexpected invalid interface ID.
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected
new file mode 100644
index 0000000000..420b4210e2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data
new file mode 100644
index 0000000000..e3fa5bb4f5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data
@@ -0,0 +1,24 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[u4]1 // associated interface pointer: interface ID index
+[u4]1 // associated interface pointer: version
+[anchr]method0_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]2 // num_elements
+[u4]3
+[u4]0 // Unexpected master interface ID.
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected
new file mode 100644
index 0000000000..420b4210e2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data
new file mode 100644
index 0000000000..f9e62015c9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data
@@ -0,0 +1,27 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[u4]1111 // associated interface pointer: The interface ID index
+ // is out of range.
+[u4]1 // associated interface pointer: version
+[anchr]method0_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]3 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected
new file mode 100644
index 0000000000..420b4210e2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data
new file mode 100644
index 0000000000..b785ed1b48
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data
@@ -0,0 +1,25 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[u4]0xFFFFFFFF // associated interface pointer: Unexpected invalid
+ // interface ID index.
+[u4]1 // associated interface pointer: version
+[anchr]method0_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]2 // num_elements
+[u4]3
+[u4]4
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected
new file mode 100644
index 0000000000..d8eda1f573
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data
new file mode 100644
index 0000000000..efa21623f5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data
@@ -0,0 +1,26 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]1 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method1_params // num_bytes
+[u4]0 // version
+[u4]1 // associated interface request: interface ID index
+[u4]0 // padding
+[anchr]method1_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]3 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data
new file mode 100644
index 0000000000..5a66aad82d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data
@@ -0,0 +1,27 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]1 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method1_params // num_bytes
+[u4]0 // version
+[u4]0xFFFFFFFF // associated interface request: Unexpected invalid
+ // interface ID index.
+[u4]0 // padding
+[anchr]method1_params
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]3 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected
new file mode 100644
index 0000000000..d8eda1f573
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data
new file mode 100644
index 0000000000..ab29603ec1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data
@@ -0,0 +1,18 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]2 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[u8]0 // payload_interface_ids: This array is a nullable field.
+[anchr]message_header
+
+[anchr]payload
+[dist4]method2_params // num_bytes
+[u4]0 // version
+[u4]0xFFFFFFFF // associated interface pointer: Invalid interface ID
+ // index.
+[u4]1 // associated interface pointer: version
+[anchr]method2_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data
new file mode 100644
index 0000000000..6cb71d374f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data
@@ -0,0 +1,36 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]associated_interface_array // num_bytes
+[u4]2 // num_elements
+[u4]2 // interface ID index
+[u4]14 // version
+[u4]2 // interface ID index: The same value as the
+ // one above.
+[u4]18 // version
+[anchr]associated_interface_array
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]4 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[u4]19
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected
new file mode 100644
index 0000000000..420b4210e2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data
new file mode 100644
index 0000000000..13df01e049
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data
@@ -0,0 +1,35 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]associated_interface_array // num_bytes
+[u4]2 // num_elements
+[u4]2 // interface ID index
+[u4]14 // version
+[u4]3 // interface ID index
+[u4]18 // version
+[anchr]associated_interface_array
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]4 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[u4]19
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data
new file mode 100644
index 0000000000..2e163be160
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data
@@ -0,0 +1,36 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]associated_interface_array // num_bytes
+[u4]2 // num_elements
+[u4]2 // interface ID index
+[u4]14 // version
+[u4]0xFFFFFFFF // interface ID index: Unexpected invalid
+ // value.
+[u4]18 // version
+[anchr]associated_interface_array
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]4 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[u4]19
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected
new file mode 100644
index 0000000000..d8eda1f573
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data
new file mode 100644
index 0000000000..4a63003e19
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data
@@ -0,0 +1,38 @@
+[dist4]message_header // num_bytes
+[u4]2 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[dist8]payload
+[dist8]payload_interface_ids
+[anchr]message_header
+
+[anchr]payload
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]associated_interface_array // num_bytes
+[u4]3 // num_elements
+[u4]2 // interface ID index
+[u4]14 // version
+[u4]3 // interface ID index
+[u4]0 // version
+[u4]0 // interface ID index : It is smaller than
+ // the first element above, which is wrong.
+[u4]18 // version
+[anchr]associated_interface_array
+
+[anchr]payload_interface_ids
+[dist4]interface_id_array // num_bytes
+[u4]4 // num_elements : It is okay to have IDs that are not
+ // referred to.
+[u4]4
+[u4]5
+[u4]8
+[u4]19
+[anchr]interface_id_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected
new file mode 100644
index 0000000000..420b4210e2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_INTERFACE_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data
new file mode 100644
index 0000000000..30032a172b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data
@@ -0,0 +1,7 @@
+[dist4]message_header // num_bytes
+[u4]0 // version number
+[u4]0 // interface ID
+[u4]2 // There is no Method2
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected
new file mode 100644
index 0000000000..a32d895c31
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data
new file mode 100644
index 0000000000..68899f4650
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data
@@ -0,0 +1,2 @@
+[u4]24 // num_bytes: Bigger than the total size of the message.
+[u4]0 // version
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data
new file mode 100644
index 0000000000..21e7fbc02f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data
@@ -0,0 +1 @@
+0x00
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data
new file mode 100644
index 0000000000..dfb2dd262d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]3 // flags: This combination is illegal.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data
new file mode 100644
index 0000000000..27804a8390
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]1 // flags: This is a response message which expects to
+ // have a request ID.
+[u4]0 // padding
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected
new file mode 100644
index 0000000000..083db1ad27
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data
new file mode 100644
index 0000000000..6302baeec1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data
@@ -0,0 +1,7 @@
+[dist4]message_header // num_bytes
+[u4]0 // version number
+[u4]0 // interface ID
+[u4]9999 // There is no Method9999.
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected
new file mode 100644
index 0000000000..a32d895c31
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data
new file mode 100644
index 0000000000..2fd0fcd452
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data
@@ -0,0 +1,6 @@
+[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow.
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]0 // flags
+[u4]0 // padding
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data
new file mode 100644
index 0000000000..f58eca94df
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data
@@ -0,0 +1,4 @@
+[dist4]message_header // num_bytes: Less than the minimal size of message
+ // header.
+[u4]0 // version
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data
new file mode 100644
index 0000000000..e98f66f147
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data
@@ -0,0 +1,6 @@
+[u4]0 // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]0 // flags
+[u4]0 // padding
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data
new file mode 100644
index 0000000000..df9e418105
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data
@@ -0,0 +1,9 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]0 // flags
+[u4]0 // padding
+[u8]0 // Extra bytes that result in mismatched |num_bytes| and
+ // |version|.
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data
new file mode 100644
index 0000000000..e2c574eea5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data
@@ -0,0 +1,10 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]1 // flags
+[u4]0 // padding
+[u8]0 // request_id
+[u8]0 // Extra bytes that result in mismatched |num_bytes| and
+ // |version|.
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data
new file mode 100644
index 0000000000..f7a321b6f8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data
@@ -0,0 +1,7 @@
+[dist4]message_header // num_bytes
+[u4]8 // version: |num_bytes| is too small for |version|.
+[u4]0 // interface ID
+[u4]0x80000000 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data
new file mode 100644
index 0000000000..841da5e360
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[f]-1 // param0
+[u4]0 // padding
+[anchr]method0_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data
new file mode 100644
index 0000000000..cff6a3066c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data
@@ -0,0 +1,11 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[u4]16 // num_bytes: Incomplete struct.
+[u4]0 // version
+[f]-1 // param0
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data
new file mode 100644
index 0000000000..3f03ab2d04
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data
@@ -0,0 +1,9 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[u4]16 // num_bytes: Incomplete struct header.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data
new file mode 100644
index 0000000000..7aee806966
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]2 // flags: kMessageIsResponse is set in a request.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data
new file mode 100644
index 0000000000..5448c5f91b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data
@@ -0,0 +1,9 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]1 // flags: kMessageExpectsResponse is set in a request
+ // for a method that does not take a response.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data
new file mode 100644
index 0000000000..4a3e2fea2e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data
@@ -0,0 +1,12 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow.
+[u4]0 // version
+[f]-1 // param0
+[u4]0 // padding
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data
new file mode 100644
index 0000000000..fa4c555096
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data
@@ -0,0 +1,11 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method0_params // num_bytes: Less than the minimal size that we expect.
+[u4]0 // version
+[anchr]method0_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data
new file mode 100644
index 0000000000..d62206ed15
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data
@@ -0,0 +1,12 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[u4]4 // num_bytes: Less than the size of struct header.
+[u4]0 // version
+[f]-1 // param0
+[u4]0 // padding
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data
new file mode 100644
index 0000000000..5dca2fe238
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data
@@ -0,0 +1,48 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]10 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method10_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method10_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[dist8]key_string_1
+[dist8]key_string_2
+[anchr]key_array_member
+
+[anchr]key_string_1
+[dist4]key_string_1_member // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]key_string_1_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]key_string_2
+[dist4]key_string_2_member // num_bytes
+[u4]5 // num_elements
+5 6 7 8 9
+[anchr]key_string_2_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+1 2
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data
new file mode 100644
index 0000000000..f64fbc388b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data
@@ -0,0 +1,48 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]10 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method10_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method10_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[dist8]key_string_1
+[dist8]key_string_2
+[anchr]key_array_member
+
+[anchr]key_string_1
+[dist4]key_string_1_member // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]key_string_1_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]key_string_2
+[dist4]key_string_2_member // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]key_string_2_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+1 2
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data
new file mode 100644
index 0000000000..3a99dc2ab8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data
@@ -0,0 +1,25 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]10 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method10_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method10_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[u8]0 // null keys array
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+1 2
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data
new file mode 100644
index 0000000000..459d806dac
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data
@@ -0,0 +1,40 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]10 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method10_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method10_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[u8]0 // null values array
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[dist8]key_string_1
+[dist8]key_string_2
+[anchr]key_array_member
+
+[anchr]key_string_1
+[dist4]key_string_1_member // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]key_string_1_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]key_string_2
+[dist4]key_string_2_member // num_bytes
+[u4]5 // num_elements
+5 6 7 8 9
+[anchr]key_string_2_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data
new file mode 100644
index 0000000000..9127a26c25
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data
@@ -0,0 +1,40 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]10 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method10_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method10_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[dist8]key_string_1
+[u8]0 // one null key
+[anchr]key_array_member
+
+[anchr]key_string_1
+[dist4]key_string_1_member // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]key_string_1_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+1 2
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data
new file mode 100644
index 0000000000..a2f903859f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data
@@ -0,0 +1,48 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]10 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method10_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method10_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[dist8]key_string_1
+[dist8]key_string_2
+[anchr]key_array_member
+
+[anchr]key_string_1
+[dist4]key_string_1_member // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]key_string_1_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]key_string_2
+[dist4]key_string_2_member // num_bytes
+[u4]5 // num_elements
+5 6 7 8 9
+[anchr]key_string_2_member
+
+[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]1 // num_elements
+1 // unequal size
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected
new file mode 100644
index 0000000000..2798d4861e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data
new file mode 100644
index 0000000000..781079bc39
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes
+[u4]0 // version
+[s4]123 // i
+[u4]0 // padding
+[anchr]struct_g
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data
new file mode 100644
index 0000000000..b9ab5bff51
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data
@@ -0,0 +1,20 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes
+[u4]1 // version
+[s4]123 // i
+[u4]0 // padding
+[u8]0 // struct_a
+[anchr]struct_g
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data
new file mode 100644
index 0000000000..7d61446321
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data
@@ -0,0 +1,20 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes
+[u4]2 // version
+[s4]123 // i
+[u4]0 // padding
+[u8]0 // struct_a
+[anchr]struct_g
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data
new file mode 100644
index 0000000000..3c3ee12717
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data
@@ -0,0 +1,28 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes
+[u4]3 // version
+[s4]123 // i
+[b]00000001 // b
+0 0 0 // padding
+[u8]0 // struct_a
+[dist8]str_ptr // str
+[anchr]struct_g
+
+[anchr]str_ptr
+[dist4]string // num_bytes
+[u4]2 // num_elements
+0 1
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data
new file mode 100644
index 0000000000..2e9fde6eeb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data
@@ -0,0 +1,30 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes
+[u4]5 // version: Newer than what the validator knows.
+ // It is okay that the size is the same as the latest
+ // version that the validator knows.
+[s4]123 // i
+[b]00000001 // b
+0 0 0 // padding
+[u8]0 // struct_a
+[dist8]str_ptr // str
+[anchr]struct_g
+
+[anchr]str_ptr
+[dist4]string // num_bytes
+[u4]2 // num_elements
+0 1
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data
new file mode 100644
index 0000000000..9a956267e8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data
@@ -0,0 +1,30 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes
+[u4]5 // version: Newer than what the validator knows.
+[s4]123 // i
+[b]00000001 // b
+0 0 0 // padding
+[u8]0 // struct_a
+[dist8]str_ptr // str
+[u8]0 // unknown contents
+[u8]0 // unknown contents
+[anchr]struct_g
+
+[anchr]str_ptr
+[dist4]string // num_bytes
+[u4]2 // num_elements
+0 1
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data
new file mode 100644
index 0000000000..c2e5a8da66
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data
@@ -0,0 +1,21 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes: The size is too big for the version.
+[u4]1 // version
+[s4]123 // i
+[u4]0 // padding
+[u8]0 // struct_a
+[u8]0 // Unexpected contents that cause the mismatch.
+[anchr]struct_g
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data
new file mode 100644
index 0000000000..edfe5fa8b1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]11 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method11_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method11_params
+
+[anchr]param0_ptr
+[dist4]struct_g // num_bytes: The size is too small for the version.
+[u4]2 // version
+[s4]123 // i
+[u4]0 // padding
+[anchr]struct_g
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data
new file mode 100644
index 0000000000..13135aecad
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data
@@ -0,0 +1,9 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]12 // name
+[u4]0 // flags: kMessageExpectsResponse is not set but
+ // expected.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data
new file mode 100644
index 0000000000..51973be114
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data
@@ -0,0 +1,17 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]13 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method13_params // num_bytes
+[u4]0 // version
+[u4]0xFFFFFFFF // param0
+[u4]1234
+[u4]65535 // param1
+[u4]0xFFFFFFFF // param2
+[u4]3242
+[u4]0 // padding
+[anchr]method13_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data
new file mode 100644
index 0000000000..b739731093
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data
@@ -0,0 +1,19 @@
+[handles]2
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]13 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method13_params // num_bytes
+[u4]0 // version
+[u4]0 // param0
+[u4]1234
+[u4]65535 // param1
+[u4]1 // param2
+[u4]3242
+[u4]0 // padding
+[anchr]method13_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data
new file mode 100644
index 0000000000..14448497f1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]14 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method14_params // num_bytes
+[u4]0 // version
+[u4]0 // param0
+[u4]1 // param1
+[anchr]method14_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data
new file mode 100644
index 0000000000..50b9ea33b0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]14 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method14_params // num_bytes
+[u4]0 // version
+[u4]0 // param0
+[u4]0xFFFFFFFF // param1: Unknown value is okay for extensible enum.
+[anchr]method14_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data
new file mode 100644
index 0000000000..567f23b254
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data
@@ -0,0 +1,14 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]14 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method14_params // num_bytes
+[u4]0 // version
+[u4]0xFFFFFFFF // param0: Unknown value is not allowed for
+ // non-extensible enum.
+[u4]2 // param1
+[anchr]method14_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected
new file mode 100644
index 0000000000..9ef4ce3fdd
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNKNOWN_ENUM_VALUE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data
new file mode 100644
index 0000000000..21af8a3df1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data
@@ -0,0 +1,22 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]15 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method15_params // num_bytes
+[u4]0 // version
+[dist8]enum_array_0 // param0
+[u8]0 // param1
+[anchr]method15_params
+
+[anchr]enum_array_0
+[dist4]enum_array_0_member // num_bytes
+[u4]0 // num_elements
+[anchr]enum_array_0_member
+
+[u8]0x5678 // This is not part of the array above (which is
+ // empty), so enum validation shouldn't be done on
+ // it.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data
new file mode 100644
index 0000000000..c418d8994c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data
@@ -0,0 +1,27 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]15 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method15_params // num_bytes
+[u4]0 // version
+[dist8]enum_array_0 // param0
+[dist8]enum_array_1 // param1
+[anchr]method15_params
+
+[anchr]enum_array_0
+[dist4]enum_array_0_member // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]enum_array_0_member
+
+[anchr]enum_array_1
+[dist4]enum_array_1_member // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]enum_array_1_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data
new file mode 100644
index 0000000000..b6be6d93c6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data
@@ -0,0 +1,20 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]15 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method15_params // num_bytes
+[u4]0 // version
+[u8]0 // param0
+[dist8]enum_array_1 // param1
+[anchr]method15_params
+
+[anchr]enum_array_1
+[dist4]enum_array_1_member // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]0x1234 // Unknown value is okay for extensible enum.
+[anchr]enum_array_1_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data
new file mode 100644
index 0000000000..0a46e0a4a2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data
@@ -0,0 +1,21 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]15 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method15_params // num_bytes
+[u4]0 // version
+[dist8]enum_array_0 // param0
+[u8]0 // param1
+[anchr]method15_params
+
+[anchr]enum_array_0
+[dist4]enum_array_0_member // num_bytes
+[u4]2 // num_elements
+[u4]1
+[u4]0x5678 // Unknown value is not allowed for non-extensible
+ // enum.
+[anchr]enum_array_0_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected
new file mode 100644
index 0000000000..9ef4ce3fdd
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNKNOWN_ENUM_VALUE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data
new file mode 100644
index 0000000000..0425ea72f6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data
@@ -0,0 +1,34 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]16 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method16_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method16_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[u4]0x5678 // Unknown value is not allowed for non-extensible
+ // enum.
+[u4]1
+[anchr]key_array_member
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+[u4]1
+[u4]2
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected
new file mode 100644
index 0000000000..9ef4ce3fdd
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNKNOWN_ENUM_VALUE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data
new file mode 100644
index 0000000000..2c2ea26d93
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data
@@ -0,0 +1,34 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]16 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method16_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method16_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[u4]1
+[u4]2
+[anchr]key_array_member
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+[u4]0x5678 // Unknown value is not allowed for non-extensible
+ // enum.
+[u4]1
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected
new file mode 100644
index 0000000000..9ef4ce3fdd
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNKNOWN_ENUM_VALUE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data
new file mode 100644
index 0000000000..48807ab36f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data
@@ -0,0 +1,23 @@
+[handles]10 // Larger than the number of handles that we know about is okay.
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]17 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method17_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method17_params
+
+[anchr]param0_ptr
+[dist4]interface_array // num_bytes
+[u4]2 // num_elements
+[u4]4 // handle
+[u4]14 // version
+[u4]5 // handle
+[u4]18 // version
+[anchr]interface_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data
new file mode 100644
index 0000000000..e549a10e3a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data
@@ -0,0 +1,24 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]17 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method17_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method17_params
+
+[anchr]param0_ptr
+[dist4]interface_array // num_bytes
+[u4]2 // num_elements
+[u4]4 // handle
+[u4]14 // version
+[u4]10 // handle: It is outside of the valid encoded handle
+ // range [0, 10)
+[u4]18 // version
+[anchr]interface_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected
new file mode 100644
index 0000000000..eef8e38a84
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data
new file mode 100644
index 0000000000..1ce1d92fe5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data
@@ -0,0 +1,23 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]17 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method17_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method17_params
+
+[anchr]param0_ptr
+[dist4]interface_array // num_bytes
+[u4]2 // num_elements
+[u4]4 // handle
+[u4]14 // version
+[u4]0xFFFFFFFF // handle: An unexpected invalid handle.
+[u4]18 // version
+[anchr]interface_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected
new file mode 100644
index 0000000000..6768236f6e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data
new file mode 100644
index 0000000000..663796d50e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data
@@ -0,0 +1,14 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]18 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method18_params // num_bytes
+[u4]0 // version
+[u4]0 // param0: Size 0 indicating the inlined union is null.
+[u4]0 // param0: Tag field ignored.
+[u8]0 // param0: Payload field ignored.
+[anchr]method18_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data
new file mode 100644
index 0000000000..96e52d5ee2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data
@@ -0,0 +1,612 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]19 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]struct_a1 // num_bytes
+[u4]0 // version
+[dist8]struct_a2_ptr // struct_a2
+[anchr]struct_a1
+
+[anchr]struct_a2_ptr
+[dist4]struct_a2 // num_bytes
+[u4]0 // version
+[dist8]struct_a3_ptr // struct_a2
+[anchr]struct_a2
+
+[anchr]struct_a3_ptr
+[dist4]struct_a3 // num_bytes
+[u4]0 // version
+[dist8]struct_a4_ptr // struct_a3
+[anchr]struct_a3
+
+[anchr]struct_a4_ptr
+[dist4]struct_a4 // num_bytes
+[u4]0 // version
+[dist8]struct_a5_ptr // struct_a4
+[anchr]struct_a4
+
+[anchr]struct_a5_ptr
+[dist4]struct_a5 // num_bytes
+[u4]0 // version
+[dist8]struct_a6_ptr // struct_a5
+[anchr]struct_a5
+
+[anchr]struct_a6_ptr
+[dist4]struct_a6 // num_bytes
+[u4]0 // version
+[dist8]struct_a7_ptr // struct_a6
+[anchr]struct_a6
+
+[anchr]struct_a7_ptr
+[dist4]struct_a7 // num_bytes
+[u4]0 // version
+[dist8]struct_a8_ptr // struct_a7
+[anchr]struct_a7
+
+[anchr]struct_a8_ptr
+[dist4]struct_a8 // num_bytes
+[u4]0 // version
+[dist8]struct_a9_ptr // struct_a8
+[anchr]struct_a8
+
+[anchr]struct_a9_ptr
+[dist4]struct_a9 // num_bytes
+[u4]0 // version
+[dist8]struct_a10_ptr // struct_a9
+[anchr]struct_a9
+
+[anchr]struct_a10_ptr
+[dist4]struct_a10 // num_bytes
+[u4]0 // version
+[dist8]struct_a11_ptr // struct_a10
+[anchr]struct_a10
+
+[anchr]struct_a11_ptr
+[dist4]struct_a11 // num_bytes
+[u4]0 // version
+[dist8]struct_a12_ptr // struct_a11
+[anchr]struct_a11
+
+[anchr]struct_a12_ptr
+[dist4]struct_a12 // num_bytes
+[u4]0 // version
+[dist8]struct_a13_ptr // struct_a12
+[anchr]struct_a12
+
+[anchr]struct_a13_ptr
+[dist4]struct_a13 // num_bytes
+[u4]0 // version
+[dist8]struct_a14_ptr // struct_a13
+[anchr]struct_a13
+
+[anchr]struct_a14_ptr
+[dist4]struct_a14 // num_bytes
+[u4]0 // version
+[dist8]struct_a15_ptr // struct_a14
+[anchr]struct_a14
+
+[anchr]struct_a15_ptr
+[dist4]struct_a15 // num_bytes
+[u4]0 // version
+[dist8]struct_a16_ptr // struct_a15
+[anchr]struct_a15
+
+[anchr]struct_a16_ptr
+[dist4]struct_a16 // num_bytes
+[u4]0 // version
+[dist8]struct_a17_ptr // struct_a16
+[anchr]struct_a16
+
+[anchr]struct_a17_ptr
+[dist4]struct_a17 // num_bytes
+[u4]0 // version
+[dist8]struct_a18_ptr // struct_a17
+[anchr]struct_a17
+
+[anchr]struct_a18_ptr
+[dist4]struct_a18 // num_bytes
+[u4]0 // version
+[dist8]struct_a19_ptr // struct_a18
+[anchr]struct_a18
+
+[anchr]struct_a19_ptr
+[dist4]struct_a19 // num_bytes
+[u4]0 // version
+[dist8]struct_a20_ptr // struct_a19
+[anchr]struct_a19
+
+[anchr]struct_a20_ptr
+[dist4]struct_a20 // num_bytes
+[u4]0 // version
+[dist8]struct_a21_ptr // struct_a20
+[anchr]struct_a20
+
+[anchr]struct_a21_ptr
+[dist4]struct_a21 // num_bytes
+[u4]0 // version
+[dist8]struct_a22_ptr // struct_a21
+[anchr]struct_a21
+
+[anchr]struct_a22_ptr
+[dist4]struct_a22 // num_bytes
+[u4]0 // version
+[dist8]struct_a23_ptr // struct_a22
+[anchr]struct_a22
+
+[anchr]struct_a23_ptr
+[dist4]struct_a23 // num_bytes
+[u4]0 // version
+[dist8]struct_a24_ptr // struct_a23
+[anchr]struct_a23
+
+[anchr]struct_a24_ptr
+[dist4]struct_a24 // num_bytes
+[u4]0 // version
+[dist8]struct_a25_ptr // struct_a24
+[anchr]struct_a24
+
+[anchr]struct_a25_ptr
+[dist4]struct_a25 // num_bytes
+[u4]0 // version
+[dist8]struct_a26_ptr // struct_a25
+[anchr]struct_a25
+
+[anchr]struct_a26_ptr
+[dist4]struct_a26 // num_bytes
+[u4]0 // version
+[dist8]struct_a27_ptr // struct_a26
+[anchr]struct_a26
+
+[anchr]struct_a27_ptr
+[dist4]struct_a27 // num_bytes
+[u4]0 // version
+[dist8]struct_a28_ptr // struct_a27
+[anchr]struct_a27
+
+[anchr]struct_a28_ptr
+[dist4]struct_a28 // num_bytes
+[u4]0 // version
+[dist8]struct_a29_ptr // struct_a28
+[anchr]struct_a28
+
+[anchr]struct_a29_ptr
+[dist4]struct_a29 // num_bytes
+[u4]0 // version
+[dist8]struct_a30_ptr // struct_a29
+[anchr]struct_a29
+
+[anchr]struct_a30_ptr
+[dist4]struct_a30 // num_bytes
+[u4]0 // version
+[dist8]struct_a31_ptr // struct_a30
+[anchr]struct_a30
+
+[anchr]struct_a31_ptr
+[dist4]struct_a31 // num_bytes
+[u4]0 // version
+[dist8]struct_a32_ptr // struct_a31
+[anchr]struct_a31
+
+[anchr]struct_a32_ptr
+[dist4]struct_a32 // num_bytes
+[u4]0 // version
+[dist8]struct_a33_ptr // struct_a32
+[anchr]struct_a32
+
+[anchr]struct_a33_ptr
+[dist4]struct_a33 // num_bytes
+[u4]0 // version
+[dist8]struct_a34_ptr // struct_a33
+[anchr]struct_a33
+
+[anchr]struct_a34_ptr
+[dist4]struct_a34 // num_bytes
+[u4]0 // version
+[dist8]struct_a35_ptr // struct_a34
+[anchr]struct_a34
+
+[anchr]struct_a35_ptr
+[dist4]struct_a35 // num_bytes
+[u4]0 // version
+[dist8]struct_a36_ptr // struct_a35
+[anchr]struct_a35
+
+[anchr]struct_a36_ptr
+[dist4]struct_a36 // num_bytes
+[u4]0 // version
+[dist8]struct_a37_ptr // struct_a36
+[anchr]struct_a36
+
+[anchr]struct_a37_ptr
+[dist4]struct_a37 // num_bytes
+[u4]0 // version
+[dist8]struct_a38_ptr // struct_a37
+[anchr]struct_a37
+
+[anchr]struct_a38_ptr
+[dist4]struct_a38 // num_bytes
+[u4]0 // version
+[dist8]struct_a39_ptr // struct_a38
+[anchr]struct_a38
+
+[anchr]struct_a39_ptr
+[dist4]struct_a39 // num_bytes
+[u4]0 // version
+[dist8]struct_a40_ptr // struct_a39
+[anchr]struct_a39
+
+[anchr]struct_a40_ptr
+[dist4]struct_a40 // num_bytes
+[u4]0 // version
+[dist8]struct_a41_ptr // struct_a40
+[anchr]struct_a40
+
+[anchr]struct_a41_ptr
+[dist4]struct_a41 // num_bytes
+[u4]0 // version
+[dist8]struct_a42_ptr // struct_a41
+[anchr]struct_a41
+
+[anchr]struct_a42_ptr
+[dist4]struct_a42 // num_bytes
+[u4]0 // version
+[dist8]struct_a43_ptr // struct_a42
+[anchr]struct_a42
+
+[anchr]struct_a43_ptr
+[dist4]struct_a43 // num_bytes
+[u4]0 // version
+[dist8]struct_a44_ptr // struct_a43
+[anchr]struct_a43
+
+[anchr]struct_a44_ptr
+[dist4]struct_a44 // num_bytes
+[u4]0 // version
+[dist8]struct_a45_ptr // struct_a44
+[anchr]struct_a44
+
+[anchr]struct_a45_ptr
+[dist4]struct_a45 // num_bytes
+[u4]0 // version
+[dist8]struct_a46_ptr // struct_a45
+[anchr]struct_a45
+
+[anchr]struct_a46_ptr
+[dist4]struct_a46 // num_bytes
+[u4]0 // version
+[dist8]struct_a47_ptr // struct_a46
+[anchr]struct_a46
+
+[anchr]struct_a47_ptr
+[dist4]struct_a47 // num_bytes
+[u4]0 // version
+[dist8]struct_a48_ptr // struct_a47
+[anchr]struct_a47
+
+[anchr]struct_a48_ptr
+[dist4]struct_a48 // num_bytes
+[u4]0 // version
+[dist8]struct_a49_ptr // struct_a48
+[anchr]struct_a48
+
+[anchr]struct_a49_ptr
+[dist4]struct_a49 // num_bytes
+[u4]0 // version
+[dist8]struct_a50_ptr // struct_a49
+[anchr]struct_a49
+
+[anchr]struct_a50_ptr
+[dist4]struct_a50 // num_bytes
+[u4]0 // version
+[dist8]struct_a51_ptr // struct_a50
+[anchr]struct_a50
+
+[anchr]struct_a51_ptr
+[dist4]struct_a51 // num_bytes
+[u4]0 // version
+[dist8]struct_a52_ptr // struct_a51
+[anchr]struct_a51
+
+[anchr]struct_a52_ptr
+[dist4]struct_a52 // num_bytes
+[u4]0 // version
+[dist8]struct_a53_ptr // struct_a52
+[anchr]struct_a52
+
+[anchr]struct_a53_ptr
+[dist4]struct_a53 // num_bytes
+[u4]0 // version
+[dist8]struct_a54_ptr // struct_a53
+[anchr]struct_a53
+
+[anchr]struct_a54_ptr
+[dist4]struct_a54 // num_bytes
+[u4]0 // version
+[dist8]struct_a55_ptr // struct_a54
+[anchr]struct_a54
+
+[anchr]struct_a55_ptr
+[dist4]struct_a55 // num_bytes
+[u4]0 // version
+[dist8]struct_a56_ptr // struct_a55
+[anchr]struct_a55
+
+[anchr]struct_a56_ptr
+[dist4]struct_a56 // num_bytes
+[u4]0 // version
+[dist8]struct_a57_ptr // struct_a56
+[anchr]struct_a56
+
+[anchr]struct_a57_ptr
+[dist4]struct_a57 // num_bytes
+[u4]0 // version
+[dist8]struct_a58_ptr // struct_a57
+[anchr]struct_a57
+
+[anchr]struct_a58_ptr
+[dist4]struct_a58 // num_bytes
+[u4]0 // version
+[dist8]struct_a59_ptr // struct_a58
+[anchr]struct_a58
+
+[anchr]struct_a59_ptr
+[dist4]struct_a59 // num_bytes
+[u4]0 // version
+[dist8]struct_a60_ptr // struct_a59
+[anchr]struct_a59
+
+[anchr]struct_a60_ptr
+[dist4]struct_a60 // num_bytes
+[u4]0 // version
+[dist8]struct_a61_ptr // struct_a60
+[anchr]struct_a60
+
+[anchr]struct_a61_ptr
+[dist4]struct_a61 // num_bytes
+[u4]0 // version
+[dist8]struct_a62_ptr // struct_a61
+[anchr]struct_a61
+
+[anchr]struct_a62_ptr
+[dist4]struct_a62 // num_bytes
+[u4]0 // version
+[dist8]struct_a63_ptr // struct_a62
+[anchr]struct_a62
+
+[anchr]struct_a63_ptr
+[dist4]struct_a63 // num_bytes
+[u4]0 // version
+[dist8]struct_a64_ptr // struct_a63
+[anchr]struct_a63
+
+[anchr]struct_a64_ptr
+[dist4]struct_a64 // num_bytes
+[u4]0 // version
+[dist8]struct_a65_ptr // struct_a64
+[anchr]struct_a64
+
+[anchr]struct_a65_ptr
+[dist4]struct_a65 // num_bytes
+[u4]0 // version
+[dist8]struct_a66_ptr // struct_a65
+[anchr]struct_a65
+
+[anchr]struct_a66_ptr
+[dist4]struct_a66 // num_bytes
+[u4]0 // version
+[dist8]struct_a67_ptr // struct_a66
+[anchr]struct_a66
+
+[anchr]struct_a67_ptr
+[dist4]struct_a67 // num_bytes
+[u4]0 // version
+[dist8]struct_a68_ptr // struct_a67
+[anchr]struct_a67
+
+[anchr]struct_a68_ptr
+[dist4]struct_a68 // num_bytes
+[u4]0 // version
+[dist8]struct_a69_ptr // struct_a68
+[anchr]struct_a68
+
+[anchr]struct_a69_ptr
+[dist4]struct_a69 // num_bytes
+[u4]0 // version
+[dist8]struct_a70_ptr // struct_a69
+[anchr]struct_a69
+
+[anchr]struct_a70_ptr
+[dist4]struct_a70 // num_bytes
+[u4]0 // version
+[dist8]struct_a71_ptr // struct_a70
+[anchr]struct_a70
+
+[anchr]struct_a71_ptr
+[dist4]struct_a71 // num_bytes
+[u4]0 // version
+[dist8]struct_a72_ptr // struct_a71
+[anchr]struct_a71
+
+[anchr]struct_a72_ptr
+[dist4]struct_a72 // num_bytes
+[u4]0 // version
+[dist8]struct_a73_ptr // struct_a72
+[anchr]struct_a72
+
+[anchr]struct_a73_ptr
+[dist4]struct_a73 // num_bytes
+[u4]0 // version
+[dist8]struct_a74_ptr // struct_a73
+[anchr]struct_a73
+
+[anchr]struct_a74_ptr
+[dist4]struct_a74 // num_bytes
+[u4]0 // version
+[dist8]struct_a75_ptr // struct_a74
+[anchr]struct_a74
+
+[anchr]struct_a75_ptr
+[dist4]struct_a75 // num_bytes
+[u4]0 // version
+[dist8]struct_a76_ptr // struct_a75
+[anchr]struct_a75
+
+[anchr]struct_a76_ptr
+[dist4]struct_a76 // num_bytes
+[u4]0 // version
+[dist8]struct_a77_ptr // struct_a76
+[anchr]struct_a76
+
+[anchr]struct_a77_ptr
+[dist4]struct_a77 // num_bytes
+[u4]0 // version
+[dist8]struct_a78_ptr // struct_a77
+[anchr]struct_a77
+
+[anchr]struct_a78_ptr
+[dist4]struct_a78 // num_bytes
+[u4]0 // version
+[dist8]struct_a79_ptr // struct_a78
+[anchr]struct_a78
+
+[anchr]struct_a79_ptr
+[dist4]struct_a79 // num_bytes
+[u4]0 // version
+[dist8]struct_a80_ptr // struct_a79
+[anchr]struct_a79
+
+[anchr]struct_a80_ptr
+[dist4]struct_a80 // num_bytes
+[u4]0 // version
+[dist8]struct_a81_ptr // struct_a80
+[anchr]struct_a80
+
+[anchr]struct_a81_ptr
+[dist4]struct_a81 // num_bytes
+[u4]0 // version
+[dist8]struct_a82_ptr // struct_a81
+[anchr]struct_a81
+
+[anchr]struct_a82_ptr
+[dist4]struct_a82 // num_bytes
+[u4]0 // version
+[dist8]struct_a83_ptr // struct_a82
+[anchr]struct_a82
+
+[anchr]struct_a83_ptr
+[dist4]struct_a83 // num_bytes
+[u4]0 // version
+[dist8]struct_a84_ptr // struct_a83
+[anchr]struct_a83
+
+[anchr]struct_a84_ptr
+[dist4]struct_a84 // num_bytes
+[u4]0 // version
+[dist8]struct_a85_ptr // struct_a84
+[anchr]struct_a84
+
+[anchr]struct_a85_ptr
+[dist4]struct_a85 // num_bytes
+[u4]0 // version
+[dist8]struct_a86_ptr // struct_a85
+[anchr]struct_a85
+
+[anchr]struct_a86_ptr
+[dist4]struct_a86 // num_bytes
+[u4]0 // version
+[dist8]struct_a87_ptr // struct_a86
+[anchr]struct_a86
+
+[anchr]struct_a87_ptr
+[dist4]struct_a87 // num_bytes
+[u4]0 // version
+[dist8]struct_a88_ptr // struct_a87
+[anchr]struct_a87
+
+[anchr]struct_a88_ptr
+[dist4]struct_a88 // num_bytes
+[u4]0 // version
+[dist8]struct_a89_ptr // struct_a88
+[anchr]struct_a88
+
+[anchr]struct_a89_ptr
+[dist4]struct_a89 // num_bytes
+[u4]0 // version
+[dist8]struct_a90_ptr // struct_a89
+[anchr]struct_a89
+
+[anchr]struct_a90_ptr
+[dist4]struct_a90 // num_bytes
+[u4]0 // version
+[dist8]struct_a91_ptr // struct_a90
+[anchr]struct_a90
+
+[anchr]struct_a91_ptr
+[dist4]struct_a91 // num_bytes
+[u4]0 // version
+[dist8]struct_a92_ptr // struct_a91
+[anchr]struct_a91
+
+[anchr]struct_a92_ptr
+[dist4]struct_a92 // num_bytes
+[u4]0 // version
+[dist8]struct_a93_ptr // struct_a92
+[anchr]struct_a92
+
+[anchr]struct_a93_ptr
+[dist4]struct_a93 // num_bytes
+[u4]0 // version
+[dist8]struct_a94_ptr // struct_a93
+[anchr]struct_a93
+
+[anchr]struct_a94_ptr
+[dist4]struct_a94 // num_bytes
+[u4]0 // version
+[dist8]struct_a95_ptr // struct_a94
+[anchr]struct_a94
+
+[anchr]struct_a95_ptr
+[dist4]struct_a95 // num_bytes
+[u4]0 // version
+[dist8]struct_a96_ptr // struct_a95
+[anchr]struct_a95
+
+[anchr]struct_a96_ptr
+[dist4]struct_a96 // num_bytes
+[u4]0 // version
+[dist8]struct_a97_ptr // struct_a96
+[anchr]struct_a96
+
+[anchr]struct_a97_ptr
+[dist4]struct_a97 // num_bytes
+[u4]0 // version
+[dist8]struct_a98_ptr // struct_a97
+[anchr]struct_a97
+
+[anchr]struct_a98_ptr
+[dist4]struct_a98 // num_bytes
+[u4]0 // version
+[dist8]struct_a99_ptr // struct_a98
+[anchr]struct_a98
+
+[anchr]struct_a99_ptr
+[dist4]struct_a99 // num_bytes
+[u4]0 // version
+[dist8]struct_a100_ptr // struct_a99
+[anchr]struct_a99
+
+[anchr]struct_a100_ptr
+[dist4]struct_a100 // num_bytes
+[u4]0 // version
+[u8]0 // struct_a100
+[anchr]struct_a100
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected
new file mode 100644
index 0000000000..81d6cd84e4
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MAX_RECURSION_DEPTH \ No newline at end of file
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data
new file mode 100644
index 0000000000..b6c201a948
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data
@@ -0,0 +1,18 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]1 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method1_params
+
+[anchr]param0_ptr
+[dist4]struct_a // num_bytes
+[u4]0 // version
+[u8]1234 // i
+[anchr]struct_a
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data
new file mode 100644
index 0000000000..ec39b71d03
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data
@@ -0,0 +1,20 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]1 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method1_params
+
+[u1]0 // Causes the following struct to be misaligned.
+
+[anchr]param0_ptr
+[dist4]struct_a // num_bytes
+[u4]0 // version
+[u8]1234 // i
+[anchr]struct_a
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected
new file mode 100644
index 0000000000..acca999b3f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MISALIGNED_OBJECT
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data
new file mode 100644
index 0000000000..6d9205093b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]1 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]0 // version
+[u8]0xFFFFFFFFFFFFFFFF // param0: Test whether decoding the pointer causes
+ // overflow.
+[anchr]method1_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected
new file mode 100644
index 0000000000..23abb8cbb7
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data
new file mode 100644
index 0000000000..569733bcb3
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data
@@ -0,0 +1,12 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]1 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]0 // version
+[u8]0 // param0: An unexpected null pointer.
+[anchr]method1_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data
new file mode 100644
index 0000000000..8ec608bcd1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data
@@ -0,0 +1,57 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]20 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method20_params // num_bytes
+[u4]0 // version
+[dist8]map_data_ptr // param0
+[anchr]method20_params
+
+[anchr]map_data_ptr
+[dist4]map_data_struct_header // num_bytes
+[u4]0 // version
+[dist8]key_array_ptr
+[dist8]value_array_ptr
+[anchr]map_data_struct_header
+
+[anchr]key_array_ptr
+[dist4]key_array_member // num_bytes
+[u4]2 // num_elements
+[dist8]key_struct_b_1
+[dist8]key_struct_b_2
+[anchr]key_array_member
+
+[anchr]key_struct_b_1
+[dist4]key_struct_b_1_member // num_bytes
+[u4]0 // version
+[dist8]key_struct_a_1 // struct_a
+[anchr]key_struct_b_1_member
+
+[anchr]key_struct_a_1
+[dist4]key_struct_a_1_member // num_bytes
+[u4]0 // version
+[u8]1234 // i
+[anchr]key_struct_a_1_member
+
+[anchr]key_struct_b_2
+[dist4]key_struct_b_2_member // num_bytes
+[u4]0 // version
+[dist8]key_struct_a_2 // struct_a
+[anchr]key_struct_b_2_member
+
+[anchr]key_struct_a_2
+[dist4]key_struct_a_2_member // num_bytes
+[u4]0 // version
+[u8]5678 // i
+[anchr]key_struct_a_2_member
+
+[anchr]value_array_ptr
+[dist4]value_array_member // num_bytes
+[u4]2 // num_elements
+[u1]1
+[u1]2
+[anchr]value_array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data
new file mode 100644
index 0000000000..d3ae88e3e6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]21 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method21_params // num_bytes
+[u4]0 // version
+[u4]7 // param0. All values are valid for an extensible enum.
+[u4]0 // padding
+[anchr]method21_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data
new file mode 100644
index 0000000000..414785ceb1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data
@@ -0,0 +1,14 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]22 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method21_params // num_bytes
+[u4]0 // version
+[u4]0 // param0. No values are valid for an empty
+ // non-extensible enum.
+[u4]0 // padding
+[anchr]method21_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected
new file mode 100644
index 0000000000..9ef4ce3fdd
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNKNOWN_ENUM_VALUE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data
new file mode 100644
index 0000000000..40719f5708
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data
@@ -0,0 +1,34 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]2 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]0 // version
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+[u8]0 // Having extra bytes in the middle is okay if the following objects are
+ // still properly alignmented.
+
+[anchr]struct_a_ptr
+[dist4]struct_a_member // num_bytes
+[u4]0 // version
+[u8]12345 // i
+[anchr]struct_a_member
+
+[anchr]param1_ptr
+[dist4]struct_a_param // num_bytes
+[u4]0 // version
+[u8]67890 // i
+[anchr]struct_a_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data
new file mode 100644
index 0000000000..ef6525b008
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data
@@ -0,0 +1,27 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]2 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]0 // version
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+// There are two pointers pointing to the same struct.
+[anchr]struct_a_ptr
+[anchr]param1_ptr
+[dist4]struct_a // num_bytes
+[u4]0 // version
+[u8]12345 // i
+[anchr]struct_a
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data
new file mode 100644
index 0000000000..58b25a1aa9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]2 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]0 // version
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+[anchr]struct_a_ptr
+[dist4]struct_a_member // num_bytes
+[u4]0 // version
+
+[anchr]param1_ptr
+// The following |num_bytes| and |version| fields are also the |i| field of the
+// previous struct.
+[dist4]struct_a_param // num_bytes
+[u4]0 // version
+[anchr]struct_a_member
+[u8]67890 // i
+[anchr]struct_a_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data
new file mode 100644
index 0000000000..3038ed8097
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data
@@ -0,0 +1,34 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]2 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]0 // version
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+// The following two structs are arranged in wrong order.
+
+[anchr]param1_ptr
+[dist4]struct_a_param // num_bytes
+[u4]0 // version
+[u8]67890 // i
+[anchr]struct_a_param
+
+[anchr]struct_a_ptr
+[dist4]struct_a_member // num_bytes
+[u4]0 // version
+[u8]12345 // i
+[anchr]struct_a_member
+
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data
new file mode 100644
index 0000000000..681463663f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data
@@ -0,0 +1,18 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow.
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data
new file mode 100644
index 0000000000..45021c0634
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data
@@ -0,0 +1,18 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]7 // num_bytes: Less than the size of array header.
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data
new file mode 100644
index 0000000000..3d3870298f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data
@@ -0,0 +1,20 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]array // num_bytes: Less than the size needed (array header + 12 boolean
+ // values).
+[u4]12 // num_elements
+[b]01010101
+[anchr]array
+[b]00001111
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data
new file mode 100644
index 0000000000..2f9e091b04
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[u8]0xFFFFFFFFFFFFFFFF // param0: Test whether decoding the pointer causes
+ // overflow.
+[anchr]method3_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected
new file mode 100644
index 0000000000..23abb8cbb7
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data
new file mode 100644
index 0000000000..ad26763f5f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]array // num_bytes
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
+[anchr]array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data
new file mode 100644
index 0000000000..d7734589ce
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data
@@ -0,0 +1,16 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]16 // num_bytes
+[u1]0 // num_elements: Incomplete array.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data
new file mode 100644
index 0000000000..ca462a5758
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data
@@ -0,0 +1,15 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]16 // num_bytes: Incomplete array header.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data
new file mode 100644
index 0000000000..5adfbbaa97
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data
@@ -0,0 +1,21 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[u2]0 // Causes the following array to be misaligned.
+
+[anchr]param0_ptr
+[dist4]array // num_bytes
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
+[anchr]array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected
new file mode 100644
index 0000000000..acca999b3f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MISALIGNED_OBJECT
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data
new file mode 100644
index 0000000000..0f96c4bd58
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data
@@ -0,0 +1,12 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]3 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]0 // version
+[u8]0 // param0: An unexpected null pointer.
+[anchr]method3_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data
new file mode 100644
index 0000000000..84943d29ec
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data
@@ -0,0 +1,34 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]4 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method4_params // num_bytes: Larger than what we know is okay.
+[u4]3 // version: Larger than what we know is okay.
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[u8]0 // unknown
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]0 // version
+[dist8]array_ptr // array
+[anchr]struct_c
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
+
+[u4]0 [u1]0 // Padding to make the next array aligned properly.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data
new file mode 100644
index 0000000000..2f84185552
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data
@@ -0,0 +1,26 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]4 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method4_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]0 // version
+[dist8]array_ptr // array
+[anchr]struct_c
+
+[anchr]param1_ptr
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data
new file mode 100644
index 0000000000..d863e64a12
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]4 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method4_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]0 // version
+[dist8]array_ptr // array
+[anchr]struct_c
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+
+[anchr]param1_ptr
+// The first three bytes of |num_bytes| are also the elements of the previous
+// array.
+[dist4]array_param // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_param
+[anchr]array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data
new file mode 100644
index 0000000000..b61423a6d7
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data
@@ -0,0 +1,35 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]4 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method4_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]0 // version
+[dist8]array_ptr // array
+[anchr]struct_c
+
+// The following two arrays are arranged in wrong order.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_param
+
+[u4]0 [u2]0 // Padding to make the next array aligned properly.
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected
new file mode 100644
index 0000000000..779df88cf6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data
new file mode 100644
index 0000000000..dcec8952d1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data
@@ -0,0 +1,37 @@
+[handles]10 // Larger than the number of handles that we know about is okay.
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]5 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]0 // version
+[dist8]struct_d_ptr // struct_d
+[u4]3 // data_pipe_consumer
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]0 // version
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data
new file mode 100644
index 0000000000..d4a82ed174
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data
@@ -0,0 +1,38 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]5 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[u4]10 // param1: It is outside of the valid encoded handle
+ // range [0, 10).
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]0 // version
+[dist8]struct_d_ptr // struct_d
+[u4]3 // data_pipe_consumer
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]0 // version
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected
new file mode 100644
index 0000000000..eef8e38a84
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data
new file mode 100644
index 0000000000..9ee7a48c9c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data
@@ -0,0 +1,37 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]5 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]0 // version
+[dist8]struct_d_ptr // struct_d
+[u4]4 // data_pipe_consumer: The same value as |param1| above.
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]0 // version
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected
new file mode 100644
index 0000000000..eef8e38a84
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data
new file mode 100644
index 0000000000..cb01caeb1a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data
@@ -0,0 +1,37 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]5 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]0 // version
+[dist8]struct_d_ptr // struct_d
+[u4]3 // data_pipe_consumer
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]0 // version
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]1 // The two message pipe handles have the same value.
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected
new file mode 100644
index 0000000000..eef8e38a84
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data
new file mode 100644
index 0000000000..b06ae0a46b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data
@@ -0,0 +1,36 @@
+[handles]5
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]5 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]0 // version
+[dist8]struct_d_ptr // struct_d
+[s4]-1 // data_pipe_consumer: An unexpected invalid handle.
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]0 // version
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]1 // num_elements
+[u4]2
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected
new file mode 100644
index 0000000000..6768236f6e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data
new file mode 100644
index 0000000000..f641de0561
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data
@@ -0,0 +1,38 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]5 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[u4]9 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]0 // version
+[dist8]struct_d_ptr // struct_d
+[u4]1 // data_pipe_consumer: It is smaller than those handles
+ // in |message_pipe_array|, which is wrong.
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]0 // version
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]3
+[u4]4
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected
new file mode 100644
index 0000000000..eef8e38a84
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data
new file mode 100644
index 0000000000..fb3f862f4c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data
@@ -0,0 +1,24 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]6 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method6_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method6_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]1 // num_elements
+[dist8]element_ptr
+[anchr]array_param
+
+[anchr]element_ptr
+[dist4]array_element // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_element
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data
new file mode 100644
index 0000000000..c8cacf0b2e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data
@@ -0,0 +1,24 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]6 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method6_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method6_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]1 // num_elements
+[dist8]element_ptr
+[anchr]array_param
+
+[anchr]element_ptr
+[dist4]array_element // num_bytes: It is insufficient to store 12 elements.
+[u4]12 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_element
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data
new file mode 100644
index 0000000000..c9726763e1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data
@@ -0,0 +1,40 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]7 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]0 // version
+[dist8]array_ptr // fixed_size_array
+[anchr]struct_f
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
+
+[u4]0 [u1]0 // Padding to make the next array aligned properly.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[u8]0 // A null pointer, which is okay.
+[dist8]array_element_ptr
+[anchr]array_param
+
+[anchr]array_element_ptr
+[dist4]array_element // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_element
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data
new file mode 100644
index 0000000000..4d25cf24a2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data
@@ -0,0 +1,26 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]7 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]0 // version
+[u8]0 // fixed_size_array: An unexpected null pointer.
+[anchr]struct_f
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[u8]0 // A null pointer, which is okay.
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data
new file mode 100644
index 0000000000..cee6e1404f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data
@@ -0,0 +1,34 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]7 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]0 // version
+[dist8]array_ptr // fixed_size_array
+[anchr]struct_f
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]2 // num_elements: Too few elements.
+0 1
+[anchr]array_member
+
+[u4]0 [u1]0 [u1]0 // Padding for alignment of next array.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[u8]0 // A null pointer, which is okay.
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data
new file mode 100644
index 0000000000..3095a73893
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data
@@ -0,0 +1,40 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]7 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]0 // version
+[dist8]array_ptr // fixed_size_array
+[anchr]struct_f
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 3
+[anchr]array_member
+
+[u4]0 [u1]0 // Padding for alignment of next array.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[dist8]array_element_ptr
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
+
+[anchr]array_element_ptr
+[dist4]array_element // num_bytes
+[u4]4 // num_elements: Too many elements.
+0 1 2 3
+[anchr]array_element
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data
new file mode 100644
index 0000000000..b19e141b0b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data
@@ -0,0 +1,21 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]8 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method8_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]0x20000001 // num_elements: The corresponding array size should be
+ // 0x20000001 * 8 + 8 = 0x100000010 which is
+ // 2^32 + 16 (base-10), while |num_bytes| is a 32-bit
+ // unsigned integer and its value is 16.
+[u8]0
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data
new file mode 100644
index 0000000000..08c4bc3121
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]8 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method8_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]3 // num_elements
+[u8]0 // A null pointer, which is okay.
+[dist8]nested_array_ptr
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
+
+[anchr]nested_array_ptr
+[dist4]nested_array // num_bytes
+[u4]1 // num_elements
+[dist8]string_ptr
+[anchr]nested_array
+
+[anchr]string_ptr
+[dist4]string // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data
new file mode 100644
index 0000000000..03f2a10cb5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data
@@ -0,0 +1,12 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]8 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]0 // version
+[u8]0 // param0: An unexpected null pointer.
+[anchr]method8_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data
new file mode 100644
index 0000000000..b1b4462051
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data
@@ -0,0 +1,33 @@
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]8 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method8_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]3 // num_elements
+[u8]0 // A null pointer, which is okay.
+[dist8]nested_array_ptr
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
+
+[anchr]nested_array_ptr
+[dist4]nested_array // num_bytes
+[u4]2 // num_elements
+[dist8]string_ptr
+[u8]0 // An unexpected null pointer.
+[anchr]nested_array
+
+[anchr]string_ptr
+[dist4]string // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data
new file mode 100644
index 0000000000..6ed00092c6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data
@@ -0,0 +1,36 @@
+[handles]4
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]9 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method9_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method9_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[dist8]nested_array_ptr_0
+[dist8]nested_array_ptr_1
+[anchr]array_param
+
+[anchr]nested_array_ptr_0
+[dist4]nested_array_0 // num_bytes
+[u4]2 // num_elements
+[u4]0
+[s4]-1 // An invalid handle, which is okay.
+[anchr]nested_array_0
+
+[anchr]nested_array_ptr_1
+[dist4]nested_array_1 // num_bytes
+[u4]3 // num_elements
+[u4]2
+[s4]-1 // An invalid handle, which is okay.
+[u4]3
+[anchr]nested_array_1
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data
new file mode 100644
index 0000000000..90feced0b6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data
@@ -0,0 +1,14 @@
+[handles]4
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]9 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method9_params // num_bytes
+[u4]0 // version
+[u8]0 // param0: A null pointer, which is okay.
+[anchr]method9_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data
new file mode 100644
index 0000000000..e87fbcba31
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data
@@ -0,0 +1,27 @@
+[handles]4
+
+[dist4]message_header // num_bytes
+[u4]0 // version
+[u4]0 // interface ID
+[u4]9 // name
+[u4]0 // flags
+[u4]0 // padding
+[anchr]message_header
+
+[dist4]method9_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method9_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[dist8]nested_array_ptr_0
+[u8]0 // An unexpected null pointer.
+[anchr]array_param
+
+[anchr]nested_array_ptr_0
+[dist4]nested_array_0 // num_bytes
+[u4]1 // num_elements
+[u4]0
+[anchr]nested_array_0
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected
new file mode 100644
index 0000000000..95d8db01bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data
new file mode 100644
index 0000000000..7da356fc5e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]2 // flags kMessageIsResponse
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]uint8_array // num_bytes
+[u4]1 // num_elements
+[u1]0
+[anchr]uint8_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data
new file mode 100644
index 0000000000..bdcfc46853
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]2 // flags kMessageIsResponse
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]uint8_array // num_bytes
+[u4]2 // num_elements: The size is too small to hold 2 elements.
+[u1]0
+[anchr]uint8_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected
new file mode 100644
index 0000000000..5a1ec4ef20
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data
new file mode 100644
index 0000000000..a1fe69d82e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data
@@ -0,0 +1,20 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]1 // flags kMessageExpectsResponse
+[u4]0 // padding
+[u8]7 // request_id
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]basic_struct // num_bytes
+[u4]0 // version
+[s4]-1 // a
+[u4]0 // padding
+[anchr]basic_struct
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data
new file mode 100644
index 0000000000..e689adb670
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data
@@ -0,0 +1,17 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0 // name
+[u4]1 // flags kMessageExpectsResponse
+[u4]0 // padding
+[u8]7 // request_id
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // version
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[u4]0 // num_bytes: The struct size is too small.
+[u4]0 // version
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected
new file mode 100644
index 0000000000..25aceeea5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data
new file mode 100644
index 0000000000..7198afa30e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]0xffffffff // name
+[u4]3 // flags: This combination is illegal.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data
new file mode 100644
index 0000000000..72835098f7
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]1 // name: Method1 does not have a response message.
+[u4]2 // flags: kMessageIsResponse
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected
new file mode 100644
index 0000000000..65a48b3c06
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD \ No newline at end of file
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data
new file mode 100644
index 0000000000..d1d8d3f157
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]12 // name
+[u4]0 // flags: kMessageIsResponse is not set in a response.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data
new file mode 100644
index 0000000000..091b68c235
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]12 // name
+[u4]1 // flags: kMessageExpectsResponse is set in a response.
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected
new file mode 100644
index 0000000000..c33fde327b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data
new file mode 100644
index 0000000000..0eda287e32
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]1 // version
+[u4]0 // interface ID
+[u4]11 // name: Method11 does not have a response message.
+[u4]2 // flags: kMessageIsResponse
+[u4]0 // padding
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected
new file mode 100644
index 0000000000..65a48b3c06
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD \ No newline at end of file
diff --git a/mojo/public/interfaces/bindings/tests/echo.mojom b/mojo/public/interfaces/bindings/tests/echo.mojom
new file mode 100644
index 0000000000..56c6063010
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/echo.mojom
@@ -0,0 +1,12 @@
+// Copyright 2017 The Chromium 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 test.echo.mojom;
+
+import "echo_import.mojom";
+
+interface Echo {
+ EchoPoint(test.echo_import.mojom.Point point)
+ => (test.echo_import.mojom.Point result);
+};
diff --git a/mojo/public/interfaces/bindings/tests/echo_import.mojom b/mojo/public/interfaces/bindings/tests/echo_import.mojom
new file mode 100644
index 0000000000..a024ce2ff1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/echo_import.mojom
@@ -0,0 +1,10 @@
+// Copyright 2017 The Chromium 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 test.echo_import.mojom;
+
+struct Point {
+ int32 x;
+ int32 y;
+};
diff --git a/mojo/public/interfaces/bindings/tests/math_calculator.mojom b/mojo/public/interfaces/bindings/tests/math_calculator.mojom
new file mode 100644
index 0000000000..7d1b171e1a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/math_calculator.mojom
@@ -0,0 +1,12 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.math"]
+module math;
+
+interface Calculator {
+ Clear@0() => (double value@0);
+ Add@1(double value@0) => (double value@0);
+ Multiply@2(double value@0) => (double value@0);
+};
diff --git a/mojo/public/interfaces/bindings/tests/no_module.mojom b/mojo/public/interfaces/bindings/tests/no_module.mojom
new file mode 100644
index 0000000000..f380011290
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/no_module.mojom
@@ -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.
+
+// Entities without module
+
+enum EnumWithoutModule {
+ A
+};
diff --git a/mojo/public/interfaces/bindings/tests/ping_service.mojom b/mojo/public/interfaces/bindings/tests/ping_service.mojom
new file mode 100644
index 0000000000..ba6ad3d66a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/ping_service.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.ping"]
+module mojo.test;
+
+interface PingService {
+ Ping() => ();
+};
+
+interface EchoService {
+ Echo(string test_data) => (string echo_data);
+};
diff --git a/mojo/public/interfaces/bindings/tests/rect.mojom b/mojo/public/interfaces/bindings/tests/rect.mojom
new file mode 100644
index 0000000000..4ecc4d9660
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/rect.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_structs"]
+module mojo.test;
+
+struct Rect {
+ int32 x;
+ int32 y;
+ int32 width;
+ int32 height;
+};
+
+// A copy of Rect that is typemapped differently in the chromium and blink
+// variants.
+struct TypemappedRect {
+ int32 x;
+ int32 y;
+ int32 width;
+ int32 height;
+};
+
+// A copy of Rect that is typemapped to the same custom type in the chromium and
+// blink variants.
+struct SharedTypemappedRect {
+ int32 x;
+ int32 y;
+ int32 width;
+ int32 height;
+}; \ No newline at end of file
diff --git a/mojo/public/interfaces/bindings/tests/regression_tests.mojom b/mojo/public/interfaces/bindings/tests/regression_tests.mojom
new file mode 100644
index 0000000000..d87a3ad65f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/regression_tests.mojom
@@ -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.
+
+// Module containing entities for regression tests of the generator. Entities
+// must never be modified, instead new entity must be added to add new tests.
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.regression_tests"]
+module regression_tests;
+
+interface CheckMethodWithEmptyResponse {
+WithouParameterAndEmptyResponse() => ();
+WithParameterAndEmptyResponse(bool b) => ();
+};
+
+interface CheckNameCollision {
+WithNameCollision(bool message, bool response) => (bool message, bool response);
+};
+
+enum EnumWithReference {
+ k_STEREO_AND_KEYBOARD_MIC = 30,
+ k_MAX = k_STEREO_AND_KEYBOARD_MIC
+};
+
+enum EnumWithLowercase {
+ PlanarF16,
+ PlanarF32
+};
+
+enum EnumWithNumbers {
+ k_2_1 = 4
+};
+
+enum EnumWithK {
+ K = 0
+};
+
+struct Edge {
+ Vertex? v;
+};
+
+struct Vertex {
+ EmptyStruct? e;
+};
+
+struct EmptyStruct {
+};
+
+struct A {
+ B? b;
+};
+
+struct B {
+ A? a;
+};
+
+// Previously, a field or parameter called |handles| would be shadowed by a
+// method parameter in generated C++ bindings code.
+struct HandlesNameCollisionStruct {
+ EmptyStruct handles;
+};
+
+struct HandlesHandleNameCollisionStruct {
+ handle handles;
+};
+
+union HandlesNameCollisionUnion {
+ int32 handles;
+};
+
+struct HandlesUnionNameCollisionStruct {
+ HandlesNameCollisionUnion handles;
+};
+
+interface HandlesNameCollisionInterface {
+ Method(EmptyStruct handles) => (handle handles);
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_factory.mojom b/mojo/public/interfaces/bindings/tests/sample_factory.mojom
new file mode 100644
index 0000000000..ade3bf37d6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_factory.mojom
@@ -0,0 +1,41 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"]
+module sample;
+
+import "sample_import.mojom";
+
+// This sample shows how handles to MessagePipes can be sent as both parameters
+// to methods as well as fields on structs.
+
+struct Request {
+ int32 x;
+ handle<message_pipe>? pipe;
+ array<handle<message_pipe>>? more_pipes;
+
+ // Interfaces can be used as members.
+ imported.ImportedInterface? obj;
+};
+
+struct Response {
+ int32 x;
+ handle<message_pipe>? pipe;
+};
+
+interface NamedObject {
+ SetName(string name);
+ GetName() => (string name);
+};
+
+interface Factory {
+ DoStuff(Request request, handle<message_pipe>? pipe) =>
+ (Response response, string text);
+ DoStuff2(handle<data_pipe_consumer> pipe) => (string text);
+ CreateNamedObject(NamedObject& obj);
+ RequestImportedInterface(
+ imported.ImportedInterface& obj) => (imported.ImportedInterface& obj);
+ TakeImportedInterface(
+ imported.ImportedInterface obj) => (imported.ImportedInterface obj);
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_import.mojom b/mojo/public/interfaces/bindings/tests/sample_import.mojom
new file mode 100644
index 0000000000..d28cb7b4db
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_import.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"]
+module imported;
+
+// This sample just defines some types that are imported into
+// sample_service.mojom, to show how import works.
+
+enum Shape {
+ RECTANGLE = 1,
+ CIRCLE,
+ TRIANGLE,
+ LAST = TRIANGLE,
+};
+
+// These enum values should not interfere with those of Shape above.
+enum AnotherShape {
+ RECTANGLE = 10,
+ CIRCLE,
+ TRIANGLE,
+};
+
+enum YetAnotherShape {
+ RECTANGLE = 20,
+ CIRCLE,
+ TRIANGLE,
+};
+
+struct Point {
+ int32 x;
+ int32 y;
+};
+
+interface ImportedInterface {
+ DoSomething();
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_import2.mojom b/mojo/public/interfaces/bindings/tests/sample_import2.mojom
new file mode 100644
index 0000000000..ca4e81c0bb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_import2.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"]
+module imported;
+
+import "sample_import.mojom";
+
+// This sample adds more types and constants to the "imported" namespace,
+// to test a bug with importing multiple modules with the same namespace.
+
+enum Color {
+ RED,
+ BLACK,
+};
+
+struct Size {
+ int32 width;
+ int32 height;
+};
+
+struct Thing {
+ imported.Shape shape = RECTANGLE;
+ imported.Color color = Color.BLACK;
+ Point location;
+ Size size;
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom b/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom
new file mode 100644
index 0000000000..5960d75665
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample",
+ JavaConstantsClassName="InterfaceConstants",
+ Foo = "hello world"]
+module sample;
+
+const uint64 kLong = 4405;
+
+enum Enum {
+ VALUE
+};
+
+interface PingTest {
+ Ping() => ();
+};
+
+interface Provider {
+ EchoString(string a) => (string a);
+ EchoStrings(string a, string b) => (string a, string b);
+ EchoMessagePipeHandle(handle<message_pipe> a) => (handle<message_pipe> a);
+ EchoEnum(Enum a) => (Enum a);
+ EchoInt(int32 a) => (int32 a);
+};
+
+interface IntegerAccessor {
+ GetInteger() => (int64 data, [MinVersion=2] Enum type);
+ [MinVersion=1]
+ SetInteger(int64 data, [MinVersion=3] Enum type);
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_service.mojom b/mojo/public/interfaces/bindings/tests/sample_service.mojom
new file mode 100644
index 0000000000..761cb91a9b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_service.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"]
+module sample;
+
+import "sample_import.mojom";
+import "sample_import2.mojom";
+
+const uint8 kTwelve = 12;
+
+struct Bar {
+ enum Type {
+ VERTICAL = 1,
+ HORIZONTAL,
+ BOTH,
+ INVALID
+ };
+ uint8 alpha@0 = 0xff;
+ uint8 beta@1;
+ uint8 gamma@2;
+ Type type@3 = sample.Bar.Type.VERTICAL;
+};
+
+struct Foo {
+ const string kFooby = "Fooby";
+ string name@8 = kFooby;
+ int32 x@0;
+ int32 y@1;
+ bool a@2 = true;
+ bool b@3;
+ bool c@4;
+ Bar? bar@5;
+ array<Bar>? extra_bars@7;
+ array<uint8>? data@6;
+ handle<message_pipe>? source@9;
+ array<handle<data_pipe_consumer>>? input_streams@10;
+ array<handle<data_pipe_producer>>? output_streams@11;
+ array<array<bool>>? array_of_array_of_bools@12;
+ array<array<array<string>>>? multi_array_of_strings@13;
+ array<bool>? array_of_bools@14;
+};
+
+struct DefaultsTest {
+ int8 a0@0 = -12;
+ uint8 a1@1 = sample.kTwelve;
+ int16 a2@2 = 1234;
+ uint16 a3@3 = 34567;
+ int32 a4@4 = 123456;
+ uint32 a5@5 = 3456789012;
+ int64 a6@6 = -111111111111;
+ uint64 a7@7 = 9999999999999999999;
+ int32 a8@8 = 0x12345;
+ int32 a9@9 = -0x12345;
+ int32 a10@10 = +1234;
+ bool a11@11 = true;
+ bool a12@12 = false;
+ float a13@13 = 123.25;
+ double a14@14 = 1234567890.123;
+ double a15@15 = 1E10;
+ double a16@16 = -1.2E+20;
+ double a17@17 = +1.23E-20;
+
+ // TODO(vtl): Add tests for default vs null when those are implemented (for
+ // structs, arrays, and strings).
+ array<uint8> a18@18;
+ string a19@19;
+
+ Bar.Type a20@20 = BOTH;
+ imported.Point a21@21;
+ imported.Thing a22@22 = default;
+
+ uint64 a23@23 = 0xFFFFFFFFFFFFFFFF;
+ int64 a24@24 = 0x123456789;
+ int64 a25@25 = -0x123456789;
+
+ double a26@26 = double.INFINITY;
+ double a27@27 = double.NEGATIVE_INFINITY;
+ double a28@28 = double.NAN;
+ float a29@29 = float.INFINITY;
+ float a30@30 = float.NEGATIVE_INFINITY;
+ float a31@31 = float.NAN;
+};
+
+struct StructWithHoleV1 {
+ int32 v1 = 1;
+ int64 v2 = 2;
+};
+
+struct StructWithHoleV2 {
+ int32 v1 = 1;
+ int64 v2 = 2;
+ int32 v3 = 3;
+};
+
+interface Service {
+ enum BazOptions {
+ REGULAR = 0,
+ EXTRA
+ };
+ const uint8 kFavoriteBaz = 1;
+ Frobinate@0(Foo? foo@0, BazOptions baz@1, Port? port@2) => (int32 result@0);
+ GetPort@1(Port& port @0);
+};
+
+// This interface is referenced above where it is defined. It also refers to
+// itself from a method.
+interface Port {
+ PostMessageToPort@0(string message_text@0, Port port@1);
+};
diff --git a/mojo/public/interfaces/bindings/tests/scoping.mojom b/mojo/public/interfaces/bindings/tests/scoping.mojom
new file mode 100644
index 0000000000..2e9edb10b4
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/scoping.mojom
@@ -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.
+
+module mojo.test;
+
+interface A {
+ GetB(B& b);
+};
+
+interface B {
+ GetC(C& c);
+};
+
+interface C {
+ D();
+};
diff --git a/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom b/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom
new file mode 100644
index 0000000000..1239e163cb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"]
+module mojo.test;
+
+struct Struct1 {
+ uint8 i;
+};
+
+struct Struct2 {
+ handle hdl;
+};
+
+struct Struct3 {
+ Struct1 struct_1;
+};
+
+struct Struct4 {
+ array<Struct1> data;
+};
+
+struct Struct5 {
+ array<Struct1, 2> pair;
+};
+
+struct Struct6 {
+ string str;
+};
+
+struct StructOfNullables {
+ handle? hdl;
+ Struct1? struct_1;
+ string? str;
+};
diff --git a/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom b/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom
new file mode 100644
index 0000000000..b50409ee88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom
@@ -0,0 +1,83 @@
+// 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.
+
+module mojo.test;
+
+// TODO(yzshen): Rename *WithTraits* types to something more readable.
+
+struct NestedStructWithTraits {
+ int32 value;
+};
+
+enum EnumWithTraits {
+ VALUE_0,
+ VALUE_1
+};
+
+struct StructWithTraits {
+ EnumWithTraits f_enum;
+ bool f_bool;
+ uint32 f_uint32;
+ uint64 f_uint64;
+ string f_string;
+ string f_string2;
+ array<string> f_string_array;
+ array<string> f_string_set;
+ NestedStructWithTraits f_struct;
+ array<NestedStructWithTraits> f_struct_array;
+ map<string, NestedStructWithTraits> f_struct_map;
+};
+
+// Test that this container can be cloned.
+struct StructWithTraitsContainer {
+ StructWithTraits f_struct;
+};
+
+// Maps to a pass-by-value trivial struct.
+struct TrivialStructWithTraits {
+ int32 value;
+};
+
+// Maps to a move-only struct.
+struct MoveOnlyStructWithTraits {
+ handle f_handle;
+};
+
+// The custom type for MoveOnlyStructWithTraits is not clonable. Test that
+// this container can compile as long as Clone() is not used.
+struct MoveOnlyStructWithTraitsContainer {
+ MoveOnlyStructWithTraits f_struct;
+};
+
+struct StructWithTraitsForUniquePtr {
+ int32 f_int32;
+};
+
+union UnionWithTraits {
+ int32 f_int32;
+ NestedStructWithTraits f_struct;
+};
+
+interface TraitsTestService {
+ EchoStructWithTraits(StructWithTraits s) => (StructWithTraits passed);
+
+ EchoTrivialStructWithTraits(TrivialStructWithTraits s) =>
+ (TrivialStructWithTraits passed);
+
+ EchoMoveOnlyStructWithTraits(MoveOnlyStructWithTraits s) =>
+ (MoveOnlyStructWithTraits passed);
+
+ EchoNullableMoveOnlyStructWithTraits(MoveOnlyStructWithTraits? s) =>
+ (MoveOnlyStructWithTraits? passed);
+
+ EchoEnumWithTraits(EnumWithTraits e) => (EnumWithTraits passed);
+
+ EchoStructWithTraitsForUniquePtr(StructWithTraitsForUniquePtr e) => (
+ StructWithTraitsForUniquePtr passed);
+
+ EchoNullableStructWithTraitsForUniquePtr(StructWithTraitsForUniquePtr? e) => (
+ StructWithTraitsForUniquePtr? passed);
+
+ EchoUnionWithTraits(UnionWithTraits u) => (UnionWithTraits passed);
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
new file mode 100644
index 0000000000..adc4e7e809
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
@@ -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.
+
+module mojo.test;
+
+import "mojo/public/interfaces/bindings/tests/ping_service.mojom";
+
+interface FooInterface {};
+
+struct StructContainsAssociated {
+ associated FooInterface? foo_interface;
+ associated FooInterface& foo_request;
+ array<associated FooInterface> foo_interfaces;
+ array<associated FooInterface&> foo_requests;
+};
+
+union UnionContainsAssociated {
+ associated FooInterface foo_interface;
+ associated FooInterface& foo_request;
+ array<associated FooInterface> foo_interfaces;
+ array<associated FooInterface&> foo_requests;
+};
+
+interface InterfacePassesAssociated {
+ PassFoo(associated FooInterface foo_interface,
+ associated FooInterface& foo_request) =>
+ (associated FooInterface foo_interface,
+ associated FooInterface& foo_request);
+
+ PassStruct(StructContainsAssociated foo_struct) =>
+ (StructContainsAssociated foo_struct);
+
+ PassUnion(UnionContainsAssociated foo_union) =>
+ (UnionContainsAssociated foo_union);
+};
+
+interface IntegerSender {
+ Echo(int32 value) => (int32 value);
+ Send(int32 value);
+};
+
+interface IntegerSenderConnection {
+ GetSender(associated IntegerSender& sender);
+ AsyncGetSender() => (associated IntegerSender sender);
+};
+
+interface AssociatedPingProvider {
+ GetPing(associated PingService& request);
+};
+
+interface AssociatedPingProviderProvider {
+ GetPingProvider(associated AssociatedPingProvider& request);
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_bad_messages.mojom b/mojo/public/interfaces/bindings/tests/test_bad_messages.mojom
new file mode 100644
index 0000000000..dcd594754d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_bad_messages.mojom
@@ -0,0 +1,13 @@
+// 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.
+
+module mojo.test;
+
+interface TestBadMessages {
+ RejectEventually() => ();
+ RequestResponse() => ();
+
+ [Sync] RejectSync() => ();
+ [Sync] RequestResponseSync() => ();
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_constants.mojom b/mojo/public/interfaces/bindings/tests/test_constants.mojom
new file mode 100644
index 0000000000..272e6035a6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_constants.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_constants"]
+module mojo.test;
+
+// Integral types.
+const bool kBoolValue = true;
+
+const int8 kInt8Value = -2;
+
+// In the range of (MAX_INT8, MAX_UINT8].
+const uint8 kUint8Value = 128;
+
+// In the range of [MIN_INT16, MIN_INT8).
+const int16 kInt16Value = -233;
+
+// In the range of (MAX_INT16, MAX_UINT16].
+const uint16 kUint16Value = 44204;
+
+// In the range of [MIN_INT32, MIN_INT16).
+const int32 kInt32Value = -44204;
+
+// In the range of (MAX_INT32, MAX_UINT32].
+const uint32 kUint32Value = 4294967295;
+
+// In the range of [MIN_INT64, MIN_INT32).
+const int64 kInt64Value = -9223372036854775807;
+
+// In the range of (MAX_INT64, MAX_UINT64].
+const uint64 kUint64Value = 9999999999999999999;
+
+// Floating point types.
+const double kDoubleValue = 3.14159;
+const double kDoubleInfinity = double.INFINITY;
+const double kDoubleNegativeInfinity = double.NEGATIVE_INFINITY;
+const double kDoubleNaN = double.NAN;
+
+const float kFloatValue = 2.71828;
+const float kFloatInfinity = float.INFINITY;
+const float kFloatNegativeInfinity = float.NEGATIVE_INFINITY;
+const float kFloatNaN = float.NAN;
+
+const string kStringValue = "test string contents";
+
+struct StructWithConstants {
+ const int8 kInt8Value = 5;
+ const float kFloatValue = 765.432;
+ const string kStringValue = "struct test string contents";
+};
+
+interface InterfaceWithConstants {
+ const uint32 kUint32Value = 20100722;
+ const double kDoubleValue = 12.34567;
+ const string kStringValue = "interface test string contents";
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_data_view.mojom b/mojo/public/interfaces/bindings/tests/test_data_view.mojom
new file mode 100644
index 0000000000..1fe8c6a8e2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_data_view.mojom
@@ -0,0 +1,41 @@
+// 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.
+
+module mojo.test.data_view;
+
+enum TestEnum {
+ VALUE_0,
+ VALUE_1
+};
+
+interface TestInterface {
+ [Sync]
+ Echo(int32 value) => (int32 out_value);
+};
+
+struct NestedStruct {
+ int32 f_int32;
+};
+
+[Native]
+struct TestNativeStruct;
+
+union TestUnion {
+ bool f_bool;
+ int32 f_int32;
+};
+
+struct TestStruct {
+ string f_string;
+ NestedStruct? f_struct;
+ TestNativeStruct? f_native_struct;
+ array<bool> f_bool_array;
+ array<int32> f_int32_array;
+ array<TestEnum> f_enum_array;
+ array<TestInterface> f_interface_array;
+ array<array<int32>> f_nested_array;
+ array<NestedStruct> f_struct_array;
+ array<TestUnion> f_union_array;
+ map<string, int32> f_map;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_export.mojom b/mojo/public/interfaces/bindings/tests/test_export.mojom
new file mode 100644
index 0000000000..319a15b036
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_export.mojom
@@ -0,0 +1,20 @@
+// 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.
+
+module mojo.test.test_export;
+
+struct StringPair {
+ string s1;
+ string s2;
+};
+
+// This is a regression test. On Windows, if we export the generated class *and*
+// not explicitly disallow copy constructor and assign operator, compilation
+// will fail because it tries to use copy constructor of
+// InlinedStructPtr<StringPair>.
+struct StringPairContainer {
+ array<StringPair> pairs;
+};
+
+interface ExportedInterface {};
diff --git a/mojo/public/interfaces/bindings/tests/test_export2.mojom b/mojo/public/interfaces/bindings/tests/test_export2.mojom
new file mode 100644
index 0000000000..011395cccb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_export2.mojom
@@ -0,0 +1,10 @@
+// Copyright 2017 The Chromium 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 mojo.test.test_export2;
+
+struct StringPair {
+ string s1;
+ string s2;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_import.mojom b/mojo/public/interfaces/bindings/tests/test_import.mojom
new file mode 100644
index 0000000000..42014c6809
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_import.mojom
@@ -0,0 +1,11 @@
+// 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.
+
+module mojo.test.test_import;
+
+import "mojo/public/interfaces/bindings/tests/test_export.mojom";
+
+struct ImportingStruct {
+ mojo.test.test_export.StringPair strings;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_native_types.mojom b/mojo/public/interfaces/bindings/tests/test_native_types.mojom
new file mode 100644
index 0000000000..3df43182a3
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_native_types.mojom
@@ -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.
+
+module mojo.test;
+
+import "mojo/public/interfaces/bindings/tests/rect.mojom";
+
+// Used to verify that structs can be declared with no body in mojom.
+
+[Native]
+struct PickledStruct;
+
+[Native]
+enum PickledEnum;
+
+struct PickleContainer {
+ PickledStruct f_struct;
+ PickledEnum f_enum;
+};
+
+interface PicklePasser {
+ PassPickledStruct(PickledStruct pickle) => (PickledStruct passed);
+ PassPickledEnum(PickledEnum pickle) => (PickledEnum passed);
+ PassPickleContainer(PickleContainer container) => (PickleContainer passed);
+ PassPickles(array<PickledStruct> pickles) => (array<PickledStruct> passed);
+ PassPickleArrays(array<array<PickledStruct>> pickle_arrays)
+ => (array<array<PickledStruct>> passed);
+};
+
+// Used to verify support for native serialization of mojom-defined structs
+// using StrucTraits with different variants of the Rect type from rect.mojom.
+
+interface RectService {
+ AddRect(TypemappedRect r);
+ GetLargestRect() => (TypemappedRect largest);
+ PassSharedRect(SharedTypemappedRect r) => (SharedTypemappedRect passed);
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_structs.mojom b/mojo/public/interfaces/bindings/tests/test_structs.mojom
new file mode 100644
index 0000000000..03a0a20581
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_structs.mojom
@@ -0,0 +1,414 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_structs"]
+module mojo.test;
+
+import "mojo/public/interfaces/bindings/tests/rect.mojom";
+
+struct NamedRegion {
+ string? name;
+ array<Rect>? rects;
+};
+
+struct RectPair {
+ Rect? first;
+ Rect? second;
+};
+
+struct EmptyStruct {
+};
+
+[Native]
+struct UnmappedNativeStruct;
+
+// Used to verify that struct fields which don't specify a default are
+// initialized to: false for bool, 0 for numbers, and null for strings,
+// handles, and structs. The "?" nullable suffix shouldn't have any
+// impact on initial field values.
+
+struct NoDefaultFieldValues {
+ bool f0;
+ int8 f1;
+ uint8 f2;
+ int16 f3;
+ uint16 f4;
+ int32 f5;
+ uint32 f6;
+ int64 f7;
+ uint64 f8;
+ float f9;
+ double f10;
+ string f11;
+ string? f12;
+ handle<message_pipe> f13;
+ handle<data_pipe_consumer> f14;
+ handle<data_pipe_producer> f15;
+ handle<message_pipe>? f16;
+ handle<data_pipe_consumer>? f17;
+ handle<data_pipe_producer>? f18;
+ handle f19;
+ handle? f20;
+ handle<shared_buffer> f21;
+ handle<shared_buffer>? f22;
+ array<string> f23;
+ array<string?> f24;
+ array<string>? f25;
+ array<string?>? f26;
+ EmptyStruct f27;
+ EmptyStruct? f28;
+};
+
+// Used to verify that struct fields with an explicit default value
+// are initialized correctly. The "?" nullable suffix shouldn't have any
+// impact on initial field values.
+
+struct DefaultFieldValues {
+ const string kFoo = "foo";
+ bool f0 = true;
+ int8 f1 = 100;
+ uint8 f2 = 100;
+ int16 f3 = 100;
+ uint16 f4 = 100;
+ int32 f5 = 100;
+ uint32 f6 = 100;
+ int64 f7 = 100;
+ uint64 f8 = 100;
+ float f9 = 100;
+ float f10 = 100.0;
+ double f11 = 100;
+ double f12 = 100.0;
+ string f13 = kFoo;
+ string? f14 = kFoo;
+ Rect f15 = default;
+ Rect? f16 = default;
+};
+
+// Used to verify that the code generated for enum and const values defined
+// within a struct is correct. Assuming that a constant's value can be a literal
+// or another constant and that enum values can either be an integer constant or
+// another value from the same enum type.
+
+struct ScopedConstants {
+ const int32 TEN = 10;
+ const int32 ALSO_TEN = TEN;
+ enum EType {
+ E0,
+ E1,
+ E2 = 10,
+ E3 = E2,
+ E4,
+ };
+ EType f0 = E0; // 0
+ EType f1 = E1; // 1
+ EType f2 = E2; // 10
+ EType f3 = E3; // 10
+ EType f4 = E4; // 11
+ int32 f5 = TEN;
+ int32 f6 = ALSO_TEN;
+};
+
+// Used to verify that all possible Map key field types can be encoded and
+// decoded successfully.
+
+struct MapKeyTypes {
+ // TODO(yzshen): WTF::HashMap doesn't support bool as key.
+ // map<bool, bool> f0;
+ map<int8, int8> f1;
+ map<uint8, uint8> f2;
+ map<int16, int16> f3;
+ map<uint16, uint16> f4;
+ map<int32, int32> f5;
+ map<uint32, uint32> f6;
+ map<int64, int64> f7;
+ map<uint64, uint64> f8;
+ map<float, float> f9;
+ map<double, double> f10;
+ map<string, string> f11;
+ // TODO(tibell): JS/Java don't support struct as key.
+ // map<Rect, Rect> f12;
+};
+
+// Used to verify that various map value types can be encoded and decoded
+// successfully.
+
+struct MapValueTypes {
+ map<string, array<string>> f0;
+ map<string, array<string>?> f1;
+ map<string, array<string?>> f2;
+ map<string, array<string, 2>> f3;
+ map<string, array<array<string, 2>?>> f4;
+ map<string, array<array<string, 2>, 1>> f5;
+ map<string, Rect?> f6;
+ map<string, map<string, string>> f7;
+ map<string, array<map<string, string>>> f8;
+ map<string, handle> f9;
+ map<string, array<handle>> f10;
+ map<string, map<string, handle>> f11;
+};
+
+// Used to verify that various array types can be encoded and decoded
+// successfully.
+
+struct ArrayValueTypes {
+ array<int8> f0;
+ array<int16> f1;
+ array<int32> f2;
+ array<int64> f3;
+ array<float> f4;
+ array<double> f5;
+ array<SomeInterface> f6;
+ array<SomeInterface&> f7;
+};
+
+// Used to verify that various float and double values can be encoded and
+// decoded correctly.
+
+struct FloatNumberValues {
+ const double V0 = double.INFINITY;
+ const double V1 = double.NEGATIVE_INFINITY;
+ const double V2 = double.NAN;
+ const float V3 = float.INFINITY;
+ const float V4 = float.NEGATIVE_INFINITY;
+ const float V5 = float.NAN;
+ const float V6 = 0;
+ const double V7 = 1234567890.123;
+ const double V8 = 1.2E+20;
+ const double V9 = -1.2E+20;
+
+ double f0 = V0;
+ double f1 = V1;
+ double f2 = V2;
+ float f3 = V3;
+ float f4 = V4;
+ float f5 = V5;
+ float f6 = V6;
+ double f7 = V7;
+ double f8 = V8;
+ double f9 = V9;
+};
+
+// Used to verify that various signed integer values can be encoded and
+// decoded correctly.
+
+struct IntegerNumberValues {
+ const int8 V0 = -128; // Minimum
+ const int8 V1 = -1; // -1
+ const int8 V2 = 0; // 0
+ const int8 V3 = 42; // An arbitrary valid value.
+ const int8 V4 = 127; // Maximum
+
+ const int16 V5 = -32768; // ...
+ const int16 V6 = -1;
+ const int16 V7 = 0;
+ const int16 V8 = 12345;
+ const int16 V9 = 32767;
+
+ const int32 V10 = -2147483648;
+ const int32 V11 = -1;
+ const int32 V12 = 0;
+ const int32 V13 = 1234567890;
+ const int32 V14 = 2147483647;
+
+ // The limits for JavaScript integers are +/- (2^53 - 1).
+ const int64 V15 = -9007199254740991; // Number.MIN_SAFE_INTEGER
+ const int64 V16 = -1;
+ const int64 V17 = 0;
+ const int64 V18 = 1234567890123456;
+ const int64 V19 = 9007199254740991; // Number.MAX_SAFE_INTEGER
+
+ int8 f0 = V0;
+ int8 f1 = V1;
+ int8 f2 = V2;
+ int8 f3 = V3;
+ int8 f4 = V4;
+
+ int16 f5 = V5;
+ int16 f6 = V6;
+ int16 f7 = V7;
+ int16 f8 = V8;
+ int16 f9 = V9;
+
+ int32 f10 = V10;
+ int32 f11 = V11;
+ int32 f12 = V12;
+ int32 f13 = V13;
+ int32 f14 = V14;
+
+ int64 f15 = V15;
+ int64 f16 = V16;
+ int64 f17 = V17;
+ int64 f18 = V18;
+ int64 f19 = V19;
+};
+
+// Used to verify that various unsigned integer values can be encoded and
+// decoded correctly.
+
+struct UnsignedNumberValues {
+ const uint8 V0 = 0; // Minimum = 0.
+ const uint8 V1 = 42; // An arbitrary valid value.
+ const uint8 V2 = 0xFF; // Maximum
+
+ const uint16 V3 = 0; // ...
+ const uint16 V4 = 12345;
+ const uint16 V5 = 0xFFFF;
+
+ const uint32 V6 = 0;
+ const uint32 V7 = 1234567890;
+ const uint32 V8 = 0xFFFFFFFF;
+
+ // The limits for JavaScript integers are +/- (2^53 - 1).
+ const uint64 V9 = 0;
+ const uint64 V10 = 1234567890123456;
+ const uint64 V11 = 9007199254740991; // Number.MAX_SAFE_INTEGER
+
+ uint8 f0 = V0;
+ uint8 f1 = V1;
+ uint8 f2 = V2;
+
+ uint16 f3 = V3;
+ uint16 f4 = V4;
+ uint16 f5 = V5;
+
+ uint32 f6 = V6;
+ uint32 f7 = V7;
+ uint32 f8 = V8;
+
+ uint64 f9 = V9;
+ uint64 f10 = V10;
+ uint64 f11 = V11;
+};
+
+// Used to verify that various (packed) boolean array values can be encoded
+// and decoded correctly.
+
+struct BitArrayValues {
+ array<bool, 1> f0;
+ array<bool, 7> f1;
+ array<bool, 9> f2;
+ array<bool> f3;
+ array<array<bool>> f4;
+ array<array<bool>?> f5;
+ array<array<bool, 2>?> f6;
+};
+
+// Used to verify that different versions can be decoded correctly.
+
+struct MultiVersionStruct {
+ [MinVersion=0]
+ int32 f_int32;
+ [MinVersion=1]
+ Rect? f_rect;
+ [MinVersion=3]
+ string? f_string;
+ [MinVersion=5]
+ array<int8>? f_array;
+ [MinVersion=7]
+ handle<message_pipe>? f_message_pipe;
+ [MinVersion=7]
+ bool f_bool;
+ [MinVersion=9]
+ int16 f_int16;
+};
+
+struct MultiVersionStructV0 {
+ [MinVersion=0]
+ int32 f_int32;
+};
+
+struct MultiVersionStructV1 {
+ [MinVersion=0]
+ int32 f_int32;
+ [MinVersion=1]
+ Rect? f_rect;
+};
+
+struct MultiVersionStructV3 {
+ [MinVersion=0]
+ int32 f_int32;
+ [MinVersion=1]
+ Rect? f_rect;
+ [MinVersion=3]
+ string? f_string;
+};
+
+struct MultiVersionStructV5 {
+ [MinVersion=0]
+ int32 f_int32;
+ [MinVersion=1]
+ Rect? f_rect;
+ [MinVersion=3]
+ string? f_string;
+ [MinVersion=5]
+ array<int8>? f_array;
+};
+
+struct MultiVersionStructV7 {
+ [MinVersion=0]
+ int32 f_int32;
+ [MinVersion=1]
+ Rect? f_rect;
+ [MinVersion=3]
+ string? f_string;
+ [MinVersion=5]
+ array<int8>? f_array;
+ [MinVersion=7]
+ handle<message_pipe>? f_message_pipe;
+ [MinVersion=7]
+ bool f_bool;
+};
+
+// A struct where the fields are not sorted by their ordinals.
+struct ReorderedStruct {
+ [MinVersion=2]
+ int32 a@3 = 3;
+ [MinVersion=4]
+ int32 b@6 = 6;
+ [MinVersion=1]
+ int32 c@1 = 1;
+};
+
+// Used to verify that interfaces that are struct members can be defined in the
+// same file.
+
+interface SomeInterface {
+ SomeMethod(RectPair pair) => (RectPair other_pair);
+};
+
+struct ContainsInterface {
+ SomeInterface some_interface;
+};
+
+// Verify that a field can be called |other|.
+
+struct ContainsOther {
+ int32 other;
+};
+
+// Used to verify that structs can contain interface requests.
+
+struct ContainsInterfaceRequest {
+ SomeInterface& request;
+};
+
+// Used to verify that boolean fields are correctly serialized/deserialized.
+
+struct SingleBoolStruct {
+ bool value;
+};
+
+// Used to verify that structs containing typemapped types can be hashed (if the
+// typemapped type itself is hashable).
+
+struct ContainsHashable {
+ TypemappedRect rect;
+};
+
+// Used to test that nested structs can be hashed. The nested struct mustn't be
+// nullable.
+
+struct SimpleNestedStruct {
+ ContainsOther nested;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom b/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom
new file mode 100644
index 0000000000..3b8cfe6388
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom
@@ -0,0 +1,44 @@
+// 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.
+
+module mojo.test;
+
+interface TestSyncCodeGeneration {
+ [Sync]
+ NoInput() => (int32 result);
+
+ [Sync]
+ NoOutput(int32 value) => ();
+
+ [Sync]
+ NoInOut() => ();
+
+ [Sync]
+ HaveInOut(int32 value1, int32 value2) => (int32 result1, int32 result2);
+};
+
+interface TestSync {
+ [Sync]
+ Ping() => ();
+
+ [Sync]
+ Echo(int32 value) => (int32 result);
+
+ AsyncEcho(int32 value) => (int32 result);
+};
+
+// Test sync method support with associated interfaces.
+interface TestSyncMaster {
+ [Sync]
+ Ping() => ();
+
+ [Sync]
+ Echo(int32 value) => (int32 result);
+
+ AsyncEcho(int32 value) => (int32 result);
+
+ SendInterface(associated TestSync ptr);
+
+ SendRequest(associated TestSync& request);
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_unions.mojom b/mojo/public/interfaces/bindings/tests/test_unions.mojom
new file mode 100644
index 0000000000..41e1ed6ace
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_unions.mojom
@@ -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.
+
+module mojo.test;
+
+enum AnEnum {
+ FIRST, SECOND
+};
+
+[Extensible]
+enum AnExtensibleEnum {
+ FIRST, SECOND, THIRD
+};
+
+union PodUnion {
+ int8 f_int8;
+ int8 f_int8_other;
+ uint8 f_uint8;
+ int16 f_int16;
+ uint16 f_uint16;
+ int32 f_int32;
+ uint32 f_uint32;
+ int64 f_int64;
+ uint64 f_uint64;
+ float f_float;
+ double f_double;
+ bool f_bool;
+ AnEnum f_enum;
+ AnExtensibleEnum f_extensible_enum;
+};
+
+union ObjectUnion {
+ int8 f_int8;
+ string f_string;
+ DummyStruct f_dummy;
+ DummyStruct? f_nullable;
+ array<int8> f_array_int8;
+ map<string, int8> f_map_int8;
+ PodUnion f_pod_union;
+ // Test that Clone() is defined after SmallStruct is declared.
+ array<SmallStruct> f_small_structs;
+};
+
+union HandleUnion {
+ handle f_handle;
+ handle<message_pipe> f_message_pipe;
+ handle<data_pipe_consumer> f_data_pipe_consumer;
+ handle<data_pipe_producer> f_data_pipe_producer;
+ handle<shared_buffer> f_shared_buffer;
+ SmallCache f_small_cache;
+ SmallCache& f_small_cache_request;
+};
+
+struct WrapperStruct {
+ ObjectUnion? object_union;
+ PodUnion? pod_union;
+ HandleUnion? handle_union;
+};
+
+struct DummyStruct {
+ int8 f_int8;
+};
+
+struct SmallStruct {
+ DummyStruct? dummy_struct;
+ PodUnion? pod_union;
+ array<PodUnion>? pod_union_array;
+ array<PodUnion?>? nullable_pod_union_array;
+ array<DummyStruct>? s_array;
+ map<string, PodUnion>? pod_union_map;
+ map<string, PodUnion?>? nullable_pod_union_map;
+};
+
+struct SmallStructNonNullableUnion {
+ PodUnion pod_union;
+};
+
+struct SmallObjStruct {
+ ObjectUnion obj_union;
+ int8 f_int8;
+};
+
+interface SmallCache {
+ SetIntValue(int64 int_value);
+ GetIntValue() => (int64 int_value);
+};
+
+interface UnionInterface {
+ Echo(PodUnion in_val) => (PodUnion out_val);
+};
+
+struct TryNonNullStruct {
+ DummyStruct? nullable;
+ DummyStruct non_nullable;
+};
+
+union OldUnion {
+ int8 f_int8;
+};
+
+union NewUnion {
+ int8 f_int8;
+ int16 f_int16;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom b/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom
new file mode 100644
index 0000000000..183f184ef3
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom
@@ -0,0 +1,50 @@
+// 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.
+
+module mojo.test;
+
+struct TestWTFCodeGeneration {
+ string str;
+ string? nullable_str;
+ array<string> strs;
+ array<string?> nullable_strs;
+ array<array<int32>> arrays;
+ array<bool> bools;
+ array<handle<message_pipe>> handles;
+ map<string, string?> str_map;
+ map<int32, array<int32>> array_map;
+ map<int32, handle<message_pipe>> handle_map;
+ array<map<string, string?>> str_maps;
+};
+
+union TestWTFCodeGeneration2 {
+ string str;
+ array<string> strs;
+ map<string, string?> str_map;
+};
+
+struct TestWTFStruct {
+ enum NestedEnum {
+ E0,
+ E1,
+ };
+ string str;
+ int32 integer;
+};
+
+interface TestWTF {
+ enum NestedEnum {
+ E0,
+ E1,
+ };
+ EchoString(string? str) => (string? str);
+ EchoStringArray(array<string?>? arr) => (array<string?>? arr);
+ EchoStringMap(map<string, string?>? str_map)
+ => (map<string, string?>? str_map);
+};
+
+enum TopLevelEnum {
+ E0,
+ E1,
+};
diff --git a/mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom
new file mode 100644
index 0000000000..2fa77ffc9d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom
@@ -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.
+
+module mojo.test;
+
+// Associated interfaces are not supported by all language bindings yet.
+// Eventually these definitions should live in validation_test_interfaces.mojom.
+
+interface InterfaceX {};
+
+interface AssociatedConformanceTestInterface {
+ Method0(associated InterfaceX param0);
+ Method1(associated InterfaceX& param0);
+ Method2(associated InterfaceX? param0);
+ Method3(array<associated InterfaceX> param0);
+};
+
diff --git a/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom b/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom
new file mode 100644
index 0000000000..ab69045e53
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom
@@ -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.
+
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"]
+module mojo.test;
+
+struct StructA {
+ uint64 i;
+};
+
+struct StructB {
+ StructA struct_a;
+};
+
+struct StructC {
+ array<uint8> data;
+};
+
+struct StructD {
+ array<handle<message_pipe>> message_pipes;
+};
+
+struct StructE {
+ StructD struct_d;
+ handle<data_pipe_consumer> data_pipe_consumer;
+};
+
+struct StructF {
+ array<uint8, 3> fixed_size_array;
+};
+
+struct StructG {
+ int32 i;
+ [MinVersion=1]
+ StructA? struct_a;
+ [MinVersion=3]
+ string? str;
+ [MinVersion=3]
+ bool b;
+};
+
+interface InterfaceA {
+};
+
+enum EnumA {
+ ENUM_A_0,
+ ENUM_A_1
+};
+
+[Extensible]
+enum EnumB {
+ ENUM_B_0,
+ ENUM_B_1,
+ ENUM_B_2
+};
+
+// A non-extensible enum with no values is valid, but about as useless as
+// you would expect: it will fail validation for all values.
+enum EmptyEnum {};
+
+[Extensible]
+enum ExtensibleEmptyEnum {};
+
+union UnionA {
+ StructA struct_a;
+ bool b;
+};
+
+// This interface is used for testing bounds-checking in the mojom
+// binding code. If you add a method please update the files
+// ./data/validation/boundscheck_*. If you add a response please update
+// ./data/validation/resp_boundscheck_*.
+interface BoundsCheckTestInterface {
+ Method0(uint8 param0) => (uint8 param0);
+ Method1(uint8 param0);
+};
+
+interface ConformanceTestInterface {
+ Method0(float param0);
+ Method1(StructA param0);
+ Method2(StructB param0, StructA param1);
+ Method3(array<bool> param0);
+ Method4(StructC param0, array<uint8> param1);
+ Method5(StructE param0, handle<data_pipe_producer> param1);
+ Method6(array<array<uint8>> param0);
+ Method7(StructF param0, array<array<uint8, 3>?, 2> param1);
+ Method8(array<array<string>?> param0);
+ Method9(array<array<handle?>>? param0);
+ Method10(map<string, uint8> param0);
+ Method11(StructG param0);
+ Method12(float param0) => (float param0);
+ Method13(InterfaceA? param0, uint32 param1, InterfaceA? param2);
+ Method14(EnumA param0, EnumB param1);
+ Method15(array<EnumA>? param0, array<EnumB>? param1);
+ Method16(map<EnumA, EnumA>? param0);
+ Method17(array<InterfaceA> param0);
+ Method18(UnionA? param0);
+ Method19(Recursive recursive);
+ Method20(map<StructB, uint8> param0);
+ Method21(ExtensibleEmptyEnum param0);
+ Method22(EmptyEnum param0);
+};
+
+struct BasicStruct {
+ int32 a;
+};
+
+interface IntegrationTestInterface {
+ Method0(BasicStruct param0) => (array<uint8> param0);
+};
+
+// An enum generates a enum-value validation function, so we want to test it.
+// E.g., valid enum values for this enum should be: -3, 0, 1, 10
+enum BasicEnum {
+ A,
+ B,
+ C = A,
+ D = -3,
+ E = 0xA
+};
+
+// The enum validation function should be generated within the scope of this
+// struct.
+struct StructWithEnum {
+ enum EnumWithin {
+ A, B, C, D
+ };
+};
+
+// This is used to test that deeply recursive structures don't blow the stack.
+struct Recursive {
+ Recursive? recursive;
+};
diff --git a/mojo/public/interfaces/bindings/tests/versioning_test_client.mojom b/mojo/public/interfaces/bindings/tests/versioning_test_client.mojom
new file mode 100644
index 0000000000..f0136db9b5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/versioning_test_client.mojom
@@ -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.
+
+module mojo.test.versioning;
+
+// versioning_test_service.mojom and versioning_test_client.mojom contain
+// different versions of Mojom definitions for a fictitious human resource
+// management system. They are used to test the versioning mechanism.
+
+enum Department {
+ SALES,
+ DEV
+};
+
+struct Employee {
+ uint64 employee_id;
+ string name;
+ Department department;
+};
+
+interface HumanResourceDatabase {
+ AddEmployee(Employee employee) => (bool success);
+
+ QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
+ => (Employee? employee, [MinVersion=1] array<uint8>? finger_print);
+
+ [MinVersion=1]
+ AttachFingerPrint(uint64 id, array<uint8> finger_print)
+ => (bool success);
+
+ [MinVersion=2]
+ ListEmployeeIds() => (array<uint64>? ids);
+};
diff --git a/mojo/public/interfaces/bindings/tests/versioning_test_service.mojom b/mojo/public/interfaces/bindings/tests/versioning_test_service.mojom
new file mode 100644
index 0000000000..59b38324a6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/versioning_test_service.mojom
@@ -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.
+
+module mojo.test.versioning;
+
+// versioning_test_service.mojom and versioning_test_client.mojom contain
+// different versions of Mojom definitions for a fictitious human resource
+// management system. They are used to test the versioning mechanism.
+
+enum Department {
+ SALES,
+ DEV
+};
+
+struct Date {
+ uint16 year;
+ uint8 month;
+ uint8 day;
+};
+
+struct Employee {
+ uint64 employee_id;
+ string name;
+ Department department;
+ [MinVersion=1] Date? birthday;
+};
+
+interface HumanResourceDatabase {
+ AddEmployee(Employee employee) => (bool success);
+
+ QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
+ => (Employee? employee, [MinVersion=1] array<uint8>? finger_print);
+
+ [MinVersion=1]
+ AttachFingerPrint(uint64 id, array<uint8> finger_print)
+ => (bool success);
+};
diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn
new file mode 100644
index 0000000000..078064114e
--- /dev/null
+++ b/mojo/public/java/BUILD.gn
@@ -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.
+
+import("//build/config/android/rules.gni")
+
+android_library("system_java") {
+ java_files = [
+ "system/src/org/chromium/mojo/system/Core.java",
+ "system/src/org/chromium/mojo/system/DataPipe.java",
+ "system/src/org/chromium/mojo/system/Flags.java",
+ "system/src/org/chromium/mojo/system/Handle.java",
+ "system/src/org/chromium/mojo/system/InvalidHandle.java",
+ "system/src/org/chromium/mojo/system/MessagePipeHandle.java",
+ "system/src/org/chromium/mojo/system/MojoException.java",
+ "system/src/org/chromium/mojo/system/MojoResult.java",
+ "system/src/org/chromium/mojo/system/Pair.java",
+ "system/src/org/chromium/mojo/system/ResultAnd.java",
+ "system/src/org/chromium/mojo/system/SharedBufferHandle.java",
+ "system/src/org/chromium/mojo/system/UntypedHandle.java",
+ "system/src/org/chromium/mojo/system/RunLoop.java",
+ "system/src/org/chromium/mojo/system/Watcher.java",
+ ]
+}
+
+android_library("bindings_java") {
+ java_files = [
+ "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java",
+ "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java",
+ "bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java",
+ "bindings/src/org/chromium/mojo/bindings/BindingsHelper.java",
+ "bindings/src/org/chromium/mojo/bindings/Callbacks.java",
+ "bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java",
+ "bindings/src/org/chromium/mojo/bindings/Connector.java",
+ "bindings/src/org/chromium/mojo/bindings/DataHeader.java",
+ "bindings/src/org/chromium/mojo/bindings/Decoder.java",
+ "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java",
+ "bindings/src/org/chromium/mojo/bindings/DeserializationException.java",
+ "bindings/src/org/chromium/mojo/bindings/Encoder.java",
+ "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java",
+ "bindings/src/org/chromium/mojo/bindings/HandleOwner.java",
+ "bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java",
+ "bindings/src/org/chromium/mojo/bindings/Interface.java",
+ "bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java",
+ "bindings/src/org/chromium/mojo/bindings/MessageHeader.java",
+ "bindings/src/org/chromium/mojo/bindings/Message.java",
+ "bindings/src/org/chromium/mojo/bindings/MessageReceiver.java",
+ "bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java",
+ "bindings/src/org/chromium/mojo/bindings/RouterImpl.java",
+ "bindings/src/org/chromium/mojo/bindings/Router.java",
+ "bindings/src/org/chromium/mojo/bindings/SerializationException.java",
+ "bindings/src/org/chromium/mojo/bindings/ServiceMessage.java",
+ "bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java",
+ "bindings/src/org/chromium/mojo/bindings/Struct.java",
+ "bindings/src/org/chromium/mojo/bindings/Union.java",
+ ]
+
+ deps = [
+ ":system_java",
+ "//base:base_java",
+ ]
+
+ srcjar_deps = [ "../interfaces/bindings:bindings_java_sources" ]
+}
diff --git a/mojo/public/java/bindings/README.md b/mojo/public/java/bindings/README.md
new file mode 100644
index 0000000000..821a230ed9
--- /dev/null
+++ b/mojo/public/java/bindings/README.md
@@ -0,0 +1,12 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java Bindings API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+
+This document provides a brief guide to API usage with example code snippets.
+For a detailed API references please consult the class definitions in
+[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/bindings/src/org/chromium/mojo/bindings/)
+
+TODO: Make the contents of this document less non-existent.
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java
new file mode 100644
index 0000000000..ee8f6310a1
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java
@@ -0,0 +1,11 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Associated interface is not supported yet.
+ */
+public class AssociatedInterfaceNotSupported {
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java
new file mode 100644
index 0000000000..1b07cd11ba
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java
@@ -0,0 +1,11 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Associated interface is not supported yet.
+ */
+public class AssociatedInterfaceRequestNotSupported {
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
new file mode 100644
index 0000000000..8a83be928e
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper around {@link Router} that will close the connection when not referenced anymore.
+ */
+class AutoCloseableRouter implements Router {
+
+ /**
+ * The underlying router.
+ */
+ private final Router mRouter;
+
+ /**
+ * The executor to close the underlying router.
+ */
+ private final Executor mExecutor;
+
+ /**
+ * Flags to keep track if this router has been correctly closed.
+ */
+ private boolean mClosed;
+
+ /**
+ * Constructor.
+ */
+ public AutoCloseableRouter(Core core, Router router) {
+ mRouter = router;
+ mExecutor = ExecutorFactory.getExecutorForCurrentThread(core);
+ }
+
+ /**
+ * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder)
+ */
+ @Override
+ public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) {
+ mRouter.setIncomingMessageReceiver(incomingMessageReceiver);
+ }
+
+ /**
+ * @see HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ return mRouter.passHandle();
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ return mRouter.accept(message);
+ }
+
+ /**
+ * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver)
+ */
+ @Override
+ public boolean acceptWithResponder(Message message, MessageReceiver responder) {
+ return mRouter.acceptWithResponder(message, responder);
+
+ }
+
+ /**
+ * @see Router#start()
+ */
+ @Override
+ public void start() {
+ mRouter.start();
+ }
+
+ /**
+ * @see Router#setErrorHandler(ConnectionErrorHandler)
+ */
+ @Override
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ mRouter.setErrorHandler(errorHandler);
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ mRouter.close();
+ mClosed = true;
+ }
+
+ /**
+ * @see Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ if (!mClosed) {
+ mExecutor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ close();
+ }
+ });
+ throw new IllegalStateException("Warning: Router objects should be explicitly closed " +
+ "when no longer required otherwise you may leak handles.");
+ }
+ super.finalize();
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
new file mode 100644
index 0000000000..f77399d1b4
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.Watcher;
+
+/**
+ * Helper functions.
+ */
+public class BindingsHelper {
+ /**
+ * Alignment in bytes for mojo serialization.
+ */
+ public static final int ALIGNMENT = 8;
+
+ /**
+ * The size, in bytes, of a serialized handle. A handle is serialized as an int representing the
+ * offset of the handle in the list of handles.
+ */
+ public static final int SERIALIZED_HANDLE_SIZE = 4;
+
+ /**
+ * The size, in bytes, of a serialized interface, which consists of a serialized handle (4
+ * bytes) and a version number (4 bytes).
+ */
+ public static final int SERIALIZED_INTERFACE_SIZE = 8;
+
+ /**
+ * The size, in bytes, of a serialized pointer. A pointer is serializaed as an unsigned long
+ * representing the offset from its position to the pointed elemnt.
+ */
+ public static final int POINTER_SIZE = 8;
+
+ /**
+ * The size, in bytes, of a serialized union.
+ */
+ public static final int UNION_SIZE = 16;
+
+ /**
+ * The header for a serialized map element.
+ */
+ public static final DataHeader MAP_STRUCT_HEADER = new DataHeader(24, 0);
+
+ /**
+ * The value used for the expected length of a non-fixed size array.
+ */
+ public static final int UNSPECIFIED_ARRAY_LENGTH = -1;
+
+ /**
+ * Passed as |arrayNullability| when neither the array nor its elements are nullable.
+ */
+ public static final int NOTHING_NULLABLE = 0;
+
+ /**
+ * "Array bit" of |arrayNullability| is set iff the array itself is nullable.
+ */
+ public static final int ARRAY_NULLABLE = (1 << 0);
+
+ /**
+ * "Element bit" of |arrayNullability| is set iff the array elements are nullable.
+ */
+ public static final int ELEMENT_NULLABLE = (1 << 1);
+
+ public static boolean isArrayNullable(int arrayNullability) {
+ return (arrayNullability & ARRAY_NULLABLE) > 0;
+ }
+
+ public static boolean isElementNullable(int arrayNullability) {
+ return (arrayNullability & ELEMENT_NULLABLE) > 0;
+ }
+
+ /**
+ * Align |size| on {@link BindingsHelper#ALIGNMENT}.
+ */
+ public static int align(int size) {
+ return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
+ }
+
+ /**
+ * Align |size| on {@link BindingsHelper#ALIGNMENT}.
+ */
+ public static long align(long size) {
+ return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
+ }
+
+ /**
+ * Compute the size in bytes of the given string encoded as utf8.
+ */
+ public static int utf8StringSizeInBytes(String s) {
+ int res = 0;
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ int codepoint = c;
+ if (isSurrogate(c)) {
+ i++;
+ char c2 = s.charAt(i);
+ codepoint = Character.toCodePoint(c, c2);
+ }
+ res += 1;
+ if (codepoint > 0x7f) {
+ res += 1;
+ if (codepoint > 0x7ff) {
+ res += 1;
+ if (codepoint > 0xffff) {
+ res += 1;
+ if (codepoint > 0x1fffff) {
+ res += 1;
+ if (codepoint > 0x3ffffff) {
+ res += 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Returns |true| if and only if the two objects are equals, handling |null|.
+ */
+ public static boolean equals(Object o1, Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+ if (o1 == null) {
+ return false;
+ }
+ return o1.equals(o2);
+ }
+
+ /**
+ * Returns the hash code of the object, handling |null|.
+ */
+ public static int hashCode(Object o) {
+ if (o == null) {
+ return 0;
+ }
+ return o.hashCode();
+ }
+
+ /**
+ * Returns the hash code of the value.
+ */
+ public static int hashCode(boolean o) {
+ return o ? 1231 : 1237;
+ }
+
+ /**
+ * Returns the hash code of the value.
+ */
+ public static int hashCode(long o) {
+ return (int) (o ^ (o >>> 32));
+ }
+
+ /**
+ * Returns the hash code of the value.
+ */
+ public static int hashCode(float o) {
+ return Float.floatToIntBits(o);
+ }
+
+ /**
+ * Returns the hash code of the value.
+ */
+ public static int hashCode(double o) {
+ return hashCode(Double.doubleToLongBits(o));
+ }
+
+ /**
+ * Returns the hash code of the value.
+ */
+ public static int hashCode(int o) {
+ return o;
+ }
+
+ /**
+ * Determines if the given {@code char} value is a Unicode <i>surrogate code unit</i>. See
+ * {@link Character#isSurrogate}. Extracting here because the method only exists at API level
+ * 19.
+ */
+ private static boolean isSurrogate(char c) {
+ return c >= Character.MIN_SURROGATE && c < (Character.MAX_SURROGATE + 1);
+ }
+
+ /**
+ * Returns an {@link AsyncWaiter} to use with the given handle, or |null| if none if available.
+ */
+ static Watcher getWatcherForHandle(Handle handle) {
+ if (handle.getCore() != null) {
+ return handle.getCore().getWatcher();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java
new file mode 100644
index 0000000000..c6b14c1494
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java
@@ -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 was generated using
+// mojo/tools/generate_java_callback_interfaces.py
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Contains a generic interface for callbacks.
+ */
+public interface Callbacks {
+
+ /**
+ * A generic callback.
+ */
+ interface Callback0 {
+ /**
+ * Call the callback.
+ */
+ public void call();
+ }
+
+ /**
+ * A generic 1-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ */
+ interface Callback1<T1> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1);
+ }
+
+ /**
+ * A generic 2-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ */
+ interface Callback2<T1, T2> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2);
+ }
+
+ /**
+ * A generic 3-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ */
+ interface Callback3<T1, T2, T3> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3);
+ }
+
+ /**
+ * A generic 4-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ */
+ interface Callback4<T1, T2, T3, T4> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
+ }
+
+ /**
+ * A generic 5-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ * @param <T5> the type of argument 5.
+ */
+ interface Callback5<T1, T2, T3, T4, T5> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
+ }
+
+ /**
+ * A generic 6-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ * @param <T5> the type of argument 5.
+ * @param <T6> the type of argument 6.
+ */
+ interface Callback6<T1, T2, T3, T4, T5, T6> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
+ }
+
+ /**
+ * A generic 7-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ * @param <T5> the type of argument 5.
+ * @param <T6> the type of argument 6.
+ * @param <T7> the type of argument 7.
+ */
+ interface Callback7<T1, T2, T3, T4, T5, T6, T7> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java
new file mode 100644
index 0000000000..f601fb81b5
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MojoException;
+
+/**
+ * A {@link ConnectionErrorHandler} is notified of an error happening while using the bindings over
+ * message pipes.
+ */
+public interface ConnectionErrorHandler {
+ public void onConnectionError(MojoException e);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java
new file mode 100644
index 0000000000..2aa5ea690c
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java
@@ -0,0 +1,214 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.ResultAnd;
+import org.chromium.mojo.system.Watcher;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link Connector} owns a {@link MessagePipeHandle} and will send any received messages to the
+ * registered {@link MessageReceiver}. It also acts as a {@link MessageReceiver} and will send any
+ * message through the handle.
+ * <p>
+ * The method |start| must be called before the {@link Connector} will start listening to incoming
+ * messages.
+ */
+public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle> {
+
+ /**
+ * The callback that is notified when the state of the owned handle changes.
+ */
+ private final WatcherCallback mWatcherCallback = new WatcherCallback();
+
+ /**
+ * The owned message pipe.
+ */
+ private final MessagePipeHandle mMessagePipeHandle;
+
+ /**
+ * A watcher which is notified when a new message is available on the owned message pipe.
+ */
+ private final Watcher mWatcher;
+
+ /**
+ * The {@link MessageReceiver} to which received messages are sent.
+ */
+ private MessageReceiver mIncomingMessageReceiver;
+
+ /**
+ * The error handler to notify of errors.
+ */
+ private ConnectionErrorHandler mErrorHandler;
+
+ /**
+ * Create a new connector over a |messagePipeHandle|. The created connector will use the default
+ * {@link AsyncWaiter} from the {@link Core} implementation of |messagePipeHandle|.
+ */
+ public Connector(MessagePipeHandle messagePipeHandle) {
+ this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle));
+ }
+
+ /**
+ * Create a new connector over a |messagePipeHandle| using the given {@link AsyncWaiter} to get
+ * notified of changes on the handle.
+ */
+ public Connector(MessagePipeHandle messagePipeHandle, Watcher watcher) {
+ mMessagePipeHandle = messagePipeHandle;
+ mWatcher = watcher;
+ }
+
+ /**
+ * Set the {@link MessageReceiver} that will receive message from the owned message pipe.
+ */
+ public void setIncomingMessageReceiver(MessageReceiver incomingMessageReceiver) {
+ mIncomingMessageReceiver = incomingMessageReceiver;
+ }
+
+ /**
+ * Set the {@link ConnectionErrorHandler} that will be notified of errors on the owned message
+ * pipe.
+ */
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ mErrorHandler = errorHandler;
+ }
+
+ /**
+ * Start listening for incoming messages.
+ */
+ public void start() {
+ mWatcher.start(mMessagePipeHandle, Core.HandleSignals.READABLE, mWatcherCallback);
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ try {
+ mMessagePipeHandle.writeMessage(message.getData(),
+ message.getHandles(), MessagePipeHandle.WriteFlags.NONE);
+ return true;
+ } catch (MojoException e) {
+ onError(e);
+ return false;
+ }
+ }
+
+ /**
+ * Pass the owned handle of the connector. After this, the connector is disconnected. It cannot
+ * accept new message and it isn't listening to the handle anymore.
+ *
+ * @see org.chromium.mojo.bindings.HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ cancelIfActive();
+ MessagePipeHandle handle = mMessagePipeHandle.pass();
+ if (mIncomingMessageReceiver != null) {
+ mIncomingMessageReceiver.close();
+ }
+ return handle;
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ cancelIfActive();
+ mMessagePipeHandle.close();
+ if (mIncomingMessageReceiver != null) {
+ MessageReceiver incomingMessageReceiver = mIncomingMessageReceiver;
+ mIncomingMessageReceiver = null;
+ incomingMessageReceiver.close();
+ }
+ }
+
+ private class WatcherCallback implements Watcher.Callback {
+ /**
+ * @see org.chromium.mojo.system.Watcher.Callback#onResult(int)
+ */
+ @Override
+ public void onResult(int result) {
+ Connector.this.onWatcherResult(result);
+ }
+
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Watcher.Callback#onResult(int)
+ */
+ private void onWatcherResult(int result) {
+ if (result == MojoResult.OK) {
+ readOutstandingMessages();
+ } else {
+ onError(new MojoException(result));
+ }
+ }
+
+ private void onError(MojoException exception) {
+ close();
+ if (mErrorHandler != null) {
+ mErrorHandler.onConnectionError(exception);
+ }
+ }
+
+ /**
+ * Read all available messages on the owned message pipe.
+ */
+ private void readOutstandingMessages() {
+ ResultAnd<Boolean> result;
+ do {
+ try {
+ result = readAndDispatchMessage(mMessagePipeHandle, mIncomingMessageReceiver);
+ } catch (MojoException e) {
+ onError(e);
+ return;
+ }
+ } while (result.getValue());
+ if (result.getMojoResult() != MojoResult.SHOULD_WAIT) {
+ onError(new MojoException(result.getMojoResult()));
+ }
+ }
+
+ private void cancelIfActive() {
+ mWatcher.cancel();
+ mWatcher.destroy();
+ }
+
+ /**
+ * Read a message, and pass it to the given |MessageReceiver| if not null. If the
+ * |MessageReceiver| is null, the message is lost.
+ *
+ * @param receiver The {@link MessageReceiver} that will receive the read {@link Message}. Can
+ * be <code>null</code>, in which case the message is discarded.
+ */
+ static ResultAnd<Boolean> readAndDispatchMessage(
+ MessagePipeHandle handle, MessageReceiver receiver) {
+ // TODO(qsr) Allow usage of a pool of pre-allocated buffer for performance.
+ ResultAnd<ReadMessageResult> result =
+ handle.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE);
+ if (result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) {
+ return new ResultAnd<Boolean>(result.getMojoResult(), false);
+ }
+ ReadMessageResult readResult = result.getValue();
+ assert readResult != null;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(readResult.getMessageSize());
+ result = handle.readMessage(
+ buffer, readResult.getHandlesCount(), MessagePipeHandle.ReadFlags.NONE);
+ if (receiver != null && result.getMojoResult() == MojoResult.OK) {
+ boolean accepted = receiver.accept(new Message(buffer, result.getValue().getHandles()));
+ return new ResultAnd<Boolean>(result.getMojoResult(), accepted);
+ }
+ return new ResultAnd<Boolean>(result.getMojoResult(), false);
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java
new file mode 100644
index 0000000000..96acec9438
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * The header for a mojo complex element.
+ */
+public final class DataHeader {
+ /**
+ * The size of a serialized header, in bytes.
+ */
+ public static final int HEADER_SIZE = 8;
+
+ /**
+ * The offset of the size field.
+ */
+ public static final int SIZE_OFFSET = 0;
+
+ /**
+ * The offset of the number of fields field.
+ */
+ public static final int ELEMENTS_OR_VERSION_OFFSET = 4;
+
+ /**
+ * The size of the object owning this header.
+ */
+ public final int size;
+
+ /**
+ * Number of element (for an array) or version (for a struct) of the object owning this
+ * header.
+ */
+ public final int elementsOrVersion;
+
+ /**
+ * Constructor.
+ */
+ public DataHeader(int size, int elementsOrVersion) {
+ super();
+ this.size = size;
+ this.elementsOrVersion = elementsOrVersion;
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + elementsOrVersion;
+ result = prime * result + size;
+ return result;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) return true;
+ if (object == null) return false;
+ if (getClass() != object.getClass()) return false;
+
+ DataHeader other = (DataHeader) object;
+ return (elementsOrVersion == other.elementsOrVersion && size == other.size);
+ }
+} \ No newline at end of file
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
new file mode 100644
index 0000000000..64ff1c08a1
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
@@ -0,0 +1,776 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Interface.Proxy;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.InvalidHandle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.SharedBufferHandle;
+import org.chromium.mojo.system.UntypedHandle;
+
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+/**
+ * A Decoder is a helper class for deserializing a mojo struct. It enables deserialization of basic
+ * types from a {@link Message} object at a given offset into it's byte buffer.
+ */
+public class Decoder {
+
+ /**
+ * Helper class to validate the decoded message.
+ */
+ static final class Validator {
+
+ /**
+ * Minimal value for the next handle to deserialize.
+ */
+ private int mMinNextClaimedHandle;
+ /**
+ * Minimal value of the start of the next memory to claim.
+ */
+ private long mMinNextMemory;
+ /**
+ * The current nesting level when decoding.
+ */
+ private long mStackDepth;
+
+ /**
+ * The maximal memory accessible.
+ */
+ private final long mMaxMemory;
+
+ /**
+ * The number of handles in the message.
+ */
+ private final long mNumberOfHandles;
+
+ /**
+ * The maximum nesting level when decoding.
+ */
+ private static final int MAX_RECURSION_DEPTH = 100;
+
+ /**
+ * Constructor.
+ */
+ Validator(long maxMemory, int numberOfHandles) {
+ mMaxMemory = maxMemory;
+ mNumberOfHandles = numberOfHandles;
+ mStackDepth = 0;
+ }
+
+ public void claimHandle(int handle) {
+ if (handle < mMinNextClaimedHandle) {
+ throw new DeserializationException(
+ "Trying to access handle out of order.");
+ }
+ if (handle >= mNumberOfHandles) {
+ throw new DeserializationException("Trying to access non present handle.");
+ }
+ mMinNextClaimedHandle = handle + 1;
+ }
+
+ public void claimMemory(long start, long end) {
+ if (start % BindingsHelper.ALIGNMENT != 0) {
+ throw new DeserializationException("Incorrect starting alignment: " + start + ".");
+ }
+ if (start < mMinNextMemory) {
+ throw new DeserializationException("Trying to access memory out of order.");
+ }
+ if (end < start) {
+ throw new DeserializationException("Incorrect memory range.");
+ }
+ if (end > mMaxMemory) {
+ throw new DeserializationException("Trying to access out of range memory.");
+ }
+ mMinNextMemory = BindingsHelper.align(end);
+ }
+
+ public void increaseStackDepth() {
+ ++mStackDepth;
+ if (mStackDepth >= MAX_RECURSION_DEPTH) {
+ throw new DeserializationException("Recursion depth limit exceeded.");
+ }
+ }
+
+ public void decreaseStackDepth() {
+ --mStackDepth;
+ }
+ }
+
+ /**
+ * The message to deserialize from.
+ */
+ private final Message mMessage;
+
+ /**
+ * The base offset in the byte buffer.
+ */
+ private final int mBaseOffset;
+
+ /**
+ * Validator for the decoded message.
+ */
+ private final Validator mValidator;
+
+ /**
+ * Constructor.
+ *
+ * @param message The message to decode.
+ */
+ public Decoder(Message message) {
+ this(message, new Validator(message.getData().limit(), message.getHandles().size()), 0);
+ }
+
+ private Decoder(Message message, Validator validator, int baseOffset) {
+ mMessage = message;
+ mMessage.getData().order(ByteOrder.LITTLE_ENDIAN);
+ mBaseOffset = baseOffset;
+ mValidator = validator;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the current position.
+ */
+ public DataHeader readDataHeader() {
+ // Claim the memory for the header.
+ mValidator.claimMemory(mBaseOffset, mBaseOffset + DataHeader.HEADER_SIZE);
+ DataHeader result = readDataHeaderAtOffset(0, false);
+ // Claim the rest of the memory.
+ mValidator.claimMemory(mBaseOffset + DataHeader.HEADER_SIZE, mBaseOffset + result.size);
+ return result;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} for an union at the given offset.
+ */
+ public DataHeader readDataHeaderForUnion(int offset) {
+ DataHeader result = readDataHeaderAtOffset(offset, true);
+ if (result.size == 0) {
+ if (result.elementsOrVersion != 0) {
+ throw new DeserializationException(
+ "Unexpected version tag for a null union. Expecting 0, found: "
+ + result.elementsOrVersion);
+ }
+ } else if (result.size != BindingsHelper.UNION_SIZE) {
+ throw new DeserializationException(
+ "Unexpected size of an union. The size must be 0 for a null union, or 16 for "
+ + "a non-null union.");
+ }
+ return result;
+ }
+
+ /**
+ * @returns a decoder suitable to decode an union defined as the root object of a message.
+ */
+ public Decoder decoderForSerializedUnion() {
+ mValidator.claimMemory(0, BindingsHelper.UNION_SIZE);
+ return this;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset.
+ */
+ private DataHeader readDataHeaderAtOffset(int offset, boolean isUnion) {
+ int size = readInt(offset + DataHeader.SIZE_OFFSET);
+ int elementsOrVersion = readInt(offset + DataHeader.ELEMENTS_OR_VERSION_OFFSET);
+ if (size < 0) {
+ throw new DeserializationException(
+ "Negative size. Unsigned integers are not valid for java.");
+ }
+ if (elementsOrVersion < 0 && (!isUnion || elementsOrVersion != -1)) {
+ throw new DeserializationException(
+ "Negative elements or version. Unsigned integers are not valid for java.");
+ }
+
+ return new DataHeader(size, elementsOrVersion);
+ }
+
+ public DataHeader readAndValidateDataHeader(DataHeader[] versionArray) {
+ DataHeader header = readDataHeader();
+ int maxVersionIndex = versionArray.length - 1;
+ if (header.elementsOrVersion <= versionArray[maxVersionIndex].elementsOrVersion) {
+ DataHeader referenceHeader = null;
+ for (int index = maxVersionIndex; index >= 0; index--) {
+ DataHeader dataHeader = versionArray[index];
+ if (header.elementsOrVersion >= dataHeader.elementsOrVersion) {
+ referenceHeader = dataHeader;
+ break;
+ }
+ }
+ if (referenceHeader == null || referenceHeader.size != header.size) {
+ throw new DeserializationException(
+ "Header doesn't correspond to any known version.");
+ }
+ } else {
+ if (header.size < versionArray[maxVersionIndex].size) {
+ throw new DeserializationException("Message newer than the last known version"
+ + " cannot be shorter than required by the last known version.");
+ }
+ }
+ return header;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an
+ * array where elements are pointers.
+ */
+ public DataHeader readDataHeaderForPointerArray(int expectedLength) {
+ return readDataHeaderForArray(BindingsHelper.POINTER_SIZE, expectedLength);
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an
+ * array where elements are unions.
+ */
+ public DataHeader readDataHeaderForUnionArray(int expectedLength) {
+ return readDataHeaderForArray(BindingsHelper.UNION_SIZE, expectedLength);
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for a map.
+ */
+ public void readDataHeaderForMap() {
+ DataHeader si = readDataHeader();
+ if (si.size != BindingsHelper.MAP_STRUCT_HEADER.size) {
+ throw new DeserializationException(
+ "Incorrect header for map. The size is incorrect.");
+ }
+ if (si.elementsOrVersion != BindingsHelper.MAP_STRUCT_HEADER.elementsOrVersion) {
+ throw new DeserializationException(
+ "Incorrect header for map. The version is incorrect.");
+ }
+ }
+
+ /**
+ * Deserializes a byte at the given offset.
+ */
+ public byte readByte(int offset) {
+ validateBufferSize(offset, 1);
+ return mMessage.getData().get(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a boolean at the given offset, re-using any partially read byte.
+ */
+ public boolean readBoolean(int offset, int bit) {
+ validateBufferSize(offset, 1);
+ return (readByte(offset) & (1 << bit)) != 0;
+ }
+
+ /**
+ * Deserializes a short at the given offset.
+ */
+ public short readShort(int offset) {
+ validateBufferSize(offset, 2);
+ return mMessage.getData().getShort(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes an int at the given offset.
+ */
+ public int readInt(int offset) {
+ validateBufferSize(offset, 4);
+ return mMessage.getData().getInt(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a float at the given offset.
+ */
+ public float readFloat(int offset) {
+ validateBufferSize(offset, 4);
+ return mMessage.getData().getFloat(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a long at the given offset.
+ */
+ public long readLong(int offset) {
+ validateBufferSize(offset, 8);
+ return mMessage.getData().getLong(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a double at the given offset.
+ */
+ public double readDouble(int offset) {
+ validateBufferSize(offset, 8);
+ return mMessage.getData().getDouble(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a pointer at the given offset. Returns a Decoder suitable to decode the content
+ * of the pointer.
+ */
+ public Decoder readPointer(int offset, boolean nullable) {
+ int basePosition = mBaseOffset + offset;
+ long pointerOffset = readLong(offset);
+ if (pointerOffset == 0) {
+ if (!nullable) {
+ throw new DeserializationException(
+ "Trying to decode null pointer for a non-nullable type.");
+ }
+ return null;
+ }
+ int newPosition = (int) (basePosition + pointerOffset);
+ // The method |getDecoderAtPosition| will validate that the pointer address is valid.
+ return getDecoderAtPosition(newPosition);
+
+ }
+
+ /**
+ * Deserializes an array of boolean at the given offset.
+ */
+ public boolean[] readBooleans(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForBooleanArray(expectedLength);
+ byte[] bytes = new byte[(si.elementsOrVersion + 7) / BindingsHelper.ALIGNMENT];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().get(bytes);
+ boolean[] result = new boolean[si.elementsOrVersion];
+ for (int i = 0; i < bytes.length; ++i) {
+ for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) {
+ int booleanIndex = i * BindingsHelper.ALIGNMENT + j;
+ if (booleanIndex < result.length) {
+ result[booleanIndex] = (bytes[i] & (1 << j)) != 0;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of bytes at the given offset.
+ */
+ public byte[] readBytes(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(1, expectedLength);
+ byte[] result = new byte[si.elementsOrVersion];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of shorts at the given offset.
+ */
+ public short[] readShorts(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(2, expectedLength);
+ short[] result = new short[si.elementsOrVersion];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asShortBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of ints at the given offset.
+ */
+ public int[] readInts(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ int[] result = new int[si.elementsOrVersion];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asIntBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of floats at the given offset.
+ */
+ public float[] readFloats(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ float[] result = new float[si.elementsOrVersion];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asFloatBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of longs at the given offset.
+ */
+ public long[] readLongs(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(8, expectedLength);
+ long[] result = new long[si.elementsOrVersion];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asLongBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of doubles at the given offset.
+ */
+ public double[] readDoubles(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(8, expectedLength);
+ double[] result = new double[si.elementsOrVersion];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asDoubleBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an |Handle| at the given offset.
+ */
+ public Handle readHandle(int offset, boolean nullable) {
+ int index = readInt(offset);
+ if (index == -1) {
+ if (!nullable) {
+ throw new DeserializationException(
+ "Trying to decode an invalid handle for a non-nullable type.");
+ }
+ return InvalidHandle.INSTANCE;
+ }
+ mValidator.claimHandle(index);
+ return mMessage.getHandles().get(index);
+ }
+
+ /**
+ * Deserializes an |UntypedHandle| at the given offset.
+ */
+ public UntypedHandle readUntypedHandle(int offset, boolean nullable) {
+ return readHandle(offset, nullable).toUntypedHandle();
+ }
+
+ /**
+ * Deserializes a |ConsumerHandle| at the given offset.
+ */
+ public DataPipe.ConsumerHandle readConsumerHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toDataPipeConsumerHandle();
+ }
+
+ /**
+ * Deserializes a |ProducerHandle| at the given offset.
+ */
+ public DataPipe.ProducerHandle readProducerHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toDataPipeProducerHandle();
+ }
+
+ /**
+ * Deserializes a |MessagePipeHandle| at the given offset.
+ */
+ public MessagePipeHandle readMessagePipeHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toMessagePipeHandle();
+ }
+
+ /**
+ * Deserializes a |SharedBufferHandle| at the given offset.
+ */
+ public SharedBufferHandle readSharedBufferHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toSharedBufferHandle();
+ }
+
+ /**
+ * Deserializes an interface at the given offset.
+ *
+ * @return a proxy to the service.
+ */
+ public <P extends Proxy> P readServiceInterface(int offset, boolean nullable,
+ Interface.Manager<?, P> manager) {
+ MessagePipeHandle handle = readMessagePipeHandle(offset, nullable);
+ if (!handle.isValid()) {
+ return null;
+ }
+ int version = readInt(offset + BindingsHelper.SERIALIZED_HANDLE_SIZE);
+ return manager.attachProxy(handle, version);
+ }
+
+ /**
+ * Deserializes a |InterfaceRequest| at the given offset.
+ */
+ public <I extends Interface> InterfaceRequest<I> readInterfaceRequest(int offset,
+ boolean nullable) {
+ MessagePipeHandle handle = readMessagePipeHandle(offset, nullable);
+ if (handle == null) {
+ return null;
+ }
+ return new InterfaceRequest<I>(handle);
+ }
+
+ /**
+ * Deserializes an associated interface at the given offset. Not yet supported.
+ */
+ public AssociatedInterfaceNotSupported readAssociatedServiceInterfaceNotSupported(int offset,
+ boolean nullable) {
+ return null;
+ }
+
+ /**
+ * Deserializes an associated interface request at the given offset. Not yet supported.
+ */
+ public AssociatedInterfaceRequestNotSupported readAssociatedInterfaceRequestNotSupported(
+ int offset, boolean nullable) {
+ return null;
+ }
+
+ /**
+ * Deserializes a string at the given offset.
+ */
+ public String readString(int offset, boolean nullable) {
+ final int arrayNullability = nullable ? BindingsHelper.ARRAY_NULLABLE : 0;
+ byte[] bytes = readBytes(offset, arrayNullability, BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+ if (bytes == null) {
+ return null;
+ }
+ return new String(bytes, Charset.forName("utf8"));
+ }
+
+ /**
+ * Deserializes an array of |Handle| at the given offset.
+ */
+ public Handle[] readHandles(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ Handle[] result = new Handle[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |UntypedHandle| at the given offset.
+ */
+ public UntypedHandle[] readUntypedHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ UntypedHandle[] result = new UntypedHandle[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readUntypedHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |ConsumerHandle| at the given offset.
+ */
+ public DataPipe.ConsumerHandle[] readConsumerHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ DataPipe.ConsumerHandle[] result = new DataPipe.ConsumerHandle[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readConsumerHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |ProducerHandle| at the given offset.
+ */
+ public DataPipe.ProducerHandle[] readProducerHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ DataPipe.ProducerHandle[] result = new DataPipe.ProducerHandle[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readProducerHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+
+ }
+
+ /**
+ * Deserializes an array of |MessagePipeHandle| at the given offset.
+ */
+ public MessagePipeHandle[] readMessagePipeHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ MessagePipeHandle[] result = new MessagePipeHandle[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readMessagePipeHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+
+ }
+
+ /**
+ * Deserializes an array of |SharedBufferHandle| at the given offset.
+ */
+ public SharedBufferHandle[] readSharedBufferHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ SharedBufferHandle[] result = new SharedBufferHandle[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readSharedBufferHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+
+ }
+
+ /**
+ * Deserializes an array of |ServiceHandle| at the given offset.
+ */
+ public <S extends Interface, P extends Proxy> S[] readServiceInterfaces(
+ int offset, int arrayNullability, int expectedLength, Interface.Manager<S, P> manager) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si =
+ d.readDataHeaderForArray(BindingsHelper.SERIALIZED_INTERFACE_SIZE, expectedLength);
+ S[] result = manager.buildArray(si.elementsOrVersion);
+ for (int i = 0; i < result.length; ++i) {
+ // This cast is necessary because java 6 doesn't handle wildcard correctly when using
+ // Manager<S, ? extends S>
+ @SuppressWarnings("unchecked")
+ S value = (S) d.readServiceInterface(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability), manager);
+ result[i] = value;
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |InterfaceRequest| at the given offset.
+ */
+ public <I extends Interface> InterfaceRequest<I>[] readInterfaceRequests(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ @SuppressWarnings("unchecked")
+ InterfaceRequest<I>[] result = new InterfaceRequest[si.elementsOrVersion];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readInterfaceRequest(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of associated interfaces at the given offset. Not yet supported.
+ */
+ public AssociatedInterfaceNotSupported[] readAssociatedServiceInterfaceNotSupporteds(
+ int offset, int arrayNullability, int expectedLength) {
+ return null;
+ }
+
+ /**
+ * Deserializes an array of associated interface requests at the given offset. Not yet
+ * supported.
+ */
+ public AssociatedInterfaceRequestNotSupported[] readAssociatedInterfaceRequestNotSupporteds(
+ int offset, int arrayNullability, int expectedLength) {
+ return null;
+ }
+
+ /**
+ * Returns a view of this decoder at the offset |offset|.
+ */
+ private Decoder getDecoderAtPosition(int offset) {
+ return new Decoder(mMessage, mValidator, offset);
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an
+ * array of booleans.
+ */
+ private DataHeader readDataHeaderForBooleanArray(int expectedLength) {
+ DataHeader dataHeader = readDataHeader();
+ if (dataHeader.size < DataHeader.HEADER_SIZE + (dataHeader.elementsOrVersion + 7) / 8) {
+ throw new DeserializationException("Array header is incorrect.");
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && dataHeader.elementsOrVersion != expectedLength) {
+ throw new DeserializationException("Incorrect array length. Expected: " + expectedLength
+ + ", but got: " + dataHeader.elementsOrVersion + ".");
+ }
+ return dataHeader;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} of an array at the given offset.
+ */
+ private DataHeader readDataHeaderForArray(long elementSize, int expectedLength) {
+ DataHeader dataHeader = readDataHeader();
+ if (dataHeader.size
+ < (DataHeader.HEADER_SIZE + elementSize * dataHeader.elementsOrVersion)) {
+ throw new DeserializationException("Array header is incorrect.");
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && dataHeader.elementsOrVersion != expectedLength) {
+ throw new DeserializationException("Incorrect array length. Expected: " + expectedLength
+ + ", but got: " + dataHeader.elementsOrVersion + ".");
+ }
+ return dataHeader;
+ }
+
+ private void validateBufferSize(int offset, int size) {
+ if (mMessage.getData().limit() < offset + size) {
+ throw new DeserializationException("Buffer is smaller than expected.");
+ }
+ }
+
+ public void increaseStackDepth() {
+ mValidator.increaseStackDepth();
+ }
+
+ public void decreaseStackDepth() {
+ mValidator.decreaseStackDepth();
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java
new file mode 100644
index 0000000000..2ee2ae7493
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MojoException;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A {@link ConnectionErrorHandler} that delegate the errors to a list of registered handlers. This
+ * class will use weak pointers to prevent keeping references to any handlers it delegates to.
+ */
+public class DelegatingConnectionErrorHandler implements ConnectionErrorHandler {
+
+ /**
+ * The registered handlers. This uses a {@link WeakHashMap} so that it doesn't prevent the
+ * handler from being garbage collected.
+ */
+ private final Set<ConnectionErrorHandler> mHandlers =
+ Collections.newSetFromMap(new WeakHashMap<ConnectionErrorHandler, Boolean>());
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ for (ConnectionErrorHandler handler : mHandlers) {
+ handler.onConnectionError(e);
+ }
+ }
+
+ /**
+ * Add a handler that will be notified of any error this object receives.
+ */
+ public void addConnectionErrorHandler(ConnectionErrorHandler handler) {
+ mHandlers.add(handler);
+ }
+
+ /**
+ * Remove a previously registered handler.
+ */
+ public void removeConnectionErrorHandler(ConnectionErrorHandler handler) {
+ mHandlers.remove(handler);
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java
new file mode 100644
index 0000000000..eeb511c520
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Error when deserializing a mojo message.
+ */
+public class DeserializationException extends RuntimeException {
+
+ /**
+ * Constructs a new deserialization exception with the specified detail message.
+ */
+ public DeserializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new deserialization exception with the specified cause.
+ */
+ public DeserializationException(Exception cause) {
+ super(cause);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
new file mode 100644
index 0000000000..3c86a8daa4
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
@@ -0,0 +1,587 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Interface.Proxy.Handler;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.Pair;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to encode a mojo struct. It keeps track of the output buffer, resizing it as needed.
+ * It also keeps track of the associated handles, and the offset of the current data section.
+ */
+public class Encoder {
+
+ /**
+ * Container class for all state that must be shared between the main encoder and any used sub
+ * encoder.
+ */
+ private static class EncoderState {
+
+ /**
+ * The core used to encode interfaces.
+ */
+ public final Core core;
+
+ /**
+ * The ByteBuffer to which the message will be encoded.
+ */
+ public ByteBuffer byteBuffer;
+
+ /**
+ * The list of encountered handles.
+ */
+ public final List<Handle> handles = new ArrayList<Handle>();
+
+ /**
+ * The current absolute position for the next data section.
+ */
+ public int dataEnd;
+
+ /**
+ * @param core the |Core| implementation used to generate handles. Only used if the data
+ * structure being encoded contains interfaces, can be |null| otherwise.
+ * @param bufferSize A hint on the size of the message. Used to build the initial byte
+ * buffer.
+ */
+ private EncoderState(Core core, int bufferSize) {
+ assert bufferSize % BindingsHelper.ALIGNMENT == 0;
+ this.core = core;
+ byteBuffer = ByteBuffer.allocateDirect(
+ bufferSize > 0 ? bufferSize : INITIAL_BUFFER_SIZE);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ dataEnd = 0;
+ }
+
+ /**
+ * Claim the given amount of memory at the end of the buffer, resizing it if needed.
+ */
+ public void claimMemory(int size) {
+ dataEnd += size;
+ growIfNeeded();
+ }
+
+ /**
+ * Grow the associated ByteBuffer if needed.
+ */
+ private void growIfNeeded() {
+ if (byteBuffer.capacity() >= dataEnd) {
+ return;
+ }
+ int targetSize = byteBuffer.capacity() * 2;
+ while (targetSize < dataEnd) {
+ targetSize *= 2;
+ }
+ ByteBuffer newBuffer = ByteBuffer.allocateDirect(targetSize);
+ newBuffer.order(ByteOrder.nativeOrder());
+ byteBuffer.position(0);
+ byteBuffer.limit(byteBuffer.capacity());
+ newBuffer.put(byteBuffer);
+ byteBuffer = newBuffer;
+ }
+ }
+
+ /**
+ * Default initial size of the data buffer. This must be a multiple of 8 bytes.
+ */
+ private static final int INITIAL_BUFFER_SIZE = 1024;
+
+ /**
+ * Base offset in the byte buffer for writing.
+ */
+ private int mBaseOffset;
+
+ /**
+ * The encoder state shared by the main encoder and all its sub-encoder.
+ */
+ private final EncoderState mEncoderState;
+
+ /**
+ * Returns the result message.
+ */
+ public Message getMessage() {
+ mEncoderState.byteBuffer.position(0);
+ mEncoderState.byteBuffer.limit(mEncoderState.dataEnd);
+ return new Message(mEncoderState.byteBuffer, mEncoderState.handles);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param core the |Core| implementation used to generate handles. Only used if the data
+ * structure being encoded contains interfaces, can be |null| otherwise.
+ * @param sizeHint A hint on the size of the message. Used to build the initial byte buffer.
+ */
+ public Encoder(Core core, int sizeHint) {
+ this(new EncoderState(core, sizeHint));
+ }
+
+ /**
+ * Private constructor for sub-encoders.
+ */
+ private Encoder(EncoderState bufferInformation) {
+ mEncoderState = bufferInformation;
+ mBaseOffset = bufferInformation.dataEnd;
+ }
+
+ /**
+ * Returns a new encoder that will append to the current buffer.
+ */
+ public Encoder getEncoderAtDataOffset(DataHeader dataHeader) {
+ Encoder result = new Encoder(mEncoderState);
+ result.encode(dataHeader);
+ return result;
+ }
+
+ /**
+ * Encode a {@link DataHeader} and claim the amount of memory required for the data section
+ * (resizing the buffer if required).
+ */
+ public void encode(DataHeader s) {
+ mEncoderState.claimMemory(BindingsHelper.align(s.size));
+ encode(s.size, DataHeader.SIZE_OFFSET);
+ encode(s.elementsOrVersion, DataHeader.ELEMENTS_OR_VERSION_OFFSET);
+ }
+
+ /**
+ * Encode a byte at the given offset.
+ */
+ public void encode(byte v, int offset) {
+ mEncoderState.byteBuffer.put(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a boolean at the given offset.
+ */
+ public void encode(boolean v, int offset, int bit) {
+ if (v) {
+ byte encodedValue = mEncoderState.byteBuffer.get(mBaseOffset + offset);
+ encodedValue |= (byte) (1 << bit);
+ mEncoderState.byteBuffer.put(mBaseOffset + offset, encodedValue);
+ }
+ }
+
+ /**
+ * Encode a short at the given offset.
+ */
+ public void encode(short v, int offset) {
+ mEncoderState.byteBuffer.putShort(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode an int at the given offset.
+ */
+ public void encode(int v, int offset) {
+ mEncoderState.byteBuffer.putInt(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a float at the given offset.
+ */
+ public void encode(float v, int offset) {
+ mEncoderState.byteBuffer.putFloat(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a long at the given offset.
+ */
+ public void encode(long v, int offset) {
+ mEncoderState.byteBuffer.putLong(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a double at the given offset.
+ */
+ public void encode(double v, int offset) {
+ mEncoderState.byteBuffer.putDouble(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a {@link Struct} at the given offset.
+ */
+ public void encode(Struct v, int offset, boolean nullable) {
+ if (v == null) {
+ encodeNullPointer(offset, nullable);
+ return;
+ }
+ encodePointerToNextUnclaimedData(offset);
+ v.encode(this);
+ }
+
+ /**
+ * Encode a {@link Union} at the given offset.
+ */
+ public void encode(Union v, int offset, boolean nullable) {
+ if (v == null && !nullable) {
+ throw new SerializationException(
+ "Trying to encode a null pointer for a non-nullable type.");
+ }
+ if (v == null) {
+ encode(0L, offset);
+ encode(0L, offset + DataHeader.HEADER_SIZE);
+ return;
+ }
+ v.encode(this, offset);
+ }
+
+ /**
+ * Encodes a String.
+ */
+ public void encode(String v, int offset, boolean nullable) {
+ if (v == null) {
+ encodeNullPointer(offset, nullable);
+ return;
+ }
+ final int arrayNullability = nullable
+ ? BindingsHelper.ARRAY_NULLABLE : BindingsHelper.NOTHING_NULLABLE;
+ encode(v.getBytes(Charset.forName("utf8")), offset, arrayNullability,
+ BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+ }
+
+ /**
+ * Encodes a {@link Handle}.
+ */
+ public void encode(Handle v, int offset, boolean nullable) {
+ if (v == null || !v.isValid()) {
+ encodeInvalidHandle(offset, nullable);
+ } else {
+ encode(mEncoderState.handles.size(), offset);
+ mEncoderState.handles.add(v);
+ }
+ }
+
+ /**
+ * Encode an {@link Interface}.
+ */
+ public <T extends Interface> void encode(T v, int offset, boolean nullable,
+ Interface.Manager<T, ?> manager) {
+ if (v == null) {
+ encodeInvalidHandle(offset, nullable);
+ encode(0, offset + BindingsHelper.SERIALIZED_HANDLE_SIZE);
+ return;
+ }
+ if (mEncoderState.core == null) {
+ throw new UnsupportedOperationException(
+ "The encoder has been created without a Core. It can't encode an interface.");
+ }
+ // If the instance is a proxy, pass the proxy's handle instead of creating a new stub.
+ if (v instanceof Interface.Proxy) {
+ Handler handler = ((Interface.Proxy) v).getProxyHandler();
+ encode(handler.passHandle(), offset, nullable);
+ encode(handler.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE);
+ return;
+ }
+ Pair<MessagePipeHandle, MessagePipeHandle> handles =
+ mEncoderState.core.createMessagePipe(null);
+ manager.bind(v, handles.first);
+ encode(handles.second, offset, nullable);
+ encode(manager.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE);
+ }
+
+ /**
+ * Encode an {@link InterfaceRequest}.
+ */
+ public <I extends Interface> void encode(InterfaceRequest<I> v, int offset, boolean nullable) {
+ if (v == null) {
+ encodeInvalidHandle(offset, nullable);
+ return;
+ }
+ if (mEncoderState.core == null) {
+ throw new UnsupportedOperationException(
+ "The encoder has been created without a Core. It can't encode an interface.");
+ }
+ encode(v.passHandle(), offset, nullable);
+ }
+
+ /**
+ * Encode an associated interface. Not yet supported.
+ */
+ public void encode(AssociatedInterfaceNotSupported v, int offset, boolean nullable) {
+ }
+
+ /**
+ * Encode an associated interface request. Not yet supported.
+ */
+ public void encode(AssociatedInterfaceRequestNotSupported v, int offset, boolean nullable) {
+ }
+
+ /**
+ * Returns an {@link Encoder} suitable for encoding an array of pointer of the given length.
+ */
+ public Encoder encodePointerArray(int length, int offset, int expectedLength) {
+ return encoderForArray(BindingsHelper.POINTER_SIZE, length, offset, expectedLength);
+ }
+
+ /**
+ * Returns an {@link Encoder} suitable for encoding an array of union of the given length.
+ */
+ public Encoder encodeUnionArray(int length, int offset, int expectedLength) {
+ return encoderForArray(BindingsHelper.UNION_SIZE, length, offset, expectedLength);
+ }
+
+ /**
+ * Encodes an array of booleans.
+ */
+ public void encode(boolean[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && expectedLength != v.length) {
+ throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+ }
+ byte[] bytes = new byte[(v.length + 7) / BindingsHelper.ALIGNMENT];
+ for (int i = 0; i < bytes.length; ++i) {
+ for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) {
+ int booleanIndex = BindingsHelper.ALIGNMENT * i + j;
+ if (booleanIndex < v.length && v[booleanIndex]) {
+ bytes[i] |= (byte) (1 << j);
+ }
+ }
+ }
+ encodeByteArray(bytes, v.length, offset);
+ }
+
+ /**
+ * Encodes an array of bytes.
+ */
+ public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && expectedLength != v.length) {
+ throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+ }
+ encodeByteArray(v, v.length, offset);
+ }
+
+ /**
+ * Encodes an array of shorts.
+ */
+ public void encode(short[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(2, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of ints.
+ */
+ public void encode(int[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(4, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of floats.
+ */
+ public void encode(float[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(4, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of longs.
+ */
+ public void encode(long[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(8, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of doubles.
+ */
+ public void encode(double[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(8, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of {@link Handle}.
+ */
+ public void encode(Handle[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ Encoder e = encoderForArray(
+ BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength);
+ for (int i = 0; i < v.length; ++i) {
+ e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ }
+
+ /**
+ * Encodes an array of {@link Interface}.
+ */
+ public <T extends Interface> void encode(T[] v, int offset, int arrayNullability,
+ int expectedLength, Interface.Manager<T, ?> manager) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ Encoder e = encoderForArray(
+ BindingsHelper.SERIALIZED_INTERFACE_SIZE, v.length, offset, expectedLength);
+ for (int i = 0; i < v.length; ++i) {
+ e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability), manager);
+ }
+ }
+
+ public Encoder encoderForMap(int offset) {
+ encodePointerToNextUnclaimedData(offset);
+ return getEncoderAtDataOffset(BindingsHelper.MAP_STRUCT_HEADER);
+ }
+
+ /**
+ * Encodes a pointer to the next unclaimed memory and returns an encoder suitable to encode an
+ * union at this location.
+ */
+ public Encoder encoderForUnionPointer(int offset) {
+ encodePointerToNextUnclaimedData(offset);
+ Encoder result = new Encoder(mEncoderState);
+ result.mEncoderState.claimMemory(16);
+ return result;
+ }
+
+ /**
+ * Encodes an array of {@link InterfaceRequest}.
+ */
+ public <I extends Interface> void encode(InterfaceRequest<I>[] v, int offset,
+ int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ Encoder e = encoderForArray(
+ BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength);
+ for (int i = 0; i < v.length; ++i) {
+ e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ }
+
+ /**
+ * Encodes an array of associated interfaces. Not yet supported.
+ */
+ public void encode(AssociatedInterfaceNotSupported[] v, int offset, int arrayNullability,
+ int expectedLength) {}
+
+ /**
+ * Encodes an array of associated interface requests. Not yet supported.
+ */
+ public void encode(AssociatedInterfaceRequestNotSupported[] v, int offset, int arrayNullability,
+ int expectedLength) {}
+
+ /**
+ * Encodes a <code>null</code> pointer iff the object is nullable, raises an exception
+ * otherwise.
+ */
+ public void encodeNullPointer(int offset, boolean nullable) {
+ if (!nullable) {
+ throw new SerializationException(
+ "Trying to encode a null pointer for a non-nullable type.");
+ }
+ mEncoderState.byteBuffer.putLong(mBaseOffset + offset, 0);
+ }
+
+ /**
+ * Encodes an invalid handle iff the object is nullable, raises an exception otherwise.
+ */
+ public void encodeInvalidHandle(int offset, boolean nullable) {
+ if (!nullable) {
+ throw new SerializationException(
+ "Trying to encode an invalid handle for a non-nullable type.");
+ }
+ mEncoderState.byteBuffer.putInt(mBaseOffset + offset, -1);
+ }
+
+ /**
+ * Claim the given amount of memory at the end of the buffer, resizing it if needed.
+ */
+ void claimMemory(int size) {
+ mEncoderState.claimMemory(BindingsHelper.align(size));
+ }
+
+ private void encodePointerToNextUnclaimedData(int offset) {
+ encode((long) mEncoderState.dataEnd - (mBaseOffset + offset), offset);
+ }
+
+ private Encoder encoderForArray(
+ int elementSizeInByte, int length, int offset, int expectedLength) {
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && expectedLength != length) {
+ throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+ }
+ return encoderForArrayByTotalSize(length * elementSizeInByte, length, offset);
+ }
+
+ private Encoder encoderForArrayByTotalSize(int byteSize, int length, int offset) {
+ encodePointerToNextUnclaimedData(offset);
+ return getEncoderAtDataOffset(
+ new DataHeader(DataHeader.HEADER_SIZE + byteSize, length));
+ }
+
+ private void encodeByteArray(byte[] bytes, int length, int offset) {
+ encoderForArrayByTotalSize(bytes.length, length, offset).append(bytes);
+ }
+
+ private void append(byte[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.put(v);
+ }
+
+ private void append(short[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asShortBuffer().put(v);
+ }
+
+ private void append(int[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asIntBuffer().put(v);
+ }
+
+ private void append(float[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asFloatBuffer().put(v);
+ }
+
+ private void append(double[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asDoubleBuffer().put(v);
+ }
+
+ private void append(long[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asLongBuffer().put(v);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
new file mode 100644
index 0000000000..bb49cbc4bd
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.ResultAnd;
+import org.chromium.mojo.system.Watcher;
+import org.chromium.mojo.system.Watcher.Callback;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A factory which provides per-thread executors, which enable execution on the thread from which
+ * they were obtained.
+ */
+class ExecutorFactory {
+
+ /**
+ * A null buffer which is used to send messages without any data on the PipedExecutor's
+ * signaling handles.
+ */
+ private static final ByteBuffer NOTIFY_BUFFER = null;
+
+ /**
+ * Implementation of the executor which uses a pair of {@link MessagePipeHandle} for signaling.
+ * The executor will wait asynchronously on one end of a {@link MessagePipeHandle} on the thread
+ * on which it was created. Other threads can call execute with a {@link Runnable}, and the
+ * executor will queue the {@link Runnable} and write a message on the other end of the handle.
+ * This will wake up the executor which is waiting on the handle, which will then dequeue the
+ * {@link Runnable} and execute it on the original thread.
+ */
+ private static class PipedExecutor implements Executor, Callback {
+
+ /**
+ * The handle which is written to. Access to this object must be protected with |mLock|.
+ */
+ private final MessagePipeHandle mWriteHandle;
+ /**
+ * The handle which is read from.
+ */
+ private final MessagePipeHandle mReadHandle;
+ /**
+ * The list of actions left to be run. Access to this object must be protected with |mLock|.
+ */
+ private final List<Runnable> mPendingActions;
+ /**
+ * Lock protecting access to |mWriteHandle| and |mPendingActions|.
+ */
+ private final Object mLock;
+ /**
+ * The {@link Watcher} to get notified of new message availability on |mReadHandle|.
+ */
+ private final Watcher mWatcher;
+
+ /**
+ * Constructor.
+ */
+ public PipedExecutor(Core core) {
+ mWatcher = core.getWatcher();
+ assert mWatcher != null;
+ mLock = new Object();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(
+ new MessagePipeHandle.CreateOptions());
+ mReadHandle = handles.first;
+ mWriteHandle = handles.second;
+ mPendingActions = new ArrayList<Runnable>();
+ mWatcher.start(mReadHandle, Core.HandleSignals.READABLE, this);
+ }
+
+ /**
+ * @see Callback#onResult(int)
+ */
+ @Override
+ public void onResult(int result) {
+ if (result == MojoResult.OK && readNotifyBufferMessage()) {
+ runNextAction();
+ } else {
+ close();
+ }
+ }
+
+ /**
+ * Close the handles. Should only be called on the executor thread.
+ */
+ private void close() {
+ synchronized (mLock) {
+ mWriteHandle.close();
+ mPendingActions.clear();
+ }
+ mWatcher.cancel();
+ mWatcher.destroy();
+ mReadHandle.close();
+ }
+
+ /**
+ * Read the next message on |mReadHandle|, and return |true| if successful, |false|
+ * otherwise.
+ */
+ private boolean readNotifyBufferMessage() {
+ try {
+ ResultAnd<ReadMessageResult> readMessageResult =
+ mReadHandle.readMessage(NOTIFY_BUFFER, 0, MessagePipeHandle.ReadFlags.NONE);
+ if (readMessageResult.getMojoResult() == MojoResult.OK) {
+ return true;
+ }
+ } catch (MojoException e) {
+ // Will be closed by the fall back at the end of this method.
+ }
+ return false;
+ }
+
+ /**
+ * Run the next action in the |mPendingActions| queue.
+ */
+ private void runNextAction() {
+ Runnable toRun = null;
+ synchronized (mLock) {
+ toRun = mPendingActions.remove(0);
+ }
+ toRun.run();
+ }
+
+ /**
+ * Execute the given |command| in the executor thread. This can be called on any thread.
+ *
+ * @see Executor#execute(Runnable)
+ */
+ @Override
+ public void execute(Runnable command) {
+ // Accessing the write handle must be protected by the lock, because it can be closed
+ // from the executor's thread.
+ synchronized (mLock) {
+ if (!mWriteHandle.isValid()) {
+ throw new IllegalStateException(
+ "Trying to execute an action on a closed executor.");
+ }
+ mPendingActions.add(command);
+ mWriteHandle.writeMessage(NOTIFY_BUFFER, null, MessagePipeHandle.WriteFlags.NONE);
+ }
+ }
+ }
+
+ /**
+ * Keep one executor per executor thread.
+ */
+ private static final ThreadLocal<Executor> EXECUTORS = new ThreadLocal<Executor>();
+
+ /**
+ * Returns an {@link Executor} that will run all of its actions in the current thread.
+ */
+ public static Executor getExecutorForCurrentThread(Core core) {
+ Executor executor = EXECUTORS.get();
+ if (executor == null) {
+ executor = new PipedExecutor(core);
+ EXECUTORS.set(executor);
+ }
+ return executor;
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java
new file mode 100644
index 0000000000..60fc33ba4e
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Handle;
+
+import java.io.Closeable;
+
+/**
+ * Describes a class that owns a handle.
+ *
+ * @param <H> The type of the owned handle.
+ */
+public interface HandleOwner<H extends Handle> extends Closeable {
+
+ /**
+ * Pass the handle owned by this class.
+ */
+ public H passHandle();
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close();
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java
new file mode 100644
index 0000000000..2699ab899a
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java
@@ -0,0 +1,425 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Callbacks.Callback1;
+import org.chromium.mojo.bindings.Interface.AbstractProxy.HandlerImpl;
+import org.chromium.mojo.bindings.interfacecontrol.QueryVersion;
+import org.chromium.mojo.bindings.interfacecontrol.RequireVersion;
+import org.chromium.mojo.bindings.interfacecontrol.RunInput;
+import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams;
+import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput;
+import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams;
+import org.chromium.mojo.bindings.interfacecontrol.RunOutput;
+import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.Pair;
+
+import java.io.Closeable;
+
+/**
+ * Base class for mojo generated interfaces.
+ */
+public interface Interface extends ConnectionErrorHandler, Closeable {
+
+ /**
+ * The close method is called when the connection to the interface is closed.
+ *
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close();
+
+ /**
+ * A proxy to a mojo interface. This is base class for all generated proxies. It implements the
+ * Interface and each time a method is called, the parameters are serialized and sent to the
+ * {@link MessageReceiverWithResponder}, along with the response callback if needed.
+ */
+ public interface Proxy extends Interface {
+ /**
+ * Class allowing to interact with the proxy itself.
+ */
+ public interface Handler extends Closeable {
+ /**
+ * Sets the {@link ConnectionErrorHandler} that will be notified of errors.
+ */
+ public void setErrorHandler(ConnectionErrorHandler errorHandler);
+
+ /**
+ * Unbinds the proxy and passes the handle. Can return null if the proxy is not bound or
+ * if the proxy is not over a message pipe.
+ */
+ public MessagePipeHandle passHandle();
+
+ /**
+ * Returns the version number of the interface that the remote side supports.
+ */
+ public int getVersion();
+
+ /**
+ * Queries the max version that the remote side supports. On completion, the result will
+ * be returned as the input of |callback|. The version number of this interface pointer
+ * will also be updated.
+ */
+ public void queryVersion(Callback1<Integer> callback);
+
+ /**
+ * If the remote side doesn't support the specified version, it will close its end of
+ * the message pipe asynchronously. The call does nothing if |version| is no greater
+ * than getVersion().
+ * <p>
+ * If you make a call to requireVersion() with a version number X which is not supported
+ * by the remote side, it is guaranteed that all calls to the interface methods after
+ * requireVersion(X) will be ignored.
+ */
+ public void requireVersion(int version);
+ }
+
+ /**
+ * Returns the {@link Handler} object allowing to interact with the proxy itself.
+ */
+ public Handler getProxyHandler();
+ }
+
+ /**
+ * Base implementation of {@link Proxy}.
+ */
+ abstract class AbstractProxy implements Proxy {
+ /**
+ * Implementation of {@link Handler}.
+ */
+ protected static class HandlerImpl implements Proxy.Handler, ConnectionErrorHandler {
+ /**
+ * The {@link Core} implementation to use.
+ */
+ private final Core mCore;
+
+ /**
+ * The {@link MessageReceiverWithResponder} that will receive a serialized message for
+ * each method call.
+ */
+ private final MessageReceiverWithResponder mMessageReceiver;
+
+ /**
+ * The {@link ConnectionErrorHandler} that will be notified of errors.
+ */
+ private ConnectionErrorHandler mErrorHandler;
+
+ /**
+ * The currently known version of the interface.
+ */
+ private int mVersion;
+
+ /**
+ * Constructor.
+ *
+ * @param core the Core implementation used to create pipes and access the async waiter.
+ * @param messageReceiver the message receiver to send message to.
+ */
+ protected HandlerImpl(Core core, MessageReceiverWithResponder messageReceiver) {
+ this.mCore = core;
+ this.mMessageReceiver = messageReceiver;
+ }
+
+ void setVersion(int version) {
+ mVersion = version;
+ }
+
+ /**
+ * Returns the message receiver to send message to.
+ */
+ public MessageReceiverWithResponder getMessageReceiver() {
+ return mMessageReceiver;
+ }
+
+ /**
+ * Returns the Core implementation.
+ */
+ public Core getCore() {
+ return mCore;
+ }
+
+ /**
+ * Sets the {@link ConnectionErrorHandler} that will be notified of errors.
+ */
+ @Override
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ this.mErrorHandler = errorHandler;
+ }
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ if (mErrorHandler != null) {
+ mErrorHandler.onConnectionError(e);
+ }
+ }
+
+ /**
+ * @see Closeable#close()
+ */
+ @Override
+ public void close() {
+ mMessageReceiver.close();
+ }
+
+ /**
+ * @see Interface.Proxy.Handler#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ @SuppressWarnings("unchecked")
+ HandleOwner<MessagePipeHandle> handleOwner =
+ (HandleOwner<MessagePipeHandle>) mMessageReceiver;
+ return handleOwner.passHandle();
+ }
+
+ /**
+ * @see Handler#getVersion()
+ */
+ @Override
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * @see Handler#queryVersion(org.chromium.mojo.bindings.Callbacks.Callback1)
+ */
+ @Override
+ public void queryVersion(final Callback1<Integer> callback) {
+ RunMessageParams message = new RunMessageParams();
+ message.input = new RunInput();
+ message.input.setQueryVersion(new QueryVersion());
+
+ InterfaceControlMessagesHelper.sendRunMessage(getCore(), mMessageReceiver, message,
+ new Callback1<RunResponseMessageParams>() {
+ @Override
+ public void call(RunResponseMessageParams response) {
+ if (response.output != null
+ && response.output.which()
+ == RunOutput.Tag.QueryVersionResult) {
+ mVersion = response.output.getQueryVersionResult().version;
+ }
+ try {
+ callback.call(mVersion);
+ } catch (RuntimeException e) {
+ // TODO(lhchavez): Remove this hack. See b/28986534 for details.
+ android.util.Log.wtf("org.chromium.mojo.bindings.Interface",
+ "Uncaught runtime exception", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * @see Handler#requireVersion(int)
+ */
+ @Override
+ public void requireVersion(int version) {
+ if (mVersion >= version) {
+ return;
+ }
+ mVersion = version;
+ RunOrClosePipeMessageParams message = new RunOrClosePipeMessageParams();
+ message.input = new RunOrClosePipeInput();
+ message.input.setRequireVersion(new RequireVersion());
+ message.input.getRequireVersion().version = version;
+ InterfaceControlMessagesHelper.sendRunOrClosePipeMessage(
+ getCore(), mMessageReceiver, message);
+ }
+ }
+
+ /**
+ * The handler associated with this proxy.
+ */
+ private final HandlerImpl mHandler;
+
+ protected AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) {
+ mHandler = new HandlerImpl(core, messageReceiver);
+ }
+
+ /**
+ * @see Interface#close()
+ */
+ @Override
+ public void close() {
+ mHandler.close();
+ }
+
+ /**
+ * @see Proxy#getProxyHandler()
+ */
+ @Override
+ public HandlerImpl getProxyHandler() {
+ return mHandler;
+ }
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(org.chromium.mojo.system.MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ mHandler.onConnectionError(e);
+ }
+ }
+
+ /**
+ * Base implementation of Stub. Stubs are message receivers that deserialize the payload and
+ * call the appropriate method in the implementation. If the method returns result, the stub
+ * serializes the response and sends it back.
+ *
+ * @param <I> the type of the interface to delegate calls to.
+ */
+ abstract class Stub<I extends Interface> implements MessageReceiverWithResponder {
+
+ /**
+ * The {@link Core} implementation to use.
+ */
+ private final Core mCore;
+
+ /**
+ * The implementation to delegate calls to.
+ */
+ private final I mImpl;
+
+ /**
+ * Constructor.
+ *
+ * @param core the {@link Core} implementation to use.
+ * @param impl the implementation to delegate calls to.
+ */
+ public Stub(Core core, I impl) {
+ mCore = core;
+ mImpl = impl;
+ }
+
+ /**
+ * Returns the Core implementation.
+ */
+ protected Core getCore() {
+ return mCore;
+ }
+
+ /**
+ * Returns the implementation to delegate calls to.
+ */
+ protected I getImpl() {
+ return mImpl;
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.MessageReceiver#close()
+ */
+ @Override
+ public void close() {
+ mImpl.close();
+ }
+
+ }
+
+ /**
+ * The |Manager| object enables building of proxies and stubs for a given interface.
+ *
+ * @param <I> the type of the interface the manager can handle.
+ * @param <P> the type of the proxy the manager can handle. To be noted, P always extends I.
+ */
+ abstract class Manager<I extends Interface, P extends Proxy> {
+
+ /**
+ * Returns the name of the interface. This is an opaque (but human readable) identifier used
+ * by the service provider to identify services.
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the version of the managed interface.
+ */
+ public abstract int getVersion();
+
+ /**
+ * Binds the given implementation to the handle.
+ */
+ public void bind(I impl, MessagePipeHandle handle) {
+ // The router (and by consequence the handle) is intentionally leaked. It will close
+ // itself when the connected handle is closed and the proxy receives the connection
+ // error.
+ Router router = new RouterImpl(handle);
+ bind(handle.getCore(), impl, router);
+ router.start();
+ }
+
+ /**
+ * Binds the given implementation to the InterfaceRequest.
+ */
+ public final void bind(I impl, InterfaceRequest<I> request) {
+ bind(impl, request.passHandle());
+ }
+
+ /**
+ * Returns a Proxy that will send messages to the given |handle|. This implies that the
+ * other end of the handle must be bound to an implementation of the interface.
+ */
+ public final P attachProxy(MessagePipeHandle handle, int version) {
+ RouterImpl router = new RouterImpl(handle);
+ P proxy = attachProxy(handle.getCore(), router);
+ DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
+ handlers.addConnectionErrorHandler(proxy);
+ router.setErrorHandler(handlers);
+ router.start();
+ ((HandlerImpl) proxy.getProxyHandler()).setVersion(version);
+ return proxy;
+ }
+
+ /**
+ * Constructs a new |InterfaceRequest| for the interface. This method returns a Pair where
+ * the first element is a proxy, and the second element is the request. The proxy can be
+ * used immediately.
+ */
+ public final Pair<P, InterfaceRequest<I>> getInterfaceRequest(Core core) {
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ P proxy = attachProxy(handles.first, 0);
+ return Pair.create(proxy, new InterfaceRequest<I>(handles.second));
+ }
+
+ public final InterfaceRequest<I> asInterfaceRequest(MessagePipeHandle handle) {
+ return new InterfaceRequest<I>(handle);
+ }
+
+ /**
+ * Binds the implementation to the given |router|.
+ */
+ final void bind(Core core, I impl, Router router) {
+ router.setErrorHandler(impl);
+ router.setIncomingMessageReceiver(buildStub(core, impl));
+ }
+
+ /**
+ * Returns a Proxy that will send messages to the given |router|.
+ */
+ final P attachProxy(Core core, Router router) {
+ return buildProxy(core, new AutoCloseableRouter(core, router));
+ }
+
+ /**
+ * Creates a new array of the given |size|.
+ */
+ protected abstract I[] buildArray(int size);
+
+ /**
+ * Constructs a Stub delegating to the given implementation.
+ */
+ protected abstract Stub<I> buildStub(Core core, I impl);
+
+ /**
+ * Constructs a Proxy forwarding the calls to the given message receiver.
+ */
+ protected abstract P buildProxy(Core core, MessageReceiverWithResponder messageReceiver);
+
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java
new file mode 100644
index 0000000000..51f543d567
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Callbacks.Callback1;
+import org.chromium.mojo.bindings.Interface.Manager;
+import org.chromium.mojo.bindings.Interface.Proxy;
+import org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants;
+import org.chromium.mojo.bindings.interfacecontrol.QueryVersionResult;
+import org.chromium.mojo.bindings.interfacecontrol.RunInput;
+import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams;
+import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput;
+import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams;
+import org.chromium.mojo.bindings.interfacecontrol.RunOutput;
+import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams;
+import org.chromium.mojo.system.Core;
+
+/**
+ * Helper class to handle interface control messages. See
+ * mojo/public/interfaces/bindings/interface_control_messages.mojom.
+ */
+public class InterfaceControlMessagesHelper {
+ /**
+ * MessageReceiver that forwards a message containing a {@link RunResponseMessageParams} to a
+ * callback.
+ */
+ private static class RunResponseForwardToCallback
+ extends SideEffectFreeCloseable implements MessageReceiver {
+ private final Callback1<RunResponseMessageParams> mCallback;
+
+ RunResponseForwardToCallback(Callback1<RunResponseMessageParams> callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ RunResponseMessageParams response =
+ RunResponseMessageParams.deserialize(message.asServiceMessage().getPayload());
+ mCallback.call(response);
+ return true;
+ }
+ }
+
+ /**
+ * Sends the given run message through the receiver, registering the callback.
+ */
+ public static void sendRunMessage(Core core, MessageReceiverWithResponder receiver,
+ RunMessageParams params, Callback1<RunResponseMessageParams> callback) {
+ Message message = params.serializeWithHeader(
+ core, new MessageHeader(InterfaceControlMessagesConstants.RUN_MESSAGE_ID,
+ MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0));
+ receiver.acceptWithResponder(message, new RunResponseForwardToCallback(callback));
+ }
+
+ /**
+ * Sends the given run or close pipe message through the receiver.
+ */
+ public static void sendRunOrClosePipeMessage(
+ Core core, MessageReceiverWithResponder receiver, RunOrClosePipeMessageParams params) {
+ Message message = params.serializeWithHeader(core,
+ new MessageHeader(InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID));
+ receiver.accept(message);
+ }
+
+ /**
+ * Handles a received run message.
+ */
+ public static <I extends Interface, P extends Proxy> boolean handleRun(
+ Core core, Manager<I, P> manager, ServiceMessage message, MessageReceiver responder) {
+ Message payload = message.getPayload();
+ RunMessageParams query = RunMessageParams.deserialize(payload);
+ RunResponseMessageParams response = new RunResponseMessageParams();
+ response.output = new RunOutput();
+ if (query.input.which() == RunInput.Tag.QueryVersion) {
+ response.output.setQueryVersionResult(new QueryVersionResult());
+ response.output.getQueryVersionResult().version = manager.getVersion();
+ } else {
+ response.output = null;
+ }
+
+ return responder.accept(response.serializeWithHeader(
+ core, new MessageHeader(InterfaceControlMessagesConstants.RUN_MESSAGE_ID,
+ MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+ message.getHeader().getRequestId())));
+ }
+
+ /**
+ * Handles a received run or close pipe message. Closing the pipe is handled by returning
+ * |false|.
+ */
+ public static <I extends Interface, P extends Proxy> boolean handleRunOrClosePipe(
+ Manager<I, P> manager, ServiceMessage message) {
+ Message payload = message.getPayload();
+ RunOrClosePipeMessageParams query = RunOrClosePipeMessageParams.deserialize(payload);
+ if (query.input.which() == RunOrClosePipeInput.Tag.RequireVersion) {
+ return query.input.getRequireVersion().version <= manager.getVersion();
+ }
+ return false;
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java
new file mode 100644
index 0000000000..61899b4467
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MessagePipeHandle;
+
+/**
+ * One end of the message pipe representing a request to create an implementation to be bound to it.
+ * The other end of the pipe is bound to a proxy, which can be used immediately, while the
+ * InterfaceRequest is being sent.
+ * <p>
+ * InterfaceRequest are built using |Interface.Manager|.
+ *
+ * @param <P> the type of the remote interface proxy.
+ */
+public class InterfaceRequest<P extends Interface> implements HandleOwner<MessagePipeHandle> {
+
+ /**
+ * The handle which will be sent and will be connected to the implementation.
+ */
+ private final MessagePipeHandle mHandle;
+
+ /**
+ * Constructor.
+ *
+ * @param handle the handle which will be sent and will be connected to the implementation.
+ */
+ InterfaceRequest(MessagePipeHandle handle) {
+ mHandle = handle;
+ }
+
+ /**
+ * @see HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ return mHandle.pass();
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ mHandle.close();
+ }
+
+ /**
+ * Returns an {@link InterfaceRequest} that wraps the given handle. This method is not type safe
+ * and should be avoided unless absolutely necessary.
+ */
+ @SuppressWarnings("rawtypes")
+ public static InterfaceRequest asInterfaceRequestUnsafe(MessagePipeHandle handle) {
+ return new InterfaceRequest(handle);
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
new file mode 100644
index 0000000000..996c457338
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A raw message to be sent/received from a {@link MessagePipeHandle}. Note that this can contain
+ * any data, not necessarily a Mojo message with a proper header. See also {@link ServiceMessage}.
+ */
+public class Message {
+
+ /**
+ * The data of the message.
+ */
+ private final ByteBuffer mBuffer;
+
+ /**
+ * The handles of the message.
+ */
+ private final List<? extends Handle> mHandle;
+
+ /**
+ * This message interpreted as a message for a mojo service with an appropriate header.
+ */
+ private ServiceMessage mWithHeader;
+
+ /**
+ * Constructor.
+ *
+ * @param buffer The buffer containing the bytes to send. This must be a direct buffer.
+ * @param handles The list of handles to send.
+ */
+ public Message(ByteBuffer buffer, List<? extends Handle> handles) {
+ assert buffer.isDirect();
+ mBuffer = buffer;
+ mHandle = handles;
+ }
+
+ /**
+ * The data of the message.
+ */
+ public ByteBuffer getData() {
+ return mBuffer;
+ }
+
+ /**
+ * The handles of the message.
+ */
+ public List<? extends Handle> getHandles() {
+ return mHandle;
+ }
+
+ /**
+ * Returns the message interpreted as a message for a mojo service.
+ */
+ public ServiceMessage asServiceMessage() {
+ if (mWithHeader == null) {
+ mWithHeader = new ServiceMessage(this);
+ }
+ return mWithHeader;
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java
new file mode 100644
index 0000000000..771d22b5c3
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java
@@ -0,0 +1,248 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Header information for a message.
+ */
+public class MessageHeader {
+
+ private static final int SIMPLE_MESSAGE_SIZE = 24;
+ private static final int SIMPLE_MESSAGE_VERSION = 0;
+ private static final DataHeader SIMPLE_MESSAGE_STRUCT_INFO =
+ new DataHeader(SIMPLE_MESSAGE_SIZE, SIMPLE_MESSAGE_VERSION);
+
+ private static final int MESSAGE_WITH_REQUEST_ID_SIZE = 32;
+ private static final int MESSAGE_WITH_REQUEST_ID_VERSION = 1;
+ private static final DataHeader MESSAGE_WITH_REQUEST_ID_STRUCT_INFO =
+ new DataHeader(MESSAGE_WITH_REQUEST_ID_SIZE, MESSAGE_WITH_REQUEST_ID_VERSION);
+
+ private static final int INTERFACE_ID_OFFSET = 8;
+ private static final int TYPE_OFFSET = 12;
+ private static final int FLAGS_OFFSET = 16;
+ private static final int REQUEST_ID_OFFSET = 24;
+
+ /**
+ * Flag for a header of a simple message.
+ */
+ public static final int NO_FLAG = 0;
+
+ /**
+ * Flag for a header of a message that expected a response.
+ */
+ public static final int MESSAGE_EXPECTS_RESPONSE_FLAG = 1 << 0;
+
+ /**
+ * Flag for a header of a message that is a response.
+ */
+ public static final int MESSAGE_IS_RESPONSE_FLAG = 1 << 1;
+
+ private final DataHeader mDataHeader;
+ private final int mType;
+ private final int mFlags;
+ private long mRequestId;
+
+ /**
+ * Constructor for the header of a message which does not have a response.
+ */
+ public MessageHeader(int type) {
+ mDataHeader = SIMPLE_MESSAGE_STRUCT_INFO;
+ mType = type;
+ mFlags = 0;
+ mRequestId = 0;
+ }
+
+ /**
+ * Constructor for the header of a message which have a response or being itself a response.
+ */
+ public MessageHeader(int type, int flags, long requestId) {
+ assert mustHaveRequestId(flags);
+ mDataHeader = MESSAGE_WITH_REQUEST_ID_STRUCT_INFO;
+ mType = type;
+ mFlags = flags;
+ mRequestId = requestId;
+ }
+
+ /**
+ * Constructor, parsing the header from a message. Should only be used by {@link Message}
+ * itself.
+ */
+ MessageHeader(Message message) {
+ Decoder decoder = new Decoder(message);
+ mDataHeader = decoder.readDataHeader();
+ validateDataHeader(mDataHeader);
+
+ // Currently associated interfaces are not supported.
+ int interfaceId = decoder.readInt(INTERFACE_ID_OFFSET);
+ if (interfaceId != 0) {
+ throw new DeserializationException("Non-zero interface ID, expecting zero since "
+ + "associated interfaces are not yet supported.");
+ }
+
+ mType = decoder.readInt(TYPE_OFFSET);
+ mFlags = decoder.readInt(FLAGS_OFFSET);
+ if (mustHaveRequestId(mFlags)) {
+ if (mDataHeader.size < MESSAGE_WITH_REQUEST_ID_SIZE) {
+ throw new DeserializationException("Incorrect message size, expecting at least "
+ + MESSAGE_WITH_REQUEST_ID_SIZE
+ + " for a message with a request identifier, but got: " + mDataHeader.size);
+
+ }
+ mRequestId = decoder.readLong(REQUEST_ID_OFFSET);
+ } else {
+ mRequestId = 0;
+ }
+ }
+
+ /**
+ * Returns the size in bytes of the serialization of the header.
+ */
+ public int getSize() {
+ return mDataHeader.size;
+ }
+
+ /**
+ * Returns the type of the message.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the flags associated to the message.
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Returns if the message has the given flag.
+ */
+ public boolean hasFlag(int flag) {
+ return (mFlags & flag) == flag;
+ }
+
+ /**
+ * Returns if the message has a request id.
+ */
+ public boolean hasRequestId() {
+ return mustHaveRequestId(mFlags);
+ }
+
+ /**
+ * Return the request id for the message. Must only be called if the message has a request id.
+ */
+ public long getRequestId() {
+ assert hasRequestId();
+ return mRequestId;
+ }
+
+ /**
+ * Encode the header.
+ */
+ public void encode(Encoder encoder) {
+ encoder.encode(mDataHeader);
+ // Set the interface ID field to 0.
+ encoder.encode(0, INTERFACE_ID_OFFSET);
+ encoder.encode(getType(), TYPE_OFFSET);
+ encoder.encode(getFlags(), FLAGS_OFFSET);
+ if (hasRequestId()) {
+ encoder.encode(getRequestId(), REQUEST_ID_OFFSET);
+ }
+ }
+
+ /**
+ * Returns true if the header has the expected flags. Only considers flags this class knows
+ * about in order to allow this class to work with future version of the header format.
+ */
+ public boolean validateHeader(int expectedFlags) {
+ int knownFlags = getFlags() & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG);
+ return knownFlags == expectedFlags;
+ }
+
+ /**
+ * Returns true if the header has the expected type and flags. Only consider flags this class
+ * knows about in order to allow this class to work with future version of the header format.
+ */
+ public boolean validateHeader(int expectedType, int expectedFlags) {
+ return getType() == expectedType && validateHeader(expectedFlags);
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mDataHeader == null) ? 0 : mDataHeader.hashCode());
+ result = prime * result + mFlags;
+ result = prime * result + (int) (mRequestId ^ (mRequestId >>> 32));
+ result = prime * result + mType;
+ return result;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) return true;
+ if (object == null) return false;
+ if (getClass() != object.getClass()) return false;
+
+ MessageHeader other = (MessageHeader) object;
+ return (BindingsHelper.equals(mDataHeader, other.mDataHeader)
+ && mFlags == other.mFlags
+ && mRequestId == other.mRequestId
+ && mType == other.mType);
+ }
+
+ /**
+ * Set the request id on the message contained in the given buffer.
+ */
+ void setRequestId(ByteBuffer buffer, long requestId) {
+ assert mustHaveRequestId(buffer.getInt(FLAGS_OFFSET));
+ buffer.putLong(REQUEST_ID_OFFSET, requestId);
+ mRequestId = requestId;
+ }
+
+ /**
+ * Returns whether a message with the given flags must have a request Id.
+ */
+ private static boolean mustHaveRequestId(int flags) {
+ return (flags & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG)) != 0;
+ }
+
+ /**
+ * Validate that the given {@link DataHeader} can be the data header of a message header.
+ */
+ private static void validateDataHeader(DataHeader dataHeader) {
+ if (dataHeader.elementsOrVersion < SIMPLE_MESSAGE_VERSION) {
+ throw new DeserializationException("Incorrect number of fields, expecting at least "
+ + SIMPLE_MESSAGE_VERSION + ", but got: " + dataHeader.elementsOrVersion);
+ }
+ if (dataHeader.size < SIMPLE_MESSAGE_SIZE) {
+ throw new DeserializationException(
+ "Incorrect message size, expecting at least " + SIMPLE_MESSAGE_SIZE
+ + ", but got: " + dataHeader.size);
+ }
+ if (dataHeader.elementsOrVersion == SIMPLE_MESSAGE_VERSION
+ && dataHeader.size != SIMPLE_MESSAGE_SIZE) {
+ throw new DeserializationException("Incorrect message size for a message with "
+ + SIMPLE_MESSAGE_VERSION + " fields, expecting " + SIMPLE_MESSAGE_SIZE
+ + ", but got: " + dataHeader.size);
+ }
+ if (dataHeader.elementsOrVersion == MESSAGE_WITH_REQUEST_ID_VERSION
+ && dataHeader.size != MESSAGE_WITH_REQUEST_ID_SIZE) {
+ throw new DeserializationException("Incorrect message size for a message with "
+ + MESSAGE_WITH_REQUEST_ID_VERSION + " fields, expecting "
+ + MESSAGE_WITH_REQUEST_ID_SIZE + ", but got: " + dataHeader.size);
+ }
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java
new file mode 100644
index 0000000000..4223297914
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import java.io.Closeable;
+
+/**
+ * A class which implements this interface can receive {@link Message} objects.
+ */
+public interface MessageReceiver extends Closeable {
+
+ /**
+ * Receive a {@link Message}. The {@link MessageReceiver} is allowed to mutate the message.
+ * Returns |true| if the message has been handled, |false| otherwise.
+ */
+ boolean accept(Message message);
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close();
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java
new file mode 100644
index 0000000000..e24a5586c6
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * A {@link MessageReceiver} that can also handle the handle the response message generated from the
+ * given message.
+ */
+public interface MessageReceiverWithResponder extends MessageReceiver {
+
+ /**
+ * A variant on {@link #accept(Message)} that registers a {@link MessageReceiver}
+ * (known as the responder) to handle the response message generated from the given message. The
+ * responder's {@link #accept(Message)} method may be called as part of the call to
+ * {@link #acceptWithResponder(Message, MessageReceiver)}, or some time after its
+ * return.
+ */
+ boolean acceptWithResponder(Message message, MessageReceiver responder);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java
new file mode 100644
index 0000000000..ba19ae5ac4
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MessagePipeHandle;
+
+/**
+ * A {@link Router} will handle mojo message and forward those to a {@link Connector}. It deals with
+ * parsing of headers and adding of request ids in order to be able to match a response to a
+ * request.
+ */
+public interface Router extends MessageReceiverWithResponder, HandleOwner<MessagePipeHandle> {
+
+ /**
+ * Start listening for incoming messages.
+ */
+ public void start();
+
+ /**
+ * Set the {@link MessageReceiverWithResponder} that will deserialize and use the message
+ * received from the pipe.
+ */
+ public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver);
+
+ /**
+ * Set the handle that will be notified of errors on the message pipe.
+ */
+ public void setErrorHandler(ConnectionErrorHandler errorHandler);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java
new file mode 100644
index 0000000000..aebc9e21c8
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java
@@ -0,0 +1,274 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import android.annotation.SuppressLint;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.Watcher;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of {@link Router}.
+ */
+@SuppressLint("UseSparseArrays") // https://crbug.com/600699
+public class RouterImpl implements Router {
+
+ /**
+ * {@link MessageReceiver} used as the {@link Connector} callback.
+ */
+ private class HandleIncomingMessageThunk implements MessageReceiver {
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ return handleIncomingMessage(message);
+ }
+
+ /**
+ * @see MessageReceiver#close()
+ */
+ @Override
+ public void close() {
+ handleConnectorClose();
+ }
+
+ }
+
+ /**
+ *
+ * {@link MessageReceiver} used to return responses to the caller.
+ */
+ class ResponderThunk implements MessageReceiver {
+ private boolean mAcceptWasInvoked;
+
+ /**
+ * @see
+ * MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ mAcceptWasInvoked = true;
+ return RouterImpl.this.accept(message);
+ }
+
+ /**
+ * @see MessageReceiver#close()
+ */
+ @Override
+ public void close() {
+ RouterImpl.this.close();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (!mAcceptWasInvoked) {
+ // We close the pipe here as a way of signaling to the calling application that an
+ // error condition occurred. Without this the calling application would have no
+ // way of knowing it should stop waiting for a response.
+ RouterImpl.this.closeOnHandleThread();
+ }
+ super.finalize();
+ }
+ }
+
+ /**
+ * The {@link Connector} which is connected to the handle.
+ */
+ private final Connector mConnector;
+
+ /**
+ * The {@link MessageReceiverWithResponder} that will consume the messages received from the
+ * pipe.
+ */
+ private MessageReceiverWithResponder mIncomingMessageReceiver;
+
+ /**
+ * The next id to use for a request id which needs a response. It is auto-incremented.
+ */
+ private long mNextRequestId = 1;
+
+ /**
+ * The map from request ids to {@link MessageReceiver} of request currently in flight.
+ */
+ private Map<Long, MessageReceiver> mResponders = new HashMap<Long, MessageReceiver>();
+
+ /**
+ * An Executor that will run on the thread associated with the MessagePipe to which
+ * this Router is bound. This may be {@code Null} if the MessagePipeHandle passed
+ * in to the constructor is not valid.
+ */
+ private final Executor mExecutor;
+
+ /**
+ * Constructor that will use the default {@link Watcher}.
+ *
+ * @param messagePipeHandle The {@link MessagePipeHandle} to route message for.
+ */
+ public RouterImpl(MessagePipeHandle messagePipeHandle) {
+ this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param messagePipeHandle The {@link MessagePipeHandle} to route message for.
+ * @param watcher the {@link Watcher} to use to get notification of new messages on the
+ * handle.
+ */
+ public RouterImpl(MessagePipeHandle messagePipeHandle, Watcher watcher) {
+ mConnector = new Connector(messagePipeHandle, watcher);
+ mConnector.setIncomingMessageReceiver(new HandleIncomingMessageThunk());
+ Core core = messagePipeHandle.getCore();
+ if (core != null) {
+ mExecutor = ExecutorFactory.getExecutorForCurrentThread(core);
+ } else {
+ mExecutor = null;
+ }
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.Router#start()
+ */
+ @Override
+ public void start() {
+ mConnector.start();
+ }
+
+ /**
+ * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder)
+ */
+ @Override
+ public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) {
+ this.mIncomingMessageReceiver = incomingMessageReceiver;
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ // A message without responder is directly forwarded to the connector.
+ return mConnector.accept(message);
+ }
+
+ /**
+ * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver)
+ */
+ @Override
+ public boolean acceptWithResponder(Message message, MessageReceiver responder) {
+ // The message must have a header.
+ ServiceMessage messageWithHeader = message.asServiceMessage();
+ // Checking the message expects a response.
+ assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG);
+
+ // Compute a request id for being able to route the response.
+ // TODO(lhchavez): Remove this hack. See b/28986534 for details.
+ synchronized (mResponders) {
+ long requestId = mNextRequestId++;
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ if (requestId == 0) {
+ requestId = mNextRequestId++;
+ }
+ if (mResponders.containsKey(requestId)) {
+ throw new IllegalStateException("Unable to find a new request identifier.");
+ }
+ messageWithHeader.setRequestId(requestId);
+ if (!mConnector.accept(messageWithHeader)) {
+ return false;
+ }
+ // Only keep the responder is the message has been accepted.
+ mResponders.put(requestId, responder);
+ }
+ return true;
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ return mConnector.passHandle();
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ mConnector.close();
+ }
+
+ /**
+ * @see Router#setErrorHandler(ConnectionErrorHandler)
+ */
+ @Override
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ mConnector.setErrorHandler(errorHandler);
+ }
+
+ /**
+ * Receive a message from the connector. Returns |true| if the message has been handled.
+ */
+ private boolean handleIncomingMessage(Message message) {
+ MessageHeader header = message.asServiceMessage().getHeader();
+ if (header.hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) {
+ if (mIncomingMessageReceiver != null) {
+ return mIncomingMessageReceiver.acceptWithResponder(message, new ResponderThunk());
+ }
+ // If we receive a request expecting a response when the client is not
+ // listening, then we have no choice but to tear down the pipe.
+ close();
+ return false;
+ } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+ long requestId = header.getRequestId();
+ MessageReceiver responder;
+ // TODO(lhchavez): Remove this hack. See b/28986534 for details.
+ synchronized (mResponders) {
+ responder = mResponders.get(requestId);
+ if (responder == null) {
+ return false;
+ }
+ mResponders.remove(requestId);
+ }
+ return responder.accept(message);
+ } else {
+ if (mIncomingMessageReceiver != null) {
+ return mIncomingMessageReceiver.accept(message);
+ }
+ // OK to drop the message.
+ }
+ return false;
+ }
+
+ private void handleConnectorClose() {
+ if (mIncomingMessageReceiver != null) {
+ mIncomingMessageReceiver.close();
+ }
+ }
+
+ /**
+ * Invokes {@link #close()} asynchronously on the thread associated with
+ * this Router's Handle. If this Router was constructed with an invalid
+ * handle then this method does nothing.
+ */
+ private void closeOnHandleThread() {
+ if (mExecutor != null) {
+ mExecutor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ close();
+ }
+ });
+ }
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java
new file mode 100644
index 0000000000..d4f550227f
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Error that can be thrown when serializing a mojo message.
+ */
+public class SerializationException extends RuntimeException {
+
+ /**
+ * Constructs a new serialization exception with the specified detail message.
+ */
+ public SerializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new serialization exception with the specified cause.
+ */
+ public SerializationException(Exception cause) {
+ super(cause);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java
new file mode 100644
index 0000000000..313dc6aea3
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Represents a {@link Message} which contains a {@link MessageHeader}. Deals with parsing the
+ * {@link MessageHeader} for a message.
+ */
+public class ServiceMessage extends Message {
+
+ private final MessageHeader mHeader;
+ private Message mPayload;
+
+ /**
+ * Reinterpret the given |message| as a message with the given |header|. The |message| must
+ * contain the |header| as the start of its raw data.
+ */
+ public ServiceMessage(Message baseMessage, MessageHeader header) {
+ super(baseMessage.getData(), baseMessage.getHandles());
+ assert header.equals(new org.chromium.mojo.bindings.MessageHeader(baseMessage));
+ this.mHeader = header;
+ }
+
+ /**
+ * Reinterpret the given |message| as a message with a header. The |message| must contain a
+ * header as the start of it's raw data, which will be parsed by this constructor.
+ */
+ ServiceMessage(Message baseMessage) {
+ this(baseMessage, new org.chromium.mojo.bindings.MessageHeader(baseMessage));
+ }
+
+ /**
+ * @see Message#asServiceMessage()
+ */
+ @Override
+ public ServiceMessage asServiceMessage() {
+ return this;
+ }
+
+ /**
+ * Returns the header of the given message. This will throw a {@link DeserializationException}
+ * if the start of the message is not a valid header.
+ */
+ public MessageHeader getHeader() {
+ return mHeader;
+ }
+
+ /**
+ * Returns the payload of the message.
+ */
+ public Message getPayload() {
+ if (mPayload == null) {
+ ByteBuffer truncatedBuffer =
+ ((ByteBuffer) getData().position(getHeader().getSize())).slice();
+ truncatedBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ mPayload = new Message(truncatedBuffer, getHandles());
+ }
+ return mPayload;
+ }
+
+ /**
+ * Set the request identifier on the message.
+ */
+ void setRequestId(long requestId) {
+ mHeader.setRequestId(getData(), requestId);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java
new file mode 100644
index 0000000000..118c991f7b
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import java.io.Closeable;
+
+/**
+ * An implementation of closeable that doesn't do anything.
+ */
+public class SideEffectFreeCloseable implements Closeable {
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java
new file mode 100644
index 0000000000..14f4e1e726
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Base class for all mojo structs.
+ */
+public abstract class Struct {
+ /**
+ * The base size of the encoded struct.
+ */
+ private final int mEncodedBaseSize;
+
+ /**
+ * The version of the struct.
+ */
+ private final int mVersion;
+
+ /**
+ * Constructor.
+ */
+ protected Struct(int encodedBaseSize, int version) {
+ mEncodedBaseSize = encodedBaseSize;
+ mVersion = version;
+ }
+
+ /**
+ * Returns the version of the struct. It is the max version of the struct in the mojom if it has
+ * been created locally, and the version of the received struct if it has been deserialized.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns the serialization of the struct. This method can close Handles.
+ *
+ * @param core the |Core| implementation used to generate handles. Only used if the data
+ * structure being encoded contains interfaces, can be |null| otherwise.
+ */
+ public Message serialize(Core core) {
+ Encoder encoder = new Encoder(core, mEncodedBaseSize);
+ encode(encoder);
+ return encoder.getMessage();
+ }
+
+ /**
+ * Similar to the method above, but returns the serialization result as |ByteBuffer|.
+ *
+ * @throws UnsupportedOperationException if the struct contains interfaces or handles.
+ * @throws SerializationException on serialization failure.
+ */
+ public ByteBuffer serialize() {
+ // If the struct contains interfaces which require a non-null |Core| instance, it will throw
+ // UnsupportedOperationException.
+ Message message = serialize(null);
+
+ if (!message.getHandles().isEmpty())
+ throw new UnsupportedOperationException("Handles are discarded.");
+
+ return message.getData();
+ }
+
+ /**
+ * Returns the serialization of the struct prepended with the given header.
+ *
+ * @param header the header to prepend to the returned message.
+ * @param core the |Core| implementation used to generate handles. Only used if the |Struct|
+ * being encoded contains interfaces, can be |null| otherwise.
+ */
+ public ServiceMessage serializeWithHeader(Core core, MessageHeader header) {
+ Encoder encoder = new Encoder(core, mEncodedBaseSize + header.getSize());
+ header.encode(encoder);
+ encode(encoder);
+ return new ServiceMessage(encoder.getMessage(), header);
+ }
+
+ /**
+ * Use the given encoder to serialize this data structure.
+ */
+ protected abstract void encode(Encoder encoder);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java
new file mode 100644
index 0000000000..90b40ea57b
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+
+/**
+ * Base class for all mojo unions.
+ */
+public abstract class Union {
+ /**
+ * Returns the serialization of the union. This method can close Handles.
+ *
+ * @param core the |Core| implementation used to generate handles. Only used if the data
+ * structure being encoded contains interfaces, can be |null| otherwise.
+ */
+ public Message serialize(Core core) {
+ Encoder encoder = new Encoder(core, BindingsHelper.UNION_SIZE);
+ encoder.claimMemory(16);
+ encode(encoder, 0);
+ return encoder.getMessage();
+ }
+
+ /**
+ * Serializes this data structure using the given encoder.
+ */
+ protected abstract void encode(Encoder encoder, int offset);
+}
diff --git a/mojo/public/java/system/README.md b/mojo/public/java/system/README.md
new file mode 100644
index 0000000000..3213e4c790
--- /dev/null
+++ b/mojo/public/java/system/README.md
@@ -0,0 +1,25 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java System API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+
+This document provides a brief guide to Java Mojo bindings usage with example
+code snippets.
+
+For a detailed API references please consult the class definitions in
+[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/system/src/org/chromium/mojo/system/).
+
+*TODO: Make the contents of this document less non-existent.*
+
+## Message Pipes
+
+## Data Pipes
+
+## Shared Buffers
+
+## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
+
+## Watchers
+
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
new file mode 100644
index 0000000000..40e4be365d
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
@@ -0,0 +1,183 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.system;
+
+/**
+ * Core mojo interface giving access to the base operations. See |src/mojo/public/c/system/core.h|
+ * for the underlying api.
+ */
+public interface Core {
+
+ /**
+ * Used to indicate an infinite deadline (timeout).
+ */
+ public static final long DEADLINE_INFINITE = -1;
+
+ /**
+ * Signals for the wait operations on handles.
+ */
+ public static class HandleSignals extends Flags<HandleSignals> {
+ /**
+ * Constructor.
+ *
+ * @param signals the serialized signals.
+ */
+ public HandleSignals(int signals) {
+ super(signals);
+ }
+
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_READABLE = 1 << 0;
+ private static final int FLAG_WRITABLE = 1 << 1;
+ private static final int FLAG_PEER_CLOSED = 1 << 2;
+
+ /**
+ * Immutable signals.
+ */
+ public static final HandleSignals NONE = HandleSignals.none().immutable();
+ public static final HandleSignals READABLE =
+ HandleSignals.none().setReadable(true).immutable();
+ public static final HandleSignals WRITABLE =
+ HandleSignals.none().setWritable(true).immutable();
+
+ /**
+ * Change the readable bit of this signal.
+ *
+ * @param readable the new value of the readable bit.
+ * @return this.
+ */
+ public HandleSignals setReadable(boolean readable) {
+ return setFlag(FLAG_READABLE, readable);
+ }
+
+ /**
+ * Change the writable bit of this signal.
+ *
+ * @param writable the new value of the writable bit.
+ * @return this.
+ */
+ public HandleSignals setWritable(boolean writable) {
+ return setFlag(FLAG_WRITABLE, writable);
+ }
+
+ /**
+ * Change the peer closed bit of this signal.
+ *
+ * @param peerClosed the new value of the peer closed bit.
+ * @return this.
+ */
+ public HandleSignals setPeerClosed(boolean peerClosed) {
+ return setFlag(FLAG_PEER_CLOSED, peerClosed);
+ }
+
+ /**
+ * Returns a signal with no bit set.
+ */
+ public static HandleSignals none() {
+ return new HandleSignals(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Returns a platform-dependent monotonically increasing tick count representing "right now."
+ */
+ public long getTimeTicksNow();
+
+ /**
+ * Returned by wait functions to indicate the signaling state of handles.
+ */
+ public static class HandleSignalsState {
+ /**
+ * Signals that were satisfied at some time // before the call returned.
+ */
+ private final HandleSignals mSatisfiedSignals;
+
+ /**
+ * Signals that are possible to satisfy. For example, if the return value was
+ * |MOJO_RESULT_FAILED_PRECONDITION|, you can use this field to determine which, if any, of
+ * the signals can still be satisfied.
+ */
+ private final HandleSignals mSatisfiableSignals;
+
+ /**
+ * Constructor.
+ */
+ public HandleSignalsState(
+ HandleSignals satisfiedSignals, HandleSignals satisfiableSignals) {
+ mSatisfiedSignals = satisfiedSignals;
+ mSatisfiableSignals = satisfiableSignals;
+ }
+
+ /**
+ * Returns the satisfiedSignals.
+ */
+ public HandleSignals getSatisfiedSignals() {
+ return mSatisfiedSignals;
+ }
+
+ /**
+ * Returns the satisfiableSignals.
+ */
+ public HandleSignals getSatisfiableSignals() {
+ return mSatisfiableSignals;
+ }
+ }
+
+ /**
+ * Creates a message pipe, which is a bidirectional communication channel for framed data (i.e.,
+ * messages), with the given options. Messages can contain plain data and/or Mojo handles.
+ *
+ * @return the set of handles for the two endpoints (ports) of the message pipe.
+ */
+ public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe(
+ MessagePipeHandle.CreateOptions options);
+
+ /**
+ * Creates a data pipe, which is a unidirectional communication channel for unframed data, with
+ * the given options. Data is unframed, but must come as (multiples of) discrete elements, of
+ * the size given in |options|. See |DataPipe.CreateOptions| for a description of the different
+ * options available for data pipes. |options| may be set to null for a data pipe with the
+ * default options (which will have an element size of one byte and have some system-dependent
+ * capacity).
+ *
+ * @return the set of handles for the two endpoints of the data pipe.
+ */
+ public Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> createDataPipe(
+ DataPipe.CreateOptions options);
+
+ /**
+ * Creates a buffer that can be shared between applications (by duplicating the handle -- see
+ * |SharedBufferHandle.duplicate()| -- and passing it over a message pipe). To access the
+ * buffer, one must call |SharedBufferHandle.map|.
+ *
+ * @return the new |SharedBufferHandle|.
+ */
+ public SharedBufferHandle createSharedBuffer(SharedBufferHandle.CreateOptions options,
+ long numBytes);
+
+ /**
+ * Acquires a handle from the native side. The handle will be owned by the returned object and
+ * must not be closed outside of it.
+ *
+ * @return a new {@link UntypedHandle} representing the native handle.
+ */
+ public UntypedHandle acquireNativeHandle(int handle);
+
+ /**
+ * Returns an implementation of {@link Watcher}.
+ */
+ public Watcher getWatcher();
+
+ /**
+ * Returns a new run loop.
+ */
+ public RunLoop createDefaultRunLoop();
+
+ /**
+ * Returns the current run loop if it exists.
+ */
+ public RunLoop getCurrentRunLoop();
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java
new file mode 100644
index 0000000000..4deaf0975d
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java
@@ -0,0 +1,334 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.system;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Interface for data pipes. A data pipe is a unidirectional communication channel for unframed
+ * data. Data is unframed, but must come as (multiples of) discrete elements, of the size given at
+ * creation time.
+ */
+public interface DataPipe {
+
+ /**
+ * Flags for the data pipe creation operation.
+ */
+ public static class CreateFlags extends Flags<CreateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final CreateFlags NONE = CreateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected CreateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static CreateFlags none() {
+ return new CreateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify creation parameters for a data pipe to |Core.createDataPipe()|.
+ */
+ public static class CreateOptions {
+
+ /**
+ * Used to specify different modes of operation, see |DataPipe.CreateFlags|.
+ */
+ private CreateFlags mFlags = CreateFlags.none();
+ /**
+ * The size of an element, in bytes. All transactions and buffers will consist of an
+ * integral number of elements. Must be nonzero.
+ */
+ private int mElementNumBytes;
+ /**
+ * The capacity of the data pipe, in number of bytes; must be a multiple of
+ * |element_num_bytes|. The data pipe will always be able to queue AT LEAST this much data.
+ * Set to zero to opt for a system-dependent automatically-calculated capacity (which will
+ * always be at least one element).
+ */
+ private int mCapacityNumBytes;
+
+ /**
+ * @return the flags
+ */
+ public CreateFlags getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * @return the elementNumBytes
+ */
+ public int getElementNumBytes() {
+ return mElementNumBytes;
+ }
+
+ /**
+ * @param elementNumBytes the elementNumBytes to set
+ */
+ public void setElementNumBytes(int elementNumBytes) {
+ mElementNumBytes = elementNumBytes;
+ }
+
+ /**
+ * @return the capacityNumBytes
+ */
+ public int getCapacityNumBytes() {
+ return mCapacityNumBytes;
+ }
+
+ /**
+ * @param capacityNumBytes the capacityNumBytes to set
+ */
+ public void setCapacityNumBytes(int capacityNumBytes) {
+ mCapacityNumBytes = capacityNumBytes;
+ }
+
+ }
+
+ /**
+ * Flags for the write operations on MessagePipeHandle .
+ */
+ public static class WriteFlags extends Flags<WriteFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_ALL_OR_NONE = 1 << 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final WriteFlags NONE = WriteFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ private WriteFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the all-or-none bit of those flags. If set, write either all the elements
+ * requested or none of them.
+ *
+ * @param allOrNone the new value of all-or-none bit.
+ * @return this.
+ */
+ public WriteFlags setAllOrNone(boolean allOrNone) {
+ return setFlag(FLAG_ALL_OR_NONE, allOrNone);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static WriteFlags none() {
+ return new WriteFlags(FLAG_NONE);
+ }
+ }
+
+ /**
+ * Flags for the read operations on MessagePipeHandle.
+ */
+ public static class ReadFlags extends Flags<ReadFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_ALL_OR_NONE = 1 << 0;
+ private static final int FLAG_QUERY = 1 << 2;
+ private static final int FLAG_PEEK = 1 << 3;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final ReadFlags NONE = ReadFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ private ReadFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the all-or-none bit of this flag. If set, read (or discard) either the requested
+ * number of elements or none.
+ *
+ * @param allOrNone the new value of the all-or-none bit.
+ * @return this.
+ */
+ public ReadFlags setAllOrNone(boolean allOrNone) {
+ return setFlag(FLAG_ALL_OR_NONE, allOrNone);
+ }
+
+ /**
+ * Change the query bit of this flag. If set query the number of elements available to read.
+ * Mutually exclusive with |discard| and |allOrNone| is ignored if this flag is set.
+ *
+ * @param query the new value of the query bit.
+ * @return this.
+ */
+ public ReadFlags query(boolean query) {
+ return setFlag(FLAG_QUERY, query);
+ }
+
+ /**
+ * Change the peek bit of this flag. If set, read the requested number of elements, and
+ * leave those elements in the pipe. A later read will return the same data.
+ * Mutually exclusive with |discard| and |query|.
+ *
+ * @param peek the new value of the peek bit.
+ * @return this.
+ */
+ public ReadFlags peek(boolean peek) {
+ return setFlag(FLAG_PEEK, peek);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static ReadFlags none() {
+ return new ReadFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Handle for the producer part of a data pipe.
+ */
+ public static interface ProducerHandle extends Handle {
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public ProducerHandle pass();
+
+ /**
+ * Writes the given data to the data pipe producer. |elements| points to data; the buffer
+ * must be a direct ByteBuffer and the limit should be a multiple of the data pipe's element
+ * size. If |allOrNone| is set in |flags|, either all the data will be written or none is.
+ * <p>
+ * On success, returns the amount of data that was actually written.
+ * <p>
+ * Note: If the data pipe has the "may discard" option flag (specified on creation), this
+ * will discard as much data as required to write the given data, starting with the earliest
+ * written data that has not been consumed. However, even with "may discard", if the buffer
+ * limit is greater than the data pipe's capacity (and |allOrNone| is not set), this will
+ * write the maximum amount possible (namely, the data pipe's capacity) and return that
+ * amount. It will *not* discard data from |elements|.
+ *
+ * @return number of written bytes.
+ */
+ public ResultAnd<Integer> writeData(ByteBuffer elements, WriteFlags flags);
+
+ /**
+ * Begins a two-phase write to the data pipe producer . On success, returns a |ByteBuffer|
+ * to which the caller can write. If flags has |allOrNone| set, then the buffer capacity
+ * will be at least as large as |numBytes|, which must also be a multiple of the element
+ * size (if |allOrNone| is not set, |numBytes| is ignored and the caller must check the
+ * capacity of the buffer).
+ * <p>
+ * During a two-phase write, this handle is *not* writable. E.g., if another thread tries to
+ * write to it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread can
+ * then wait for this handle to become writable again.
+ * <p>
+ * Once the caller has finished writing data to the buffer, it should call |endWriteData()|
+ * to specify the amount written and to complete the two-phase write.
+ * <p>
+ * Note: If the data pipe has the "may discard" option flag (specified on creation) and
+ * |flags| has |allOrNone| set, this may discard some data.
+ *
+ * @return The buffer to write to.
+ */
+ public ByteBuffer beginWriteData(int numBytes, WriteFlags flags);
+
+ /**
+ * Ends a two-phase write to the data pipe producer that was begun by a call to
+ * |beginWriteData()| on the same handle. |numBytesWritten| should indicate the amount of
+ * data actually written; it must be less than or equal to the capacity of the buffer
+ * returned by |beginWriteData()| and must be a multiple of the element size. The buffer
+ * returned from |beginWriteData()| must have been filled with exactly |numBytesWritten|
+ * bytes of data.
+ * <p>
+ * On failure, the two-phase write (if any) is ended (so the handle may become writable
+ * again, if there's space available) but no data written to the buffer is "put into" the
+ * data pipe.
+ */
+ public void endWriteData(int numBytesWritten);
+ }
+
+ /**
+ * Handle for the consumer part of a data pipe.
+ */
+ public static interface ConsumerHandle extends Handle {
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public ConsumerHandle pass();
+
+ /**
+ * Discards data on the data pie consumer. This method discards up to |numBytes| (which
+ * again be a multiple of the element size) bytes of data, returning the amount actually
+ * discarded. if |flags| has |allOrNone|, it will either discard exactly |numBytes| bytes of
+ * data or none. In this case, |query| must not be set.
+ */
+ public int discardData(int numBytes, ReadFlags flags);
+
+ /**
+ * Reads data from the data pipe consumer. May also be used to query the amount of data
+ * available. If |flags| has not |query| set, this tries to read up to |elements| capacity
+ * (which must be a multiple of the data pipe's element size) bytes of data to |elements|
+ * and returns the amount actually read. |elements| must be a direct ByteBuffer. If flags
+ * has |allOrNone| set, it will either read exactly |elements| capacity bytes of data or
+ * none.
+ * <p>
+ * If flags has |query| set, it queries the amount of data available, returning the number
+ * of bytes available. In this case |allOrNone| is ignored, as are |elements|.
+ */
+ public ResultAnd<Integer> readData(ByteBuffer elements, ReadFlags flags);
+
+ /**
+ * Begins a two-phase read from the data pipe consumer. On success, returns a |ByteBuffer|
+ * from which the caller can read up to its limit bytes of data. If flags has |allOrNone|
+ * set, then the limit will be at least as large as |numBytes|, which must also be a
+ * multiple of the element size (if |allOrNone| is not set, |numBytes| is ignored). |flags|
+ * must not have |query| set.
+ * <p>
+ * During a two-phase read, this handle is *not* readable. E.g., if another thread tries to
+ * read from it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread
+ * can then wait for this handle to become readable again.
+ * <p>
+ * Once the caller has finished reading data from the buffer, it should call |endReadData()|
+ * to specify the amount read and to complete the two-phase read.
+ */
+ public ByteBuffer beginReadData(int numBytes, ReadFlags flags);
+
+ /**
+ * Ends a two-phase read from the data pipe consumer that was begun by a call to
+ * |beginReadData()| on the same handle. |numBytesRead| should indicate the amount of data
+ * actually read; it must be less than or equal to the limit of the buffer returned by
+ * |beginReadData()| and must be a multiple of the element size.
+ * <p>
+ * On failure, the two-phase read (if any) is ended (so the handle may become readable
+ * again) but no data is "removed" from the data pipe.
+ */
+ public void endReadData(int numBytesRead);
+ }
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java
new file mode 100644
index 0000000000..30ff07f710
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * Base class for bit field used as flags.
+ *
+ * @param <F> the type of the flags.
+ */
+public abstract class Flags<F extends Flags<F>> {
+ private int mFlags;
+ private boolean mImmutable;
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ protected Flags(int flags) {
+ mImmutable = false;
+ mFlags = flags;
+ }
+
+ /**
+ * @return the computed flag.
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Change the given bit of this flag.
+ *
+ * @param value the new value of given bit.
+ * @return this.
+ */
+ protected F setFlag(int flag, boolean value) {
+ if (mImmutable) {
+ throw new UnsupportedOperationException("Flags is immutable.");
+ }
+ if (value) {
+ mFlags |= flag;
+ } else {
+ mFlags &= ~flag;
+ }
+ @SuppressWarnings("unchecked")
+ F f = (F) this;
+ return f;
+ }
+
+ /**
+ * Makes this flag immutable. This is a non-reversable operation.
+ */
+ protected F immutable() {
+ mImmutable = true;
+ @SuppressWarnings("unchecked")
+ F f = (F) this;
+ return f;
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return mFlags;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ Flags<?> other = (Flags<?>) obj;
+ if (mFlags != other.mFlags) return false;
+ return true;
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
new file mode 100644
index 0000000000..903f36d677
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import org.chromium.mojo.system.Core.HandleSignalsState;
+
+import java.io.Closeable;
+
+/**
+ * A generic mojo handle.
+ */
+public interface Handle extends Closeable {
+
+ /**
+ * Closes the given |handle|.
+ * <p>
+ * Concurrent operations on |handle| may succeed (or fail as usual) if they happen before the
+ * close, be cancelled with result |MojoResult.CANCELLED| if they properly overlap (this is
+ * likely the case with |wait()|, etc.), or fail with |MojoResult.INVALID_ARGUMENT| if they
+ * happen after.
+ */
+ @Override
+ public void close();
+
+ /**
+ * @return the last known signaling state of the handle.
+ */
+ public HandleSignalsState querySignalsState();
+
+ /**
+ * @return whether the handle is valid. A handle is valid until it has been explicitly closed or
+ * send through a message pipe via |MessagePipeHandle.writeMessage|.
+ */
+ public boolean isValid();
+
+ /**
+ * Converts this handle into an {@link UntypedHandle}, invalidating this handle.
+ */
+ public UntypedHandle toUntypedHandle();
+
+ /**
+ * Returns the {@link Core} implementation for this handle. Can be null if this handle is
+ * invalid.
+ */
+ public Core getCore();
+
+ /**
+ * Passes ownership of the handle from this handle to the newly created Handle object,
+ * invalidating this handle object in the process.
+ */
+ public Handle pass();
+
+ /**
+ * Releases the native handle backed by this {@link Handle}. The caller owns the handle and must
+ * close it.
+ */
+ public int releaseNativeHandle();
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
new file mode 100644
index 0000000000..f8b99c6d66
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
@@ -0,0 +1,219 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.system;
+
+import org.chromium.mojo.system.Core.HandleSignalsState;
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A handle that will always be invalid.
+ */
+public class InvalidHandle implements UntypedHandle, MessagePipeHandle, ConsumerHandle,
+ ProducerHandle, SharedBufferHandle {
+
+ /**
+ * Instance singleton.
+ */
+ public static final InvalidHandle INSTANCE = new InvalidHandle();
+
+ /**
+ * Private constructor.
+ */
+ private InvalidHandle() {
+ }
+
+ /**
+ * @see Handle#close()
+ */
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+
+ /**
+ * @see Handle#querySignalsState()
+ */
+ @Override
+ public HandleSignalsState querySignalsState() {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see Handle#isValid()
+ */
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ /**
+ * @see Handle#getCore()
+ */
+ @Override
+ public Core getCore() {
+ return null;
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public InvalidHandle pass() {
+ return this;
+ }
+
+ /**
+ * @see Handle#toUntypedHandle()
+ */
+ @Override
+ public UntypedHandle toUntypedHandle() {
+ return this;
+ }
+
+ /**
+ * @see Handle#releaseNativeHandle()
+ */
+ @Override
+ public int releaseNativeHandle() {
+ return 0;
+ }
+
+ /**
+ * @see UntypedHandle#toMessagePipeHandle()
+ */
+ @Override
+ public MessagePipeHandle toMessagePipeHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toDataPipeConsumerHandle()
+ */
+ @Override
+ public ConsumerHandle toDataPipeConsumerHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toDataPipeProducerHandle()
+ */
+ @Override
+ public ProducerHandle toDataPipeProducerHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toSharedBufferHandle()
+ */
+ @Override
+ public SharedBufferHandle toSharedBufferHandle() {
+ return this;
+ }
+
+ /**
+ * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions)
+ */
+ @Override
+ public SharedBufferHandle duplicate(DuplicateOptions options) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags)
+ */
+ @Override
+ public ByteBuffer map(long offset, long numBytes, MapFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see SharedBufferHandle#unmap(java.nio.ByteBuffer)
+ */
+ @Override
+ public void unmap(ByteBuffer buffer) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags)
+ */
+ @Override
+ public ResultAnd<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ProducerHandle#beginWriteData(int, DataPipe.WriteFlags)
+ */
+ @Override
+ public ByteBuffer beginWriteData(int numBytes,
+ DataPipe.WriteFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ProducerHandle#endWriteData(int)
+ */
+ @Override
+ public void endWriteData(int numBytesWritten) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#discardData(int, DataPipe.ReadFlags)
+ */
+ @Override
+ public int discardData(int numBytes, DataPipe.ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags)
+ */
+ @Override
+ public ResultAnd<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#beginReadData(int, DataPipe.ReadFlags)
+ */
+ @Override
+ public ByteBuffer beginReadData(int numBytes,
+ DataPipe.ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#endReadData(int)
+ */
+ @Override
+ public void endReadData(int numBytesRead) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List,
+ * MessagePipeHandle.WriteFlags)
+ */
+ @Override
+ public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags)
+ */
+ @Override
+ public ResultAnd<ReadMessageResult> readMessage(
+ ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
new file mode 100644
index 0000000000..deb6ac0f01
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Message pipes are bidirectional communication channel for framed data (i.e., messages). Messages
+ * can contain plain data and/or Mojo handles.
+ */
+public interface MessagePipeHandle extends Handle {
+
+ /**
+ * Flags for the message pipe creation operation.
+ */
+ public static class CreateFlags extends Flags<CreateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final CreateFlags NONE = CreateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected CreateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static CreateFlags none() {
+ return new CreateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify creation parameters for a message pipe to |Core#createMessagePipe()|.
+ */
+ public static class CreateOptions {
+ private CreateFlags mFlags = CreateFlags.NONE;
+
+ /**
+ * @return the flags
+ */
+ public CreateFlags getFlags() {
+ return mFlags;
+ }
+
+ }
+
+ /**
+ * Flags for the write operations on MessagePipeHandle .
+ */
+ public static class WriteFlags extends Flags<WriteFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with no bit set.
+ */
+ public static final WriteFlags NONE = WriteFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ private WriteFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static WriteFlags none() {
+ return new WriteFlags(FLAG_NONE);
+ }
+ }
+
+ /**
+ * Flags for the read operations on MessagePipeHandle.
+ */
+ public static class ReadFlags extends Flags<ReadFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_MAY_DISCARD = 1 << 0;
+
+ /**
+ * Immutable flag with no bit set.
+ */
+ public static final ReadFlags NONE = ReadFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ private ReadFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the may-discard bit of this flag. If set, if the message is unable to be read for
+ * whatever reason (e.g., the caller-supplied buffer is too small), discard the message
+ * (i.e., simply dequeue it).
+ *
+ * @param mayDiscard the new value of the may-discard bit.
+ * @return this.
+ */
+ public ReadFlags setMayDiscard(boolean mayDiscard) {
+ return setFlag(FLAG_MAY_DISCARD, mayDiscard);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static ReadFlags none() {
+ return new ReadFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Result of the |readMessage| method.
+ */
+ public static class ReadMessageResult {
+ /**
+ * If a message was read, the size in bytes of the message, otherwise the size in bytes of
+ * the next message.
+ */
+ private int mMessageSize;
+ /**
+ * If a message was read, the number of handles contained in the message, otherwise the
+ * number of handles contained in the next message.
+ */
+ private int mHandlesCount;
+ /**
+ * If a message was read, the handles contained in the message, undefined otherwise.
+ */
+ private List<UntypedHandle> mHandles;
+
+ /**
+ * @return the messageSize
+ */
+ public int getMessageSize() {
+ return mMessageSize;
+ }
+
+ /**
+ * @param messageSize the messageSize to set
+ */
+ public void setMessageSize(int messageSize) {
+ mMessageSize = messageSize;
+ }
+
+ /**
+ * @return the handlesCount
+ */
+ public int getHandlesCount() {
+ return mHandlesCount;
+ }
+
+ /**
+ * @param handlesCount the handlesCount to set
+ */
+ public void setHandlesCount(int handlesCount) {
+ mHandlesCount = handlesCount;
+ }
+
+ /**
+ * @return the handles
+ */
+ public List<UntypedHandle> getHandles() {
+ return mHandles;
+ }
+
+ /**
+ * @param handles the handles to set
+ */
+ public void setHandles(List<UntypedHandle> handles) {
+ mHandles = handles;
+ }
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public MessagePipeHandle pass();
+
+ /**
+ * Writes a message to the message pipe endpoint, with message data specified by |bytes| and
+ * attached handles specified by |handles|, and options specified by |flags|. If there is no
+ * message data, |bytes| may be null, otherwise it must be a direct ByteBuffer. If there are no
+ * attached handles, |handles| may be null.
+ * <p>
+ * If handles are attached, on success the handles will no longer be valid (the receiver will
+ * receive equivalent, but logically different, handles). Handles to be sent should not be in
+ * simultaneous use (e.g., on another thread).
+ */
+ void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags);
+
+ /**
+ * Reads a message from the message pipe endpoint; also usable to query the size of the next
+ * message or discard the next message. |bytes| indicate the buffer/buffer size to receive the
+ * message data (if any) and |maxNumberOfHandles| indicate the maximum handle count to receive
+ * the attached handles (if any). |bytes| is optional. If null, |maxNumberOfHandles| must be
+ * zero, and the return value will indicate the size of the next message. If non-null, it must
+ * be a direct ByteBuffer and the return value will indicate if the message was read or not. If
+ * the message was read its content will be in |bytes|, and the attached handles will be in the
+ * return value. Partial reads are NEVER done. Either a full read is done and |wasMessageRead|
+ * will be true, or the read is NOT done and |wasMessageRead| will be false (if |mayDiscard| was
+ * set, the message is also discarded in this case).
+ */
+ ResultAnd<ReadMessageResult> readMessage(
+ ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags);
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java
new file mode 100644
index 0000000000..4e0e3e9597
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * Exception for the core mojo API.
+ */
+public class MojoException extends RuntimeException {
+
+ private final int mCode;
+
+ /**
+ * Constructor.
+ */
+ public MojoException(int code) {
+ mCode = code;
+ }
+
+ /**
+ * Constructor.
+ */
+ public MojoException(Throwable cause) {
+ super(cause);
+ mCode = MojoResult.UNKNOWN;
+ }
+
+ /**
+ * The mojo result code associated with this exception. See {@link MojoResult} for possible
+ * values.
+ */
+ public int getMojoResult() {
+ return mCode;
+ }
+
+ /**
+ * @see Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "MojoResult(" + mCode + "): " + MojoResult.describe(mCode);
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java
new file mode 100644
index 0000000000..2602cb5e1e
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * The different mojo result codes.
+ */
+public final class MojoResult {
+ public static final int OK = 0;
+ public static final int CANCELLED = 1;
+ public static final int UNKNOWN = 2;
+ public static final int INVALID_ARGUMENT = 3;
+ public static final int DEADLINE_EXCEEDED = 4;
+ public static final int NOT_FOUND = 5;
+ public static final int ALREADY_EXISTS = 6;
+ public static final int PERMISSION_DENIED = 7;
+ public static final int RESOURCE_EXHAUSTED = 8;
+ public static final int FAILED_PRECONDITION = 9;
+ public static final int ABORTED = 10;
+ public static final int OUT_OF_RANGE = 11;
+ public static final int UNIMPLEMENTED = 12;
+ public static final int INTERNAL = 13;
+ public static final int UNAVAILABLE = 14;
+ public static final int DATA_LOSS = 15;
+ public static final int BUSY = 16;
+ public static final int SHOULD_WAIT = 17;
+
+ /**
+ * never instantiate.
+ */
+ private MojoResult() {
+ }
+
+ /**
+ * Describes the given result code.
+ */
+ public static String describe(int mCode) {
+ switch (mCode) {
+ case OK:
+ return "OK";
+ case CANCELLED:
+ return "CANCELLED";
+ case UNKNOWN:
+ return "UNKNOWN";
+ case INVALID_ARGUMENT:
+ return "INVALID_ARGUMENT";
+ case DEADLINE_EXCEEDED:
+ return "DEADLINE_EXCEEDED";
+ case NOT_FOUND:
+ return "NOT_FOUND";
+ case ALREADY_EXISTS:
+ return "ALREADY_EXISTS";
+ case PERMISSION_DENIED:
+ return "PERMISSION_DENIED";
+ case RESOURCE_EXHAUSTED:
+ return "RESOURCE_EXHAUSTED";
+ case FAILED_PRECONDITION:
+ return "FAILED_PRECONDITION";
+ case ABORTED:
+ return "ABORTED";
+ case OUT_OF_RANGE:
+ return "OUT_OF_RANGE";
+ case UNIMPLEMENTED:
+ return "UNIMPLEMENTED";
+ case INTERNAL:
+ return "INTERNAL";
+ case UNAVAILABLE:
+ return "UNAVAILABLE";
+ case DATA_LOSS:
+ return "DATA_LOSS";
+ case BUSY:
+ return "BUSY";
+ case SHOULD_WAIT:
+ return "SHOULD_WAIT";
+ default:
+ return "UNKNOWN";
+ }
+
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java
new file mode 100644
index 0000000000..2ead0204f9
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+
+/**
+ * A pair of object.
+ *
+ * @param <F> Type of the first element.
+ * @param <S> Type of the second element.
+ */
+public class Pair<F, S> {
+
+ public final F first;
+ public final S second;
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param first the first element of the pair.
+ * @param second the second element of the pair.
+ */
+ public Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ /**
+ * equals() that handles null values.
+ */
+ private boolean equals(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Pair)) {
+ return false;
+ }
+ Pair<?, ?> p = (Pair<?, ?>) o;
+ return equals(first, p.first) && equals(second, p.second);
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
+ }
+
+ /**
+ * Helper method for creating a pair.
+ *
+ * @param a the first element of the pair.
+ * @param b the second element of the pair.
+ * @return the pair containing a and b.
+ */
+ public static <A, B> Pair<A, B> create(A a, B b) {
+ return new Pair<A, B>(a, b);
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java b/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java
new file mode 100644
index 0000000000..656d0d64e4
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * Container that contains a mojo result and a value.
+ *
+ * @param <A> the type of the value.
+ */
+public class ResultAnd<A> {
+ private final int mMojoResult;
+ private final A mValue;
+
+ public ResultAnd(int result, A value) {
+ this.mMojoResult = result;
+ this.mValue = value;
+ }
+
+ /**
+ * Returns the mojo result.
+ */
+ public int getMojoResult() {
+ return mMojoResult;
+ }
+
+ /**
+ * Returns the value.
+ */
+ public A getValue() {
+ return mValue;
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java b/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java
new file mode 100644
index 0000000000..4038b2954e
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import java.io.Closeable;
+
+/**
+ * Definition of a run loop.
+ */
+public interface RunLoop extends Closeable {
+ /**
+ * Start the run loop. It will continue until quit() is called.
+ */
+ public void run();
+
+ /**
+ * Start the run loop and stop it as soon as no task is present in the work queue.
+ */
+ public void runUntilIdle();
+
+ /*
+ * Quit the currently running run loop.
+ */
+ public void quit();
+
+ /**
+ * Add a runnable to the queue of tasks.
+ * @param runnable Callback to be executed by the run loop.
+ * @param delay Delay, in MojoTimeTicks (microseconds) before the callback should
+ * be executed.
+ */
+ public void postDelayedTask(Runnable runnable, long delay);
+
+ /**
+ * Destroy the run loop and deregister it from Core.
+ */
+ @Override
+ public abstract void close();
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java
new file mode 100644
index 0000000000..df317d134d
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A buffer that can be shared between applications.
+ */
+public interface SharedBufferHandle extends Handle {
+
+ /**
+ * Flags for the shared buffer creation operation.
+ */
+ public static class CreateFlags extends Flags<CreateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final CreateFlags NONE = CreateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected CreateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static CreateFlags none() {
+ return new CreateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify creation parameters for a shared buffer to |Core#createSharedBuffer()|.
+ */
+ public static class CreateOptions {
+ private CreateFlags mFlags = CreateFlags.NONE;
+
+ /**
+ * @return the flags
+ */
+ public CreateFlags getFlags() {
+ return mFlags;
+ }
+
+ }
+
+ /**
+ * Flags for the shared buffer duplication operation.
+ */
+ public static class DuplicateFlags extends Flags<DuplicateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final DuplicateFlags NONE = DuplicateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected DuplicateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static DuplicateFlags none() {
+ return new DuplicateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify parameters in duplicating access to a shared buffer to
+ * |SharedBufferHandle#duplicate|
+ */
+ public static class DuplicateOptions {
+ private DuplicateFlags mFlags = DuplicateFlags.NONE;
+
+ /**
+ * @return the flags
+ */
+ public DuplicateFlags getFlags() {
+ return mFlags;
+ }
+
+ }
+
+ /**
+ * Flags for the shared buffer map operation.
+ */
+ public static class MapFlags extends Flags<MapFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final MapFlags NONE = MapFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected MapFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static MapFlags none() {
+ return new MapFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public SharedBufferHandle pass();
+
+ /**
+ * Duplicates the handle. This creates another handle (returned on success), which can then be
+ * sent to another application over a message pipe, while retaining access to this handle (and
+ * any mappings that it may have).
+ */
+ public SharedBufferHandle duplicate(DuplicateOptions options);
+
+ /**
+ * Map the part (at offset |offset| of length |numBytes|) of the buffer given by this handle
+ * into memory. |offset + numBytes| must be less than or equal to the size of the buffer. On
+ * success, the returned buffer points to memory with the requested part of the buffer. A single
+ * buffer handle may have multiple active mappings (possibly depending on the buffer type). The
+ * permissions (e.g., writable or executable) of the returned memory may depend on the
+ * properties of the buffer and properties attached to the buffer handle as well as |flags|.
+ */
+ public ByteBuffer map(long offset, long numBytes, MapFlags flags);
+
+ /**
+ * Unmap a buffer pointer that was mapped by |map()|.
+ */
+ public void unmap(ByteBuffer buffer);
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java
new file mode 100644
index 0000000000..199b0a1c04
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+
+/**
+ * A mojo handle of unknown type. This handle can be typed by using one of its methods, which will
+ * return a handle of the requested type and invalidate this object. No validation is made when the
+ * conversion operation is called.
+ */
+public interface UntypedHandle extends Handle {
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public UntypedHandle pass();
+
+ /**
+ * Returns the underlying handle, as a {@link MessagePipeHandle}, invalidating this
+ * representation.
+ */
+ public MessagePipeHandle toMessagePipeHandle();
+
+ /**
+ * Returns the underlying handle, as a {@link ConsumerHandle}, invalidating this representation.
+ */
+ public ConsumerHandle toDataPipeConsumerHandle();
+
+ /**
+ * Returns the underlying handle, as a {@link ProducerHandle}, invalidating this representation.
+ */
+ public ProducerHandle toDataPipeProducerHandle();
+
+ /**
+ * Returns the underlying handle, as a {@link SharedBufferHandle}, invalidating this
+ * representation.
+ */
+ public SharedBufferHandle toSharedBufferHandle();
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java b/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java
new file mode 100644
index 0000000000..9c70161067
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java
@@ -0,0 +1,38 @@
+// 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.
+
+package org.chromium.mojo.system;
+
+import org.chromium.mojo.system.Core.HandleSignals;
+
+/**
+ * Watches a handle for signals being satisfied.
+ */
+public interface Watcher {
+ /**
+ * Callback passed to {@link Watcher#start}.
+ */
+ public interface Callback {
+ /**
+ * Called when the handle is ready.
+ */
+ public void onResult(int result);
+ }
+
+ /**
+ * Starts watching a handle.
+ */
+ int start(Handle handle, HandleSignals signals, Callback callback);
+
+ /**
+ * Cancels an already-started watch.
+ */
+ void cancel();
+
+ /**
+ * Destroys the underlying implementation. Other methods will fail after destroy has been
+ * called.
+ */
+ void destroy();
+}
diff --git a/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn
new file mode 100644
index 0000000000..5ed57a1328
--- /dev/null
+++ b/mojo/public/js/BUILD.gn
@@ -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.
+
+interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings"
+
+source_set("js") {
+ sources = [
+ "constants.cc",
+ "constants.h",
+ ]
+}
+
+group("bindings") {
+ data = [
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js",
+ "bindings.js",
+ "buffer.js",
+ "codec.js",
+ "connector.js",
+ "core.js",
+ "interface_types.js",
+ "lib/control_message_handler.js",
+ "lib/control_message_proxy.js",
+ "lib/interface_endpoint_client.js",
+ "lib/interface_endpoint_handle.js",
+ "lib/pipe_control_message_handler.js",
+ "lib/pipe_control_message_proxy.js",
+ "router.js",
+ "support.js",
+ "threading.js",
+ "unicode.js",
+ "validator.js",
+ ]
+
+ deps = [
+ ":new_bindings",
+ "//mojo/public/interfaces/bindings:bindings__generator",
+ ]
+}
+
+action("new_bindings") {
+ new_bindings_js_files = [
+ # This must be the first file in the list, because it initializes global
+ # variable |mojoBindings| that the others need to refer to.
+ "new_bindings/base.js",
+
+ "$interfaces_bindings_gen_dir/new_bindings/interface_control_messages.mojom.js",
+ "new_bindings/bindings.js",
+ "new_bindings/buffer.js",
+ "new_bindings/codec.js",
+ "new_bindings/connector.js",
+ "new_bindings/interface_types.js",
+ "new_bindings/lib/control_message_handler.js",
+ "new_bindings/lib/control_message_proxy.js",
+ "new_bindings/router.js",
+ "new_bindings/unicode.js",
+ "new_bindings/validator.js",
+ ]
+ compiled_file = "$target_gen_dir/mojo_bindings.js"
+
+ # TODO(yzshen): Eventually we would like to use Closure Compiler to minify the
+ # bindings instead of simply concatenating the files.
+ script = "//v8/tools/concatenate-files.py"
+
+ sources = new_bindings_js_files
+ outputs = [
+ compiled_file,
+ ]
+
+ args = rebase_path(new_bindings_js_files)
+ args += [ rebase_path(compiled_file) ]
+
+ deps = [
+ "//mojo/public/interfaces/bindings:new_bindings__generator",
+ ]
+}
+
+group("tests") {
+ testonly = true
+
+ data = [
+ "//mojo/public/interfaces/bindings/tests/data/validation/",
+ "tests/core_unittest.js",
+ "tests/validation_test_input_parser.js",
+ "tests/validation_unittest.js",
+ ]
+
+ public_deps = [
+ ":bindings",
+ ]
+}
diff --git a/mojo/public/js/README.md b/mojo/public/js/README.md
new file mode 100644
index 0000000000..b6eafe9c82
--- /dev/null
+++ b/mojo/public/js/README.md
@@ -0,0 +1,7 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo JavaScript System and Bindings APIs
+This document is a subset of the [Mojo documentation](/mojo).
+
+**NOTE:** The JavaScript APIs are currently in flux and will stabilize soon.
+More info forthcoming ASAP!
+
+TODO: Make the contents of this document less non-existent.
diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js
new file mode 100644
index 0000000000..a944e2f7aa
--- /dev/null
+++ b/mojo/public/js/bindings.js
@@ -0,0 +1,322 @@
+// Copyright 2014 The Chromium 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("mojo/public/js/bindings", [
+ "mojo/public/js/core",
+ "mojo/public/js/interface_types",
+ "mojo/public/js/lib/interface_endpoint_client",
+ "mojo/public/js/router",
+], function(core, types, interfaceEndpointClient, router) {
+
+ var InterfaceEndpointClient = interfaceEndpointClient.InterfaceEndpointClient;
+
+ // ---------------------------------------------------------------------------
+
+ function makeRequest(interfacePtr) {
+ var pipe = core.createMessagePipe();
+ interfacePtr.ptr.bind(new types.InterfacePtrInfo(pipe.handle0, 0));
+ return new types.InterfaceRequest(pipe.handle1);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // Operations used to setup/configure an interface pointer. Exposed as the
+ // |ptr| field of generated interface pointer classes.
+ // |ptrInfoOrHandle| could be omitted and passed into bind() later.
+ function InterfacePtrController(interfaceType, ptrInfoOrHandle) {
+ this.version = 0;
+
+ this.interfaceType_ = interfaceType;
+ this.router_ = null;
+ this.interfaceEndpointClient_ = null;
+ this.proxy_ = null;
+
+ // |router_| and |interfaceEndpointClient_| are lazily initialized.
+ // |handle_| is valid between bind() and
+ // the initialization of |router_| and |interfaceEndpointClient_|.
+ this.handle_ = null;
+
+ if (ptrInfoOrHandle)
+ this.bind(ptrInfoOrHandle);
+ }
+
+ InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
+ this.reset();
+
+ if (ptrInfoOrHandle instanceof types.InterfacePtrInfo) {
+ this.version = ptrInfoOrHandle.version;
+ this.handle_ = ptrInfoOrHandle.handle;
+ } else {
+ this.handle_ = ptrInfoOrHandle;
+ }
+ };
+
+ InterfacePtrController.prototype.isBound = function() {
+ return this.router_ !== null || this.handle_ !== null;
+ };
+
+ // Although users could just discard the object, reset() closes the pipe
+ // immediately.
+ InterfacePtrController.prototype.reset = function() {
+ this.version = 0;
+ if (this.interfaceEndpointClient_) {
+ this.interfaceEndpointClient_.close();
+ this.interfaceEndpointClient_ = null;
+ }
+ if (this.router_) {
+ this.router_.close();
+ this.router_ = null;
+
+ this.proxy_ = null;
+ }
+ if (this.handle_) {
+ core.close(this.handle_);
+ this.handle_ = null;
+ }
+ };
+
+ InterfacePtrController.prototype.resetWithReason = function(reason) {
+ this.configureProxyIfNecessary_();
+ this.interfaceEndpointClient_.close(reason);
+ this.interfaceEndpointClient_ = null;
+ this.reset();
+ };
+
+ InterfacePtrController.prototype.setConnectionErrorHandler = function(
+ callback) {
+ if (!this.isBound())
+ throw new Error("Cannot set connection error handler if not bound.");
+
+ this.configureProxyIfNecessary_();
+ this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
+ };
+
+ InterfacePtrController.prototype.passInterface = function() {
+ var result;
+ if (this.router_) {
+ // TODO(yzshen): Fix Router interface to support extracting handle.
+ result = new types.InterfacePtrInfo(
+ this.router_.connector_.handle_, this.version);
+ this.router_.connector_.handle_ = null;
+ } else {
+ // This also handles the case when this object is not bound.
+ result = new types.InterfacePtrInfo(this.handle_, this.version);
+ this.handle_ = null;
+ }
+
+ this.reset();
+ return result;
+ };
+
+ InterfacePtrController.prototype.getProxy = function() {
+ this.configureProxyIfNecessary_();
+ return this.proxy_;
+ };
+
+ InterfacePtrController.prototype.waitForNextMessageForTesting = function() {
+ this.configureProxyIfNecessary_();
+ this.router_.waitForNextMessageForTesting();
+ };
+
+ InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
+ if (!this.handle_)
+ return;
+
+ this.router_ = new router.Router(this.handle_);
+ this.handle_ = null;
+
+ this.interfaceEndpointClient_ = new InterfaceEndpointClient(
+ this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
+ this.router_);
+
+ this.interfaceEndpointClient_ .setPayloadValidators([
+ this.interfaceType_.validateResponse]);
+ this.proxy_ = new this.interfaceType_.proxyClass(
+ this.interfaceEndpointClient_);
+ };
+
+ InterfacePtrController.prototype.queryVersion = function() {
+ function onQueryVersion(version) {
+ this.version = version;
+ return version;
+ }
+
+ this.configureProxyIfNecessary_();
+ return this.interfaceEndpointClient_.queryVersion().then(
+ onQueryVersion.bind(this));
+ };
+
+ InterfacePtrController.prototype.requireVersion = function(version) {
+ this.configureProxyIfNecessary_();
+
+ if (this.version >= version) {
+ return;
+ }
+ this.version = version;
+ this.interfaceEndpointClient_.requireVersion(version);
+ };
+
+ // ---------------------------------------------------------------------------
+
+ // |request| could be omitted and passed into bind() later.
+ //
+ // Example:
+ //
+ // // FooImpl implements mojom.Foo.
+ // function FooImpl() { ... }
+ // FooImpl.prototype.fooMethod1 = function() { ... }
+ // FooImpl.prototype.fooMethod2 = function() { ... }
+ //
+ // var fooPtr = new mojom.FooPtr();
+ // var request = makeRequest(fooPtr);
+ // var binding = new Binding(mojom.Foo, new FooImpl(), request);
+ // fooPtr.fooMethod1();
+ function Binding(interfaceType, impl, requestOrHandle) {
+ this.interfaceType_ = interfaceType;
+ this.impl_ = impl;
+ this.router_ = null;
+ this.interfaceEndpointClient_ = null;
+ this.stub_ = null;
+
+ if (requestOrHandle)
+ this.bind(requestOrHandle);
+ }
+
+ Binding.prototype.isBound = function() {
+ return this.router_ !== null;
+ };
+
+ Binding.prototype.createInterfacePtrAndBind = function() {
+ var ptr = new this.interfaceType_.ptrClass();
+ // TODO(yzshen): Set the version of the interface pointer.
+ this.bind(makeRequest(ptr));
+ return ptr;
+ };
+
+ Binding.prototype.bind = function(requestOrHandle) {
+ this.close();
+
+ var handle = requestOrHandle instanceof types.InterfaceRequest ?
+ requestOrHandle.handle : requestOrHandle;
+ if (!core.isHandle(handle))
+ return;
+
+ this.router_ = new router.Router(handle);
+
+ this.stub_ = new this.interfaceType_.stubClass(this.impl_);
+ this.interfaceEndpointClient_ = new InterfaceEndpointClient(
+ this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
+ this.router_, this.interfaceType_.kVersion);
+ this.interfaceEndpointClient_.setIncomingReceiver(this.stub_);
+ this.interfaceEndpointClient_ .setPayloadValidators([
+ this.interfaceType_.validateRequest]);
+ };
+
+ Binding.prototype.close = function() {
+ if (!this.isBound())
+ return;
+
+ if (this.interfaceEndpointClient_) {
+ this.interfaceEndpointClient_.close();
+ this.interfaceEndpointClient_ = null;
+ }
+
+ this.router_.close();
+ this.router_ = null;
+ this.stub_ = null;
+ };
+
+ Binding.prototype.closeWithReason = function(reason) {
+ if (this.interfaceEndpointClient_) {
+ this.interfaceEndpointClient_.close(reason);
+ this.interfaceEndpointClient_ = null;
+ }
+ this.close();
+ };
+
+ Binding.prototype.setConnectionErrorHandler
+ = function(callback) {
+ if (!this.isBound()) {
+ throw new Error("Cannot set connection error handler if not bound.");
+ }
+ this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
+ };
+
+ Binding.prototype.unbind = function() {
+ if (!this.isBound())
+ return new types.InterfaceRequest(null);
+
+ var result = new types.InterfaceRequest(this.router_.connector_.handle_);
+ this.router_.connector_.handle_ = null;
+ this.close();
+ return result;
+ };
+
+ Binding.prototype.waitForNextMessageForTesting = function() {
+ this.router_.waitForNextMessageForTesting();
+ };
+
+ // ---------------------------------------------------------------------------
+
+ function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle,
+ bindingId) {
+ this.bindingSet_ = bindingSet;
+ this.bindingId_ = bindingId;
+ this.binding_ = new Binding(interfaceType, impl, requestOrHandle);
+
+ this.binding_.setConnectionErrorHandler(function() {
+ this.bindingSet_.onConnectionError(bindingId);
+ }.bind(this));
+ }
+
+ BindingSetEntry.prototype.close = function() {
+ this.binding_.close();
+ };
+
+ function BindingSet(interfaceType) {
+ this.interfaceType_ = interfaceType;
+ this.nextBindingId_ = 0;
+ this.bindings_ = new Map();
+ this.errorHandler_ = null;
+ }
+
+ BindingSet.prototype.isEmpty = function() {
+ return this.bindings_.size == 0;
+ };
+
+ BindingSet.prototype.addBinding = function(impl, requestOrHandle) {
+ this.bindings_.set(
+ this.nextBindingId_,
+ new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle,
+ this.nextBindingId_));
+ ++this.nextBindingId_;
+ };
+
+ BindingSet.prototype.closeAllBindings = function() {
+ for (var entry of this.bindings_.values())
+ entry.close();
+ this.bindings_.clear();
+ };
+
+ BindingSet.prototype.setConnectionErrorHandler = function(callback) {
+ this.errorHandler_ = callback;
+ };
+
+ BindingSet.prototype.onConnectionError = function(bindingId) {
+ this.bindings_.delete(bindingId);
+
+ if (this.errorHandler_)
+ this.errorHandler_();
+ };
+
+ var exports = {};
+ exports.InterfacePtrInfo = types.InterfacePtrInfo;
+ exports.InterfaceRequest = types.InterfaceRequest;
+ exports.makeRequest = makeRequest;
+ exports.InterfacePtrController = InterfacePtrController;
+ exports.Binding = Binding;
+ exports.BindingSet = BindingSet;
+
+ return exports;
+});
diff --git a/mojo/public/js/buffer.js b/mojo/public/js/buffer.js
new file mode 100644
index 0000000000..e35f69513f
--- /dev/null
+++ b/mojo/public/js/buffer.js
@@ -0,0 +1,156 @@
+// Copyright 2014 The Chromium 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("mojo/public/js/buffer", function() {
+
+ var kHostIsLittleEndian = (function () {
+ var endianArrayBuffer = new ArrayBuffer(2);
+ var endianUint8Array = new Uint8Array(endianArrayBuffer);
+ var endianUint16Array = new Uint16Array(endianArrayBuffer);
+ endianUint16Array[0] = 1;
+ return endianUint8Array[0] == 1;
+ })();
+
+ var kHighWordMultiplier = 0x100000000;
+
+ function Buffer(sizeOrArrayBuffer) {
+ if (sizeOrArrayBuffer instanceof ArrayBuffer)
+ this.arrayBuffer = sizeOrArrayBuffer;
+ else
+ this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer);
+
+ this.dataView = new DataView(this.arrayBuffer);
+ this.next = 0;
+ }
+
+ Object.defineProperty(Buffer.prototype, "byteLength", {
+ get: function() { return this.arrayBuffer.byteLength; }
+ });
+
+ Buffer.prototype.alloc = function(size) {
+ var pointer = this.next;
+ this.next += size;
+ if (this.next > this.byteLength) {
+ var newSize = (1.5 * (this.byteLength + size)) | 0;
+ this.grow(newSize);
+ }
+ return pointer;
+ };
+
+ function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) {
+ (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer));
+ }
+
+ Buffer.prototype.grow = function(size) {
+ var newArrayBuffer = new ArrayBuffer(size);
+ copyArrayBuffer(newArrayBuffer, this.arrayBuffer);
+ this.arrayBuffer = newArrayBuffer;
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.trim = function() {
+ this.arrayBuffer = this.arrayBuffer.slice(0, this.next);
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.getUint8 = function(offset) {
+ return this.dataView.getUint8(offset);
+ }
+ Buffer.prototype.getUint16 = function(offset) {
+ return this.dataView.getUint16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint32 = function(offset) {
+ return this.dataView.getUint32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getInt8 = function(offset) {
+ return this.dataView.getInt8(offset);
+ }
+ Buffer.prototype.getInt16 = function(offset) {
+ return this.dataView.getInt16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt32 = function(offset) {
+ return this.dataView.getInt32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getInt32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getFloat32 = function(offset) {
+ return this.dataView.getFloat32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getFloat64 = function(offset) {
+ return this.dataView.getFloat64(offset, kHostIsLittleEndian);
+ }
+
+ Buffer.prototype.setUint8 = function(offset, value) {
+ this.dataView.setUint8(offset, value);
+ }
+ Buffer.prototype.setUint16 = function(offset, value) {
+ this.dataView.setUint16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint32 = function(offset, value) {
+ this.dataView.setUint32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint64 = function(offset, value) {
+ var hi = (value / kHighWordMultiplier) | 0;
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setInt8 = function(offset, value) {
+ this.dataView.setInt8(offset, value);
+ }
+ Buffer.prototype.setInt16 = function(offset, value) {
+ this.dataView.setInt16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt32 = function(offset, value) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt64 = function(offset, value) {
+ var hi = Math.floor(value / kHighWordMultiplier);
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setFloat32 = function(offset, value) {
+ this.dataView.setFloat32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setFloat64 = function(offset, value) {
+ this.dataView.setFloat64(offset, value, kHostIsLittleEndian);
+ }
+
+ var exports = {};
+ exports.Buffer = Buffer;
+ return exports;
+});
diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js
new file mode 100644
index 0000000000..ce58a8cfd4
--- /dev/null
+++ b/mojo/public/js/codec.js
@@ -0,0 +1,926 @@
+// Copyright 2014 The Chromium 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("mojo/public/js/codec", [
+ "mojo/public/js/buffer",
+ "mojo/public/js/interface_types",
+ "mojo/public/js/unicode",
+], function(buffer, types, unicode) {
+
+ var kErrorUnsigned = "Passing negative value to unsigned";
+ var kErrorArray = "Passing non Array for array type";
+ var kErrorString = "Passing non String for string type";
+ var kErrorMap = "Passing non Map for map type";
+
+ // Memory -------------------------------------------------------------------
+
+ var kAlignment = 8;
+
+ function align(size) {
+ return size + (kAlignment - (size % kAlignment)) % kAlignment;
+ }
+
+ function isAligned(offset) {
+ return offset >= 0 && (offset % kAlignment) === 0;
+ }
+
+ // Constants ----------------------------------------------------------------
+
+ var kArrayHeaderSize = 8;
+ var kStructHeaderSize = 8;
+ var kMessageHeaderSize = 24;
+ var kMessageWithRequestIDHeaderSize = 32;
+ var kMapStructPayloadSize = 16;
+
+ var kStructHeaderNumBytesOffset = 0;
+ var kStructHeaderVersionOffset = 4;
+
+ var kEncodedInvalidHandleValue = 0xFFFFFFFF;
+
+ // Decoder ------------------------------------------------------------------
+
+ function Decoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Decoder.prototype.align = function() {
+ this.next = align(this.next);
+ };
+
+ Decoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Decoder.prototype.readInt8 = function() {
+ var result = this.buffer.getInt8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readUint8 = function() {
+ var result = this.buffer.getUint8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readInt16 = function() {
+ var result = this.buffer.getInt16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readUint16 = function() {
+ var result = this.buffer.getUint16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readInt32 = function() {
+ var result = this.buffer.getInt32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readUint32 = function() {
+ var result = this.buffer.getUint32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readInt64 = function() {
+ var result = this.buffer.getInt64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readUint64 = function() {
+ var result = this.buffer.getUint64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readFloat = function() {
+ var result = this.buffer.getFloat32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readDouble = function() {
+ var result = this.buffer.getFloat64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.decodePointer = function() {
+ // TODO(abarth): To correctly decode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offsetPointer = this.next;
+ var offset = this.readUint64();
+ if (!offset)
+ return 0;
+ return offsetPointer + offset;
+ };
+
+ Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
+ return new Decoder(this.buffer, this.handles, pointer);
+ };
+
+ Decoder.prototype.decodeHandle = function() {
+ return this.handles[this.readUint32()] || null;
+ };
+
+ Decoder.prototype.decodeString = function() {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var base = this.next;
+ this.next += numberOfElements;
+ return unicode.decodeUtf8String(
+ new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
+ };
+
+ Decoder.prototype.decodeArray = function(cls) {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var val = new Array(numberOfElements);
+ if (cls === PackedBool) {
+ var byte;
+ for (var i = 0; i < numberOfElements; ++i) {
+ if (i % 8 === 0)
+ byte = this.readUint8();
+ val[i] = (byte & (1 << i % 8)) ? true : false;
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i) {
+ val[i] = cls.decode(this);
+ }
+ }
+ return val;
+ };
+
+ Decoder.prototype.decodeStruct = function(cls) {
+ return cls.decode(this);
+ };
+
+ Decoder.prototype.decodeStructPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return cls.decode(this.decodeAndCreateDecoder(pointer));
+ };
+
+ Decoder.prototype.decodeArrayPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
+ };
+
+ Decoder.prototype.decodeStringPointer = function() {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeString();
+ };
+
+ Decoder.prototype.decodeMap = function(keyClass, valueClass) {
+ this.skip(4); // numberOfBytes
+ this.skip(4); // version
+ var keys = this.decodeArrayPointer(keyClass);
+ var values = this.decodeArrayPointer(valueClass);
+ var val = new Map();
+ for (var i = 0; i < keys.length; i++)
+ val.set(keys[i], values[i]);
+ return val;
+ };
+
+ Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ var decoder = this.decodeAndCreateDecoder(pointer);
+ return decoder.decodeMap(keyClass, valueClass);
+ };
+
+ // Encoder ------------------------------------------------------------------
+
+ function Encoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Encoder.prototype.align = function() {
+ this.next = align(this.next);
+ };
+
+ Encoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Encoder.prototype.writeInt8 = function(val) {
+ this.buffer.setInt8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeUint8 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeInt16 = function(val) {
+ this.buffer.setInt16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeUint16 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeInt32 = function(val) {
+ this.buffer.setInt32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeUint32 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeInt64 = function(val) {
+ this.buffer.setInt64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeUint64 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeFloat = function(val) {
+ this.buffer.setFloat32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeDouble = function(val) {
+ this.buffer.setFloat64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.encodePointer = function(pointer) {
+ if (!pointer)
+ return this.writeUint64(0);
+ // TODO(abarth): To correctly encode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offset = pointer - this.next;
+ this.writeUint64(offset);
+ };
+
+ Encoder.prototype.createAndEncodeEncoder = function(size) {
+ var pointer = this.buffer.alloc(align(size));
+ this.encodePointer(pointer);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ Encoder.prototype.encodeHandle = function(handle) {
+ if (handle) {
+ this.handles.push(handle);
+ this.writeUint32(this.handles.length - 1);
+ } else {
+ this.writeUint32(kEncodedInvalidHandleValue);
+ }
+ };
+
+ Encoder.prototype.encodeString = function(val) {
+ var base = this.next + kArrayHeaderSize;
+ var numberOfElements = unicode.encodeUtf8String(
+ val, new Uint8Array(this.buffer.arrayBuffer, base));
+ var numberOfBytes = kArrayHeaderSize + numberOfElements;
+ this.writeUint32(numberOfBytes);
+ this.writeUint32(numberOfElements);
+ this.next += numberOfElements;
+ };
+
+ Encoder.prototype.encodeArray =
+ function(cls, val, numberOfElements, encodedSize) {
+ if (numberOfElements === undefined)
+ numberOfElements = val.length;
+ if (encodedSize === undefined)
+ encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
+
+ this.writeUint32(encodedSize);
+ this.writeUint32(numberOfElements);
+
+ if (cls === PackedBool) {
+ var byte = 0;
+ for (i = 0; i < numberOfElements; ++i) {
+ if (val[i])
+ byte |= (1 << i % 8);
+ if (i % 8 === 7 || i == numberOfElements - 1) {
+ Uint8.encode(this, byte);
+ byte = 0;
+ }
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i)
+ cls.encode(this, val[i]);
+ }
+ };
+
+ Encoder.prototype.encodeStruct = function(cls, val) {
+ return cls.encode(this, val);
+ };
+
+ Encoder.prototype.encodeStructPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encoder = this.createAndEncodeEncoder(cls.encodedSize);
+ cls.encode(encoder, val);
+ };
+
+ Encoder.prototype.encodeArrayPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+
+ var numberOfElements = val.length;
+ if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0)
+ throw new Error(kErrorArray);
+
+ var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
+ Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeArray(cls, val, numberOfElements, encodedSize);
+ };
+
+ Encoder.prototype.encodeStringPointer = function(val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ // Only accepts string primivites, not String Objects like new String("foo")
+ if (typeof(val) !== "string") {
+ throw new Error(kErrorString);
+ }
+ var encodedSize = kArrayHeaderSize + unicode.utf8Length(val);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeString(val);
+ };
+
+ Encoder.prototype.encodeMap = function(keyClass, valueClass, val) {
+ var keys = new Array(val.size);
+ var values = new Array(val.size);
+ var i = 0;
+ val.forEach(function(value, key) {
+ values[i] = value;
+ keys[i++] = key;
+ });
+ this.writeUint32(kStructHeaderSize + kMapStructPayloadSize);
+ this.writeUint32(0); // version
+ this.encodeArrayPointer(keyClass, keys);
+ this.encodeArrayPointer(valueClass, values);
+ }
+
+ Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ if (!(val instanceof Map)) {
+ throw new Error(kErrorMap);
+ }
+ var encodedSize = kStructHeaderSize + kMapStructPayloadSize;
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeMap(keyClass, valueClass, val);
+ };
+
+ // Message ------------------------------------------------------------------
+
+ var kMessageInterfaceIdOffset = kStructHeaderSize;
+ var kMessageNameOffset = kMessageInterfaceIdOffset + 4;
+ var kMessageFlagsOffset = kMessageNameOffset + 4;
+ var kMessageRequestIDOffset = kMessageFlagsOffset + 8;
+
+ var kMessageExpectsResponse = 1 << 0;
+ var kMessageIsResponse = 1 << 1;
+
+ function Message(buffer, handles) {
+ this.buffer = buffer;
+ this.handles = handles;
+ }
+
+ Message.prototype.getHeaderNumBytes = function() {
+ return this.buffer.getUint32(kStructHeaderNumBytesOffset);
+ };
+
+ Message.prototype.getHeaderVersion = function() {
+ return this.buffer.getUint32(kStructHeaderVersionOffset);
+ };
+
+ Message.prototype.getName = function() {
+ return this.buffer.getUint32(kMessageNameOffset);
+ };
+
+ Message.prototype.getFlags = function() {
+ return this.buffer.getUint32(kMessageFlagsOffset);
+ };
+
+ Message.prototype.getInterfaceId = function() {
+ return this.buffer.getUint32(kMessageInterfaceIdOffset);
+ };
+
+ Message.prototype.isResponse = function() {
+ return (this.getFlags() & kMessageIsResponse) != 0;
+ };
+
+ Message.prototype.expectsResponse = function() {
+ return (this.getFlags() & kMessageExpectsResponse) != 0;
+ };
+
+ Message.prototype.setRequestID = function(requestID) {
+ // TODO(darin): Verify that space was reserved for this field!
+ this.buffer.setUint64(kMessageRequestIDOffset, requestID);
+ };
+
+ Message.prototype.setInterfaceId = function(interfaceId) {
+ this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId);
+ };
+
+
+ // MessageBuilder -----------------------------------------------------------
+
+ function MessageBuilder(messageName, payloadSize) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageHeaderSize + payloadSize;
+ this.buffer = new buffer.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageHeaderSize);
+ encoder.writeUint32(kMessageHeaderSize);
+ encoder.writeUint32(0); // version.
+ encoder.writeUint32(0); // interface ID.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(0); // flags.
+ encoder.writeUint32(0); // padding.
+ }
+
+ MessageBuilder.prototype.createEncoder = function(size) {
+ var pointer = this.buffer.alloc(size);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ MessageBuilder.prototype.encodeStruct = function(cls, val) {
+ cls.encode(this.createEncoder(cls.encodedSize), val);
+ };
+
+ MessageBuilder.prototype.finish = function() {
+ // TODO(abarth): Rather than resizing the buffer at the end, we could
+ // compute the size we need ahead of time, like we do in C++.
+ this.buffer.trim();
+ var message = new Message(this.buffer, this.handles);
+ this.buffer = null;
+ this.handles = null;
+ this.encoder = null;
+ return message;
+ };
+
+ // MessageWithRequestIDBuilder -----------------------------------------------
+
+ function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+ requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
+ this.buffer = new buffer.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(1); // version.
+ encoder.writeUint32(0); // interface ID.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(flags);
+ encoder.writeUint32(0); // padding.
+ encoder.writeUint64(requestID);
+ }
+
+ MessageWithRequestIDBuilder.prototype =
+ Object.create(MessageBuilder.prototype);
+
+ MessageWithRequestIDBuilder.prototype.constructor =
+ MessageWithRequestIDBuilder;
+
+ // MessageReader ------------------------------------------------------------
+
+ function MessageReader(message) {
+ this.decoder = new Decoder(message.buffer, message.handles, 0);
+ var messageHeaderSize = this.decoder.readUint32();
+ this.payloadSize = message.buffer.byteLength - messageHeaderSize;
+ var version = this.decoder.readUint32();
+ var interface_id = this.decoder.readUint32();
+ this.messageName = this.decoder.readUint32();
+ this.flags = this.decoder.readUint32();
+ // Skip the padding.
+ this.decoder.skip(4);
+ if (version >= 1)
+ this.requestID = this.decoder.readUint64();
+ this.decoder.skip(messageHeaderSize - this.decoder.next);
+ }
+
+ MessageReader.prototype.decodeStruct = function(cls) {
+ return cls.decode(this.decoder);
+ };
+
+ // Built-in types -----------------------------------------------------------
+
+ // This type is only used with ArrayOf(PackedBool).
+ function PackedBool() {
+ }
+
+ function Int8() {
+ }
+
+ Int8.encodedSize = 1;
+
+ Int8.decode = function(decoder) {
+ return decoder.readInt8();
+ };
+
+ Int8.encode = function(encoder, val) {
+ encoder.writeInt8(val);
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Uint8() {
+ }
+
+ Uint8.encodedSize = 1;
+
+ Uint8.decode = function(decoder) {
+ return decoder.readUint8();
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Int16() {
+ }
+
+ Int16.encodedSize = 2;
+
+ Int16.decode = function(decoder) {
+ return decoder.readInt16();
+ };
+
+ Int16.encode = function(encoder, val) {
+ encoder.writeInt16(val);
+ };
+
+ function Uint16() {
+ }
+
+ Uint16.encodedSize = 2;
+
+ Uint16.decode = function(decoder) {
+ return decoder.readUint16();
+ };
+
+ Uint16.encode = function(encoder, val) {
+ encoder.writeUint16(val);
+ };
+
+ function Int32() {
+ }
+
+ Int32.encodedSize = 4;
+
+ Int32.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Int32.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function Uint32() {
+ }
+
+ Uint32.encodedSize = 4;
+
+ Uint32.decode = function(decoder) {
+ return decoder.readUint32();
+ };
+
+ Uint32.encode = function(encoder, val) {
+ encoder.writeUint32(val);
+ };
+
+ function Int64() {
+ }
+
+ Int64.encodedSize = 8;
+
+ Int64.decode = function(decoder) {
+ return decoder.readInt64();
+ };
+
+ Int64.encode = function(encoder, val) {
+ encoder.writeInt64(val);
+ };
+
+ function Uint64() {
+ }
+
+ Uint64.encodedSize = 8;
+
+ Uint64.decode = function(decoder) {
+ return decoder.readUint64();
+ };
+
+ Uint64.encode = function(encoder, val) {
+ encoder.writeUint64(val);
+ };
+
+ function String() {
+ };
+
+ String.encodedSize = 8;
+
+ String.decode = function(decoder) {
+ return decoder.decodeStringPointer();
+ };
+
+ String.encode = function(encoder, val) {
+ encoder.encodeStringPointer(val);
+ };
+
+ function NullableString() {
+ }
+
+ NullableString.encodedSize = String.encodedSize;
+
+ NullableString.decode = String.decode;
+
+ NullableString.encode = String.encode;
+
+ function Float() {
+ }
+
+ Float.encodedSize = 4;
+
+ Float.decode = function(decoder) {
+ return decoder.readFloat();
+ };
+
+ Float.encode = function(encoder, val) {
+ encoder.writeFloat(val);
+ };
+
+ function Double() {
+ }
+
+ Double.encodedSize = 8;
+
+ Double.decode = function(decoder) {
+ return decoder.readDouble();
+ };
+
+ Double.encode = function(encoder, val) {
+ encoder.writeDouble(val);
+ };
+
+ function Enum(cls) {
+ this.cls = cls;
+ }
+
+ Enum.prototype.encodedSize = 4;
+
+ Enum.prototype.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Enum.prototype.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function PointerTo(cls) {
+ this.cls = cls;
+ }
+
+ PointerTo.prototype.encodedSize = 8;
+
+ PointerTo.prototype.decode = function(decoder) {
+ var pointer = decoder.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
+ };
+
+ PointerTo.prototype.encode = function(encoder, val) {
+ if (!val) {
+ encoder.encodePointer(val);
+ return;
+ }
+ var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
+ this.cls.encode(objectEncoder, val);
+ };
+
+ function NullablePointerTo(cls) {
+ PointerTo.call(this, cls);
+ }
+
+ NullablePointerTo.prototype = Object.create(PointerTo.prototype);
+
+ function ArrayOf(cls, length) {
+ this.cls = cls;
+ this.length = length || 0;
+ }
+
+ ArrayOf.prototype.encodedSize = 8;
+
+ ArrayOf.prototype.dimensions = function() {
+ return [this.length].concat(
+ (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []);
+ }
+
+ ArrayOf.prototype.decode = function(decoder) {
+ return decoder.decodeArrayPointer(this.cls);
+ };
+
+ ArrayOf.prototype.encode = function(encoder, val) {
+ encoder.encodeArrayPointer(this.cls, val);
+ };
+
+ function NullableArrayOf(cls) {
+ ArrayOf.call(this, cls);
+ }
+
+ NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
+
+ function Handle() {
+ }
+
+ Handle.encodedSize = 4;
+
+ Handle.decode = function(decoder) {
+ return decoder.decodeHandle();
+ };
+
+ Handle.encode = function(encoder, val) {
+ encoder.encodeHandle(val);
+ };
+
+ function NullableHandle() {
+ }
+
+ NullableHandle.encodedSize = Handle.encodedSize;
+
+ NullableHandle.decode = Handle.decode;
+
+ NullableHandle.encode = Handle.encode;
+
+ function Interface(cls) {
+ this.cls = cls;
+ }
+
+ Interface.prototype.encodedSize = 8;
+
+ Interface.prototype.decode = function(decoder) {
+ var interfacePtrInfo = new types.InterfacePtrInfo(
+ decoder.decodeHandle(), decoder.readUint32());
+ var interfacePtr = new this.cls();
+ interfacePtr.ptr.bind(interfacePtrInfo);
+ return interfacePtr;
+ };
+
+ Interface.prototype.encode = function(encoder, val) {
+ var interfacePtrInfo =
+ val ? val.ptr.passInterface() : new types.InterfacePtrInfo(null, 0);
+ encoder.encodeHandle(interfacePtrInfo.handle);
+ encoder.writeUint32(interfacePtrInfo.version);
+ };
+
+ function NullableInterface(cls) {
+ Interface.call(this, cls);
+ }
+
+ NullableInterface.prototype = Object.create(Interface.prototype);
+
+ function InterfaceRequest() {
+ }
+
+ InterfaceRequest.encodedSize = 4;
+
+ InterfaceRequest.decode = function(decoder) {
+ return new types.InterfaceRequest(decoder.decodeHandle());
+ };
+
+ InterfaceRequest.encode = function(encoder, val) {
+ encoder.encodeHandle(val ? val.handle : null);
+ };
+
+ function NullableInterfaceRequest() {
+ }
+
+ NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize;
+
+ NullableInterfaceRequest.decode = InterfaceRequest.decode;
+
+ NullableInterfaceRequest.encode = InterfaceRequest.encode;
+
+ function MapOf(keyClass, valueClass) {
+ this.keyClass = keyClass;
+ this.valueClass = valueClass;
+ }
+
+ MapOf.prototype.encodedSize = 8;
+
+ MapOf.prototype.decode = function(decoder) {
+ return decoder.decodeMapPointer(this.keyClass, this.valueClass);
+ };
+
+ MapOf.prototype.encode = function(encoder, val) {
+ encoder.encodeMapPointer(this.keyClass, this.valueClass, val);
+ };
+
+ function NullableMapOf(keyClass, valueClass) {
+ MapOf.call(this, keyClass, valueClass);
+ }
+
+ NullableMapOf.prototype = Object.create(MapOf.prototype);
+
+ var exports = {};
+ exports.align = align;
+ exports.isAligned = isAligned;
+ exports.Message = Message;
+ exports.MessageBuilder = MessageBuilder;
+ exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+ exports.MessageReader = MessageReader;
+ exports.kArrayHeaderSize = kArrayHeaderSize;
+ exports.kMapStructPayloadSize = kMapStructPayloadSize;
+ exports.kStructHeaderSize = kStructHeaderSize;
+ exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
+ exports.kMessageHeaderSize = kMessageHeaderSize;
+ exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
+ exports.kMessageExpectsResponse = kMessageExpectsResponse;
+ exports.kMessageIsResponse = kMessageIsResponse;
+ exports.Int8 = Int8;
+ exports.Uint8 = Uint8;
+ exports.Int16 = Int16;
+ exports.Uint16 = Uint16;
+ exports.Int32 = Int32;
+ exports.Uint32 = Uint32;
+ exports.Int64 = Int64;
+ exports.Uint64 = Uint64;
+ exports.Float = Float;
+ exports.Double = Double;
+ exports.String = String;
+ exports.Enum = Enum;
+ exports.NullableString = NullableString;
+ exports.PointerTo = PointerTo;
+ exports.NullablePointerTo = NullablePointerTo;
+ exports.ArrayOf = ArrayOf;
+ exports.NullableArrayOf = NullableArrayOf;
+ exports.PackedBool = PackedBool;
+ exports.Handle = Handle;
+ exports.NullableHandle = NullableHandle;
+ exports.Interface = Interface;
+ exports.NullableInterface = NullableInterface;
+ exports.InterfaceRequest = InterfaceRequest;
+ exports.NullableInterfaceRequest = NullableInterfaceRequest;
+ exports.MapOf = MapOf;
+ exports.NullableMapOf = NullableMapOf;
+ return exports;
+});
diff --git a/mojo/public/js/connector.js b/mojo/public/js/connector.js
new file mode 100644
index 0000000000..012e3c7c07
--- /dev/null
+++ b/mojo/public/js/connector.js
@@ -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.
+
+define("mojo/public/js/connector", [
+ "mojo/public/js/buffer",
+ "mojo/public/js/codec",
+ "mojo/public/js/core",
+ "mojo/public/js/support",
+], function(buffer, codec, core, support) {
+
+ function Connector(handle) {
+ if (!core.isHandle(handle))
+ throw new Error("Connector: not a handle " + handle);
+ this.handle_ = handle;
+ this.dropWrites_ = false;
+ this.error_ = false;
+ this.incomingReceiver_ = null;
+ this.readWatcher_ = null;
+ this.errorHandler_ = null;
+
+ if (handle) {
+ this.readWatcher_ = support.watch(handle,
+ core.HANDLE_SIGNAL_READABLE,
+ this.readMore_.bind(this));
+ }
+ }
+
+ Connector.prototype.close = function() {
+ if (this.readWatcher_) {
+ support.cancelWatch(this.readWatcher_);
+ this.readWatcher_ = null;
+ }
+ if (this.handle_ != null) {
+ core.close(this.handle_);
+ this.handle_ = null;
+ }
+ };
+
+ Connector.prototype.accept = function(message) {
+ if (this.error_)
+ return false;
+
+ if (this.dropWrites_)
+ return true;
+
+ var result = core.writeMessage(this.handle_,
+ new Uint8Array(message.buffer.arrayBuffer),
+ message.handles,
+ core.WRITE_MESSAGE_FLAG_NONE);
+ switch (result) {
+ case core.RESULT_OK:
+ // The handles were successfully transferred, so we don't own them
+ // anymore.
+ message.handles = [];
+ break;
+ case core.RESULT_FAILED_PRECONDITION:
+ // There's no point in continuing to write to this pipe since the other
+ // end is gone. Avoid writing any future messages. Hide write failures
+ // from the caller since we'd like them to continue consuming any
+ // backlog of incoming messages before regarding the message pipe as
+ // closed.
+ this.dropWrites_ = true;
+ break;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
+ }
+ return true;
+ };
+
+ Connector.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Connector.prototype.setErrorHandler = function(handler) {
+ this.errorHandler_ = handler;
+ };
+
+ Connector.prototype.waitForNextMessageForTesting = function() {
+ var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE);
+ this.readMore_(wait.result);
+ };
+
+ Connector.prototype.readMore_ = function(result) {
+ for (;;) {
+ var read = core.readMessage(this.handle_,
+ core.READ_MESSAGE_FLAG_NONE);
+ if (this.handle_ == null) // The connector has been closed.
+ return;
+ if (read.result == core.RESULT_SHOULD_WAIT)
+ return;
+ if (read.result != core.RESULT_OK) {
+ // TODO(wangjimmy): Add a handleError method to swap the handle to be
+ // closed with a dummy handle in the case when
+ // read.result != MOJO_RESULT_FAILED_PRECONDITION
+ this.error_ = true;
+ if (this.errorHandler_)
+ this.errorHandler_.onError();
+ return;
+ }
+ var messageBuffer = new buffer.Buffer(read.buffer);
+ var message = new codec.Message(messageBuffer, read.handles);
+ if (this.incomingReceiver_)
+ this.incomingReceiver_.accept(message);
+ }
+ };
+
+ var exports = {};
+ exports.Connector = Connector;
+ return exports;
+});
diff --git a/mojo/public/js/constants.cc b/mojo/public/js/constants.cc
new file mode 100644
index 0000000000..a0ce7d4d1d
--- /dev/null
+++ b/mojo/public/js/constants.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 "mojo/public/js/constants.h"
+
+namespace mojo {
+
+const char kBindingsModuleName[] = "mojo/public/js/bindings";
+const char kBufferModuleName[] = "mojo/public/js/buffer";
+const char kCodecModuleName[] = "mojo/public/js/codec";
+const char kConnectorModuleName[] = "mojo/public/js/connector";
+const char kControlMessageHandlerModuleName[] =
+ "mojo/public/js/lib/control_message_handler";
+const char kControlMessageProxyModuleName[] =
+ "mojo/public/js/lib/control_message_proxy";
+const char kInterfaceControlMessagesMojom[] =
+ "mojo/public/interfaces/bindings/interface_control_messages.mojom";
+const char kInterfaceEndpointClientModuleName[] =
+ "mojo/public/js/lib/interface_endpoint_client";
+const char kInterfaceEndpointHandleModuleName[] =
+ "mojo/public/js/lib/interface_endpoint_handle";
+const char kInterfaceTypesModuleName[] = "mojo/public/js/interface_types";
+const char kPipeControlMessageHandlerModuleName[] =
+ "mojo/public/js/lib/pipe_control_message_handler";
+const char kPipeControlMessageProxyModuleName[] =
+ "mojo/public/js/lib/pipe_control_message_proxy";
+const char kPipeControlMessagesMojom[] =
+ "mojo/public/interfaces/bindings/pipe_control_messages.mojom";
+const char kRouterModuleName[] = "mojo/public/js/router";
+const char kUnicodeModuleName[] = "mojo/public/js/unicode";
+const char kValidatorModuleName[] = "mojo/public/js/validator";
+} // namespace mojo
diff --git a/mojo/public/js/constants.h b/mojo/public/js/constants.h
new file mode 100644
index 0000000000..f561d739b9
--- /dev/null
+++ b/mojo/public/js/constants.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 MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
+#define MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
+
+namespace mojo {
+
+// JavaScript module names:
+extern const char kBindingsModuleName[];
+extern const char kBufferModuleName[];
+extern const char kCodecModuleName[];
+extern const char kConnectorModuleName[];
+extern const char kControlMessageHandlerModuleName[];
+extern const char kControlMessageProxyModuleName[];
+extern const char kInterfaceControlMessagesMojom[];
+extern const char kInterfaceEndpointClientModuleName[];
+extern const char kInterfaceEndpointHandleModuleName[];
+extern const char kInterfaceTypesModuleName[];
+extern const char kPipeControlMessageHandlerModuleName[];
+extern const char kPipeControlMessageProxyModuleName[];
+extern const char kPipeControlMessagesMojom[];
+extern const char kRouterModuleName[];
+extern const char kUnicodeModuleName[];
+extern const char kValidatorModuleName[];
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
diff --git a/mojo/public/js/core.js b/mojo/public/js/core.js
new file mode 100644
index 0000000000..b2c4ee2733
--- /dev/null
+++ b/mojo/public/js/core.js
@@ -0,0 +1,304 @@
+// Copyright 2014 The Chromium 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 "mojo/public/js/core"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+//
+// This module provides the JavaScript bindings for mojo/public/c/system/core.h.
+// Refer to that file for more detailed documentation for equivalent methods.
+
+while (1);
+
+/**
+ * MojoHandle: An opaque handles to a Mojo object (e.g. a message pipe).
+ */
+var kInvalidHandle;
+
+/**
+ * MojoResult {number}: Result codes for Mojo operations.
+ * See core.h for more information.
+ */
+var RESULT_OK;
+var RESULT_CANCELLED;
+var RESULT_UNKNOWN;
+var RESULT_INVALID_ARGUMENT;
+var RESULT_DEADLINE_EXCEEDED;
+var RESULT_NOT_FOUND;
+var RESULT_ALREADY_EXISTS;
+var RESULT_PERMISSION_DENIED;
+var RESULT_RESOURCE_EXHAUSTED;
+var RESULT_FAILED_PRECONDITION;
+var RESULT_ABORTED;
+var RESULT_OUT_OF_RANGE;
+var RESULT_UNIMPLEMENTED;
+var RESULT_INTERNAL;
+var RESULT_UNAVAILABLE;
+var RESULT_DATA_LOSS;
+var RESULT_BUSY;
+var RESULT_SHOULD_WAIT;
+
+/**
+ * MojoHandleSignals: Used to specify signals that can be waited on for a handle
+ *(and which can be triggered), e.g., the ability to read or write to
+ * the handle.
+ * See core.h for more information.
+ */
+var HANDLE_SIGNAL_NONE;
+var HANDLE_SIGNAL_READABLE;
+var HANDLE_SIGNAL_WRITABLE;
+var HANDLE_SIGNAL_PEER_CLOSED;
+
+/**
+ * MojoCreateDataMessageOptions: Used to specify creation parameters for a data
+ * pipe to |createDataMessage()|.
+ * See core.h for more information.
+ */
+dictionary MojoCreateDataMessageOptions {
+ MojoCreateDataMessageOptionsFlags flags; // See below.
+};
+
+// MojoCreateDataMessageOptionsFlags
+var CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE;
+
+/*
+ * MojoWriteMessageFlags: Used to specify different modes to |writeMessage()|.
+ * See core.h for more information.
+ */
+var WRITE_MESSAGE_FLAG_NONE;
+
+/**
+ * MojoReadMessageFlags: Used to specify different modes to |readMessage()|.
+ * See core.h for more information.
+ */
+var READ_MESSAGE_FLAG_NONE;
+var READ_MESSAGE_FLAG_MAY_DISCARD;
+
+/**
+ * MojoCreateDataPipeOptions: Used to specify creation parameters for a data
+ * pipe to |createDataPipe()|.
+ * See core.h for more information.
+ */
+dictionary MojoCreateDataPipeOptions {
+ MojoCreateDataPipeOptionsFlags flags; // See below.
+ int32 elementNumBytes; // The size of an element, in bytes.
+ int32 capacityNumBytes; // The capacity of the data pipe, in bytes.
+};
+
+// MojoCreateDataPipeOptionsFlags
+var CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+
+/*
+ * MojoWriteDataFlags: Used to specify different modes to |writeData()|.
+ * See core.h for more information.
+ */
+var WRITE_DATA_FLAG_NONE;
+var WRITE_DATA_FLAG_ALL_OR_NONE;
+
+/**
+ * MojoReadDataFlags: Used to specify different modes to |readData()|.
+ * See core.h for more information.
+ */
+var READ_DATA_FLAG_NONE;
+var READ_DATA_FLAG_ALL_OR_NONE;
+var READ_DATA_FLAG_DISCARD;
+var READ_DATA_FLAG_QUERY;
+var READ_DATA_FLAG_PEEK;
+
+/**
+ * MojoCreateSharedBufferOptionsFlags: Used to specify options to
+ * |createSharedBuffer()|.
+ * See core.h for more information.
+ */
+var CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
+
+/**
+ * MojoDuplicateBufferHandleOptionsFlags: Used to specify options to
+ * |duplicateBufferHandle()|.
+ * See core.h for more information.
+ */
+var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE;
+var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
+
+/**
+ * MojoMapBufferFlags: Used to specify options to |mapBuffer()|.
+ * See core.h for more information.
+ */
+var MAP_BUFFER_FLAG_NONE;
+
+/**
+ * Closes the given |handle|. See MojoClose for more info.
+ * @param {MojoHandle} Handle to close.
+ * @return {MojoResult} Result code.
+ */
+function close(handle) { [native code] }
+
+/**
+ * Queries the last known signaling state of |handle|.
+ *
+ * @param {MojoHandle} handle Handle to query.
+ * @return {object} An object of the form {
+ * result, // MOJO_RESULT_OK or MOJO_RESULT_INVALID_ARGUMENT
+ * satisfiedSignals, // MojoHandleSignals (see above)
+ * satisfiableSignals, // MojoHandleSignals
+ * }
+ */
+function queryHandleSignalsState(handle) { [native code] }
+
+/**
+ * Waits on the given handle until a signal indicated by |signals| is
+ * satisfied or an error occurs.
+ *
+ * @param {MojoHandle} handle Handle to wait on.
+ * @param {MojoHandleSignals} signals Specifies the condition to wait for.
+ * @return {MojoResult} Result code.
+ */
+function wait(handle, signals) { [native code] }
+
+/**
+ * Creates a message pipe. This function always succeeds.
+ * See MojoCreateMessagePipe for more information on message pipes.
+ *
+ * @param {MojoCreateMessagePipeOptions} optionsDict Options to control the
+ * message pipe parameters. May be null.
+ * @return {MessagePipe} An object of the form {
+ * handle0,
+ * handle1,
+ * }
+ * where |handle0| and |handle1| are MojoHandles to each end of the channel.
+ */
+function createMessagePipe(optionsDict) { [native code] }
+
+/**
+ * Writes a message to the message pipe endpoint given by |handle|. See
+ * MojoWriteMessage for more information, including return codes.
+ *
+ * @param {MojoHandle} handle The endpoint to write to.
+ * @param {ArrayBufferView} buffer The message data. May be empty.
+ * @param {Array.MojoHandle} handlesArray Any handles to attach. Handles are
+ * transferred on success and will no longer be valid. May be empty.
+ * @param {MojoWriteMessageFlags} flags Flags.
+ * @return {MojoResult} Result code.
+ */
+function writeMessage(handle, buffer, handlesArray, flags) { [native code] }
+
+/**
+ * Reads a message from the message pipe endpoint given by |handle|. See
+ * MojoReadMessage for more information, including return codes.
+ *
+ * @param {MojoHandle} handle The endpoint to read from.
+ * @param {MojoReadMessageFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBufferView of the message data (only on success).
+ * handles // An array of MojoHandles transferred, if any.
+ * }
+ */
+function readMessage(handle, flags) { [native code] }
+
+/**
+ * Creates a data pipe, which is a unidirectional communication channel for
+ * unframed data, with the given options. See MojoCreateDataPipe for more
+ * more information, including return codes.
+ *
+ * @param {MojoCreateDataPipeOptions} optionsDict Options to control the data
+ * pipe parameters. May be null.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * producerHandle, // MojoHandle to use with writeData (only on success).
+ * consumerHandle, // MojoHandle to use with readData (only on success).
+ * }
+ */
+function createDataPipe(optionsDict) { [native code] }
+
+/**
+ * Writes the given data to the data pipe producer given by |handle|. See
+ * MojoWriteData for more information, including return codes.
+ *
+ * @param {MojoHandle} handle A producerHandle returned by createDataPipe.
+ * @param {ArrayBufferView} buffer The data to write.
+ * @param {MojoWriteDataFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * numBytes, // The number of bytes written.
+ * }
+ */
+function writeData(handle, buffer, flags) { [native code] }
+
+/**
+ * Reads data from the data pipe consumer given by |handle|. May also
+ * be used to discard data. See MojoReadData for more information, including
+ * return codes.
+ *
+ * @param {MojoHandle} handle A consumerHandle returned by createDataPipe.
+ * @param {MojoReadDataFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBufferView of the data read (only on success).
+ * }
+ */
+function readData(handle, flags) { [native code] }
+
+/**
+ * True if the argument is a message or data pipe handle.
+ *
+ * @param {value} an arbitrary JS value.
+ * @return true or false
+ */
+function isHandle(value) { [native code] }
+
+/**
+ * Creates shared buffer of specified size |num_bytes|.
+ * See MojoCreateSharedBuffer for more information including error codes.
+ *
+ * @param {number} num_bytes Size of the memory to be allocated for shared
+ * @param {MojoCreateSharedBufferOptionsFlags} flags Flags.
+ * buffer.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * handle, // An MojoHandle for shared buffer (only on success).
+ * }
+ */
+function createSharedBuffer(num_bytes, flags) { [native code] }
+
+/**
+ * Duplicates the |buffer_handle| to a shared buffer. Duplicated handle can be
+ * sent to another process over message pipe. See MojoDuplicateBufferHandle for
+ * more information including error codes.
+ *
+ * @param {MojoHandle} buffer_handle MojoHandle.
+ * @param {MojoCreateSharedBufferOptionsFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * handle, // A duplicated MojoHandle for shared buffer (only on success).
+ * }
+ */
+function duplicateBufferHandle(buffer_handle, flags) { [native code] }
+
+/**
+ * Maps the part (at offset |offset| of length |num_bytes|) of the buffer given
+ * by |buffer_handle| into ArrayBuffer memory |buffer|, with options specified
+ * by |flags|. See MojoMapBuffer for more information including error codes.
+ *
+ * @param {MojoHandle} buffer_handle A sharedBufferHandle returned by
+ * createSharedBuffer.
+ * @param {number} offset Offset.
+ * @param {number} num_bytes Size of the memory to be mapped.
+ * @param {MojoMapBufferFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBuffer (only on success).
+ * }
+ */
+function mapBuffer(buffer_handle, offset, num_bytes, flags) { [native code] }
+
+/**
+ * Unmaps buffer that was mapped using mapBuffer.
+ * See MojoUnmapBuffer for more information including error codes.
+ *
+ * @param {ArrayBuffer} buffer ArrayBuffer.
+ * @return {MojoResult} Result code.
+ */
+function unmapBuffer(buffer) { [native code] }
diff --git a/mojo/public/js/interface_types.js b/mojo/public/js/interface_types.js
new file mode 100644
index 0000000000..e8ed37ae64
--- /dev/null
+++ b/mojo/public/js/interface_types.js
@@ -0,0 +1,70 @@
+// 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.
+
+define("mojo/public/js/interface_types", [
+ "mojo/public/js/core",
+], function(core) {
+
+ // Constants ----------------------------------------------------------------
+ var kInterfaceIdNamespaceMask = 0x80000000;
+ var kMasterInterfaceId = 0x00000000;
+ var kInvalidInterfaceId = 0xFFFFFFFF;
+
+ // ---------------------------------------------------------------------------
+
+ function InterfacePtrInfo(handle, version) {
+ this.handle = handle;
+ this.version = version;
+ }
+
+ InterfacePtrInfo.prototype.isValid = function() {
+ return core.isHandle(this.handle);
+ };
+
+ InterfacePtrInfo.prototype.close = function() {
+ if (!this.isValid())
+ return;
+
+ core.close(this.handle);
+ this.handle = null;
+ this.version = 0;
+ };
+
+ // ---------------------------------------------------------------------------
+
+ function InterfaceRequest(handle) {
+ this.handle = handle;
+ }
+
+ InterfaceRequest.prototype.isValid = function() {
+ return core.isHandle(this.handle);
+ };
+
+ InterfaceRequest.prototype.close = function() {
+ if (!this.isValid())
+ return;
+
+ core.close(this.handle);
+ this.handle = null;
+ };
+
+ function isMasterInterfaceId(interfaceId) {
+ return interfaceId === kMasterInterfaceId;
+ }
+
+ function isValidInterfaceId(interfaceId) {
+ return interfaceId !== kInvalidInterfaceId;
+ }
+
+ var exports = {};
+ exports.InterfacePtrInfo = InterfacePtrInfo;
+ exports.InterfaceRequest = InterfaceRequest;
+ exports.isMasterInterfaceId = isMasterInterfaceId;
+ exports.isValidInterfaceId = isValidInterfaceId;
+ exports.kInvalidInterfaceId = kInvalidInterfaceId;
+ exports.kMasterInterfaceId = kMasterInterfaceId;
+ exports.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask;
+
+ return exports;
+});
diff --git a/mojo/public/js/lib/control_message_handler.js b/mojo/public/js/lib/control_message_handler.js
new file mode 100644
index 0000000000..5da306e371
--- /dev/null
+++ b/mojo/public/js/lib/control_message_handler.js
@@ -0,0 +1,111 @@
+// Copyright 2017 The Chromium 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("mojo/public/js/lib/control_message_handler", [
+ "mojo/public/interfaces/bindings/interface_control_messages.mojom",
+ "mojo/public/js/codec",
+ "mojo/public/js/validator",
+], function(controlMessages, codec, validator) {
+
+ var Validator = validator.Validator;
+
+ function validateControlRequestWithResponse(message) {
+ var messageValidator = new Validator(message);
+ var error = messageValidator.validateMessageIsRequestExpectingResponse();
+ if (error !== validator.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != controlMessages.kRunMessageId) {
+ throw new Error("Control message name is not kRunMessageId");
+ }
+
+ // Validate payload.
+ error = controlMessages.RunMessageParams.validate(messageValidator,
+ message.getHeaderNumBytes());
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function validateControlRequestWithoutResponse(message) {
+ var messageValidator = new Validator(message);
+ var error = messageValidator.validateMessageIsRequestWithoutResponse();
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != controlMessages.kRunOrClosePipeMessageId) {
+ throw new Error("Control message name is not kRunOrClosePipeMessageId");
+ }
+
+ // Validate payload.
+ error = controlMessages.RunOrClosePipeMessageParams.validate(
+ messageValidator, message.getHeaderNumBytes());
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function runOrClosePipe(message, interface_version) {
+ var reader = new codec.MessageReader(message);
+ var runOrClosePipeMessageParams = reader.decodeStruct(
+ controlMessages.RunOrClosePipeMessageParams);
+ return interface_version >=
+ runOrClosePipeMessageParams.input.require_version.version;
+ }
+
+ function run(message, responder, interface_version) {
+ var reader = new codec.MessageReader(message);
+ var runMessageParams =
+ reader.decodeStruct(controlMessages.RunMessageParams);
+ var runOutput = null;
+
+ if (runMessageParams.input.query_version) {
+ runOutput = new controlMessages.RunOutput();
+ runOutput.query_version_result = new
+ controlMessages.QueryVersionResult({'version': interface_version});
+ }
+
+ var runResponseMessageParams = new
+ controlMessages.RunResponseMessageParams();
+ runResponseMessageParams.output = runOutput;
+
+ var messageName = controlMessages.kRunMessageId;
+ var payloadSize = controlMessages.RunResponseMessageParams.encodedSize;
+ var requestID = reader.requestID;
+ var builder = new codec.MessageWithRequestIDBuilder(messageName,
+ payloadSize, codec.kMessageIsResponse, requestID);
+ builder.encodeStruct(controlMessages.RunResponseMessageParams,
+ runResponseMessageParams);
+ responder.accept(builder.finish());
+ return true;
+ }
+
+ function isControlMessage(message) {
+ return message.getName() == controlMessages.kRunMessageId ||
+ message.getName() == controlMessages.kRunOrClosePipeMessageId;
+ }
+
+ function ControlMessageHandler(interface_version) {
+ this.interface_version_ = interface_version;
+ }
+
+ ControlMessageHandler.prototype.accept = function(message) {
+ validateControlRequestWithoutResponse(message);
+ return runOrClosePipe(message, this.interface_version_);
+ };
+
+ ControlMessageHandler.prototype.acceptWithResponder = function(message,
+ responder) {
+ validateControlRequestWithResponse(message);
+ return run(message, responder, this.interface_version_);
+ };
+
+ var exports = {};
+ exports.ControlMessageHandler = ControlMessageHandler;
+ exports.isControlMessage = isControlMessage;
+
+ return exports;
+});
diff --git a/mojo/public/js/lib/control_message_proxy.js b/mojo/public/js/lib/control_message_proxy.js
new file mode 100644
index 0000000000..b6f1d3c83c
--- /dev/null
+++ b/mojo/public/js/lib/control_message_proxy.js
@@ -0,0 +1,104 @@
+// Copyright 2017 The Chromium 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("mojo/public/js/lib/control_message_proxy", [
+ "mojo/public/interfaces/bindings/interface_control_messages.mojom",
+ "mojo/public/js/codec",
+ "mojo/public/js/validator",
+], function(controlMessages, codec, validator) {
+
+ var Validator = validator.Validator;
+
+ function constructRunOrClosePipeMessage(runOrClosePipeInput) {
+ var runOrClosePipeMessageParams = new
+ controlMessages.RunOrClosePipeMessageParams();
+ runOrClosePipeMessageParams.input = runOrClosePipeInput;
+
+ var messageName = controlMessages.kRunOrClosePipeMessageId;
+ var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize;
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams,
+ runOrClosePipeMessageParams);
+ var message = builder.finish();
+ return message;
+ }
+
+ function validateControlResponse(message) {
+ var messageValidator = new Validator(message);
+ var error = messageValidator.validateMessageIsResponse();
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != controlMessages.kRunMessageId) {
+ throw new Error("Control message name is not kRunMessageId");
+ }
+
+ // Validate payload.
+ error = controlMessages.RunResponseMessageParams.validate(
+ messageValidator, message.getHeaderNumBytes());
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function acceptRunResponse(message) {
+ validateControlResponse(message);
+
+ var reader = new codec.MessageReader(message);
+ var runResponseMessageParams = reader.decodeStruct(
+ controlMessages.RunResponseMessageParams);
+
+ return Promise.resolve(runResponseMessageParams);
+ }
+
+ /**
+ * Sends the given run message through the receiver.
+ * Accepts the response message from the receiver and decodes the message
+ * struct to RunResponseMessageParams.
+ *
+ * @param {Router} receiver.
+ * @param {RunMessageParams} runMessageParams to be sent via a message.
+ * @return {Promise} that resolves to a RunResponseMessageParams.
+ */
+ function sendRunMessage(receiver, runMessageParams) {
+ var messageName = controlMessages.kRunMessageId;
+ var payloadSize = controlMessages.RunMessageParams.encodedSize;
+ // |requestID| is set to 0, but is later properly set by Router.
+ var builder = new codec.MessageWithRequestIDBuilder(messageName,
+ payloadSize, codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(controlMessages.RunMessageParams, runMessageParams);
+ var message = builder.finish();
+
+ return receiver.acceptAndExpectResponse(message).then(acceptRunResponse);
+ }
+
+ function ControlMessageProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+
+ ControlMessageProxy.prototype.queryVersion = function() {
+ var runMessageParams = new controlMessages.RunMessageParams();
+ runMessageParams.input = new controlMessages.RunInput();
+ runMessageParams.input.query_version = new controlMessages.QueryVersion();
+
+ return sendRunMessage(this.receiver_, runMessageParams).then(function(
+ runResponseMessageParams) {
+ return runResponseMessageParams.output.query_version_result.version;
+ });
+ };
+
+ ControlMessageProxy.prototype.requireVersion = function(version) {
+ var runOrClosePipeInput = new controlMessages.RunOrClosePipeInput();
+ runOrClosePipeInput.require_version = new controlMessages.RequireVersion({
+ 'version': version});
+ var message = constructRunOrClosePipeMessage(runOrClosePipeInput);
+ this.receiver_.accept(message);
+ };
+
+ var exports = {};
+ exports.ControlMessageProxy = ControlMessageProxy;
+
+ return exports;
+});
diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js
new file mode 100644
index 0000000000..631c52ec91
--- /dev/null
+++ b/mojo/public/js/lib/interface_endpoint_client.js
@@ -0,0 +1,232 @@
+// Copyright 2017 The Chromium 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("mojo/public/js/lib/interface_endpoint_client", [
+ "console",
+ "mojo/public/js/codec",
+ "mojo/public/js/lib/control_message_handler",
+ "mojo/public/js/lib/control_message_proxy",
+ "mojo/public/js/lib/interface_endpoint_handle",
+ "mojo/public/js/validator",
+ "timer",
+], function(console,
+ codec,
+ controlMessageHandler,
+ controlMessageProxy,
+ interfaceEndpointHandle,
+ validator,
+ timer) {
+
+ var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
+ var ControlMessageProxy = controlMessageProxy.ControlMessageProxy;
+ var MessageReader = codec.MessageReader;
+ var Validator = validator.Validator;
+ var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
+
+ function InterfaceEndpointClient(interfaceEndpointHandle, receiver,
+ interfaceVersion) {
+ this.controller_ = null;
+ this.encounteredError_ = false;
+ this.handle_ = interfaceEndpointHandle;
+ this.incomingReceiver_ = receiver;
+
+ if (interfaceVersion !== undefined) {
+ this.controlMessageHandler_ = new ControlMessageHandler(
+ interfaceVersion);
+ } else {
+ this.controlMessageProxy_ = new ControlMessageProxy(this);
+ }
+
+ this.nextRequestID_ = 0;
+ this.completers_ = new Map();
+ this.payloadValidators_ = [];
+ this.connectionErrorHandler_ = null;
+
+ if (interfaceEndpointHandle.pendingAssociation()) {
+ interfaceEndpointHandle.setAssociationEventHandler(
+ this.onAssociationEvent.bind(this));
+ } else {
+ this.initControllerIfNecessary_();
+ }
+ }
+
+ InterfaceEndpointClient.prototype.initControllerIfNecessary_ = function() {
+ if (this.controller_ || this.handle_.pendingAssociation()) {
+ return;
+ }
+
+ this.controller_ = this.handle_.groupController().attachEndpointClient(
+ this.handle_, this);
+ };
+
+ InterfaceEndpointClient.prototype.onAssociationEvent = function(
+ associationEvent) {
+ if (associationEvent ===
+ InterfaceEndpointHandle.AssociationEvent.ASSOCIATED) {
+ this.initControllerIfNecessary_();
+ } else if (associationEvent ===
+ InterfaceEndpointHandle.AssociationEvent
+ .PEER_CLOSED_BEFORE_ASSOCIATION) {
+ timer.createOneShot(0, this.notifyError.bind(this,
+ this.handle_.disconnectReason()));
+ }
+ };
+
+ InterfaceEndpointClient.prototype.passHandle = function() {
+ if (!this.handle_.isValid()) {
+ return new InterfaceEndpointHandle();
+ }
+
+ // Used to clear the previously set callback.
+ this.handle_.setAssociationEventHandler(undefined);
+
+ if (this.controller_) {
+ this.controller_ = null;
+ this.handle_.groupController().detachEndpointClient(this.handle_);
+ }
+ var handle = this.handle_;
+ this.handle_ = null;
+ return handle;
+ };
+
+ InterfaceEndpointClient.prototype.close = function(reason) {
+ var handle = this.passHandle();
+ handle.reset(reason);
+ };
+
+ InterfaceEndpointClient.prototype.accept = function(message) {
+ if (this.encounteredError_) {
+ return false;
+ }
+
+ this.initControllerIfNecessary_();
+ return this.controller_.sendMessage(message);
+ };
+
+ InterfaceEndpointClient.prototype.acceptAndExpectResponse = function(
+ message) {
+ if (this.encounteredError_) {
+ return Promise.reject();
+ }
+
+ this.initControllerIfNecessary_();
+
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ var requestID = this.nextRequestID_++;
+ if (requestID === 0)
+ requestID = this.nextRequestID_++;
+
+ message.setRequestID(requestID);
+ var result = this.controller_.sendMessage(message);
+ if (!result)
+ return Promise.reject(Error("Connection error"));
+
+ var completer = {};
+ this.completers_.set(requestID, completer);
+ return new Promise(function(resolve, reject) {
+ completer.resolve = resolve;
+ completer.reject = reject;
+ });
+ };
+
+ InterfaceEndpointClient.prototype.setPayloadValidators = function(
+ payloadValidators) {
+ this.payloadValidators_ = payloadValidators;
+ };
+
+ InterfaceEndpointClient.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ InterfaceEndpointClient.prototype.setConnectionErrorHandler = function(
+ handler) {
+ this.connectionErrorHandler_ = handler;
+ };
+
+ InterfaceEndpointClient.prototype.handleIncomingMessage_ = function(
+ message) {
+ var noError = validator.validationError.NONE;
+ var messageValidator = new Validator(message);
+ var err = noError;
+ for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
+ err = this.payloadValidators_[i](messageValidator);
+
+ if (err == noError) {
+ return this.handleValidIncomingMessage_(message);
+ } else {
+ validator.reportValidationError(err);
+ return false;
+ }
+ };
+
+ InterfaceEndpointClient.prototype.handleValidIncomingMessage_ = function(
+ message) {
+ if (validator.isTestingMode()) {
+ return true;
+ }
+
+ if (this.encounteredError_) {
+ return false;
+ }
+
+ var ok = false;
+
+ if (message.expectsResponse()) {
+ if (controlMessageHandler.isControlMessage(message) &&
+ this.controlMessageHandler_) {
+ ok = this.controlMessageHandler_.acceptWithResponder(message, this);
+ } else if (this.incomingReceiver_) {
+ ok = this.incomingReceiver_.acceptWithResponder(message, this);
+ }
+ } else if (message.isResponse()) {
+ var reader = new MessageReader(message);
+ var requestID = reader.requestID;
+ var completer = this.completers_.get(requestID);
+ if (completer) {
+ this.completers_.delete(requestID);
+ completer.resolve(message);
+ ok = true;
+ } else {
+ console.log("Unexpected response with request ID: " + requestID);
+ }
+ } else {
+ if (controlMessageHandler.isControlMessage(message) &&
+ this.controlMessageHandler_) {
+ ok = this.controlMessageHandler_.accept(message);
+ } else if (this.incomingReceiver_) {
+ ok = this.incomingReceiver_.accept(message);
+ }
+ }
+ return ok;
+ };
+
+ InterfaceEndpointClient.prototype.notifyError = function(reason) {
+ if (this.encounteredError_) {
+ return;
+ }
+ this.encounteredError_ = true;
+
+ this.completers_.forEach(function(value) {
+ value.reject();
+ });
+ this.completers_.clear(); // Drop any responders.
+
+ if (this.connectionErrorHandler_) {
+ this.connectionErrorHandler_(reason);
+ }
+ };
+
+ InterfaceEndpointClient.prototype.queryVersion = function() {
+ return this.controlMessageProxy_.queryVersion();
+ };
+
+ InterfaceEndpointClient.prototype.requireVersion = function(version) {
+ this.controlMessageProxy_.requireVersion(version);
+ };
+
+ var exports = {};
+ exports.InterfaceEndpointClient = InterfaceEndpointClient;
+
+ return exports;
+});
diff --git a/mojo/public/js/lib/interface_endpoint_handle.js b/mojo/public/js/lib/interface_endpoint_handle.js
new file mode 100644
index 0000000000..f48b89ba85
--- /dev/null
+++ b/mojo/public/js/lib/interface_endpoint_handle.js
@@ -0,0 +1,158 @@
+// Copyright 2017 The Chromium 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("mojo/public/js/lib/interface_endpoint_handle", [
+ "mojo/public/js/interface_types",
+ "timer",
+], function(types, timer) {
+
+ var AssociationEvent = {
+ // The interface has been associated with a message pipe.
+ ASSOCIATED: 'associated',
+ // The peer of this object has been closed before association.
+ PEER_CLOSED_BEFORE_ASSOCIATION: 'peer_closed_before_association'
+ };
+
+ function State(interfaceId, associatedGroupController) {
+ if (interfaceId === undefined) {
+ interfaceId = types.kInvalidInterfaceId;
+ }
+
+ this.interfaceId = interfaceId;
+ this.associatedGroupController = associatedGroupController;
+ this.pendingAssociation = false;
+ this.disconnectReason = null;
+ this.peerState_ = null;
+ this.associationEventHandler_ = null;
+ }
+
+ State.prototype.initPendingState = function(peer) {
+ this.pendingAssociation = true;
+ this.peerState_ = peer;
+ };
+
+ State.prototype.isValid = function() {
+ return this.pendingAssociation ||
+ types.isValidInterfaceId(this.interfaceId);
+ };
+
+ State.prototype.close = function(disconnectReason) {
+ var cachedGroupController;
+ var cachedPeerState;
+ var cachedId = types.kInvalidInterfaceId;
+
+ if (!this.pendingAssociation) {
+ if (types.isValidInterfaceId(this.interfaceId)) {
+ cachedGroupController = this.associatedGroupController;
+ this.associatedGroupController = null;
+ cachedId = this.interfaceId;
+ this.interfaceId = types.kInvalidInterfaceId;
+ }
+ } else {
+ this.pendingAssociation = false;
+ cachedPeerState = this.peerState_;
+ this.peerState_ = null;
+ }
+
+ if (cachedGroupController) {
+ cachedGroupController.closeEndpointHandle(cachedId,
+ disconnectReason);
+ } else if (cachedPeerState) {
+ cachedPeerState.onPeerClosedBeforeAssociation(disconnectReason);
+ }
+ };
+
+ State.prototype.runAssociationEventHandler = function(associationEvent) {
+ if (this.associationEventHandler_) {
+ var handler = this.associationEventHandler_;
+ this.associationEventHandler_ = null;
+ handler(associationEvent);
+ }
+ };
+
+ State.prototype.setAssociationEventHandler = function(handler) {
+ if (!this.pendingAssociation &&
+ !types.isValidInterfaceId(this.interfaceId)) {
+ return;
+ }
+
+ if (!handler) {
+ this.associationEventHandler_ = null;
+ return;
+ }
+
+ this.associationEventHandler_ = handler;
+ if (!this.pendingAssociation) {
+ timer.createOneShot(0, this.runAssociationEventHandler.bind(this,
+ AssociationEvent.ASSOCIATED));
+ } else if (!this.peerState_) {
+ timer.createOneShot(0, this.runAssociationEventHandler.bind(this,
+ AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION));
+ }
+ };
+
+ State.prototype.onAssociated = function(interfaceId,
+ associatedGroupController) {
+ if (!this.pendingAssociation) {
+ return;
+ }
+
+ this.pendingAssociation = false;
+ this.peerState_ = null;
+ this.interfaceId = interfaceId;
+ this.associatedGroupController = associatedGroupController;
+ this.runAssociationEventHandler(AssociationEvent.ASSOCIATED);
+ };
+
+ State.prototype.onPeerClosedBeforeAssociation = function(disconnectReason) {
+ if (!this.pendingAssociation) {
+ return;
+ }
+
+ this.peerState_ = null;
+ this.disconnectReason = disconnectReason;
+
+ this.runAssociationEventHandler(
+ AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION);
+ };
+
+ function InterfaceEndpointHandle(interfaceId, associatedGroupController) {
+ this.state_ = new State(interfaceId, associatedGroupController);
+ }
+
+ InterfaceEndpointHandle.prototype.isValid = function() {
+ return this.state_.isValid();
+ };
+
+ InterfaceEndpointHandle.prototype.pendingAssociation = function() {
+ return this.state_.pendingAssociation;
+ };
+
+ InterfaceEndpointHandle.prototype.id = function() {
+ return this.state_.interfaceId;
+ };
+
+ InterfaceEndpointHandle.prototype.groupController = function() {
+ return this.state_.associatedGroupController;
+ };
+
+ InterfaceEndpointHandle.prototype.disconnectReason = function() {
+ return this.state_.disconnectReason;
+ };
+
+ InterfaceEndpointHandle.prototype.setAssociationEventHandler = function(
+ handler) {
+ this.state_.setAssociationEventHandler(handler);
+ };
+
+ InterfaceEndpointHandle.prototype.reset = function(reason) {
+ this.state_.close(reason);
+ this.state_ = new State();
+ };
+
+ var exports = {};
+ exports.InterfaceEndpointHandle = InterfaceEndpointHandle;
+
+ return exports;
+});
diff --git a/mojo/public/js/lib/pipe_control_message_handler.js b/mojo/public/js/lib/pipe_control_message_handler.js
new file mode 100644
index 0000000000..2eb45d1626
--- /dev/null
+++ b/mojo/public/js/lib/pipe_control_message_handler.js
@@ -0,0 +1,61 @@
+// Copyright 2017 The Chromium 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("mojo/public/js/lib/pipe_control_message_handler", [
+ "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
+ "mojo/public/js/codec",
+ "mojo/public/js/interface_types",
+ "mojo/public/js/validator",
+], function(pipeControlMessages, codec, types, validator) {
+
+ var Validator = validator.Validator;
+
+ function validateControlRequestWithoutResponse(message) {
+ var messageValidator = new Validator(message);
+ var error = messageValidator.validateMessageIsRequestWithoutResponse();
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != pipeControlMessages.kRunOrClosePipeMessageId) {
+ throw new Error("Control message name is not kRunOrClosePipeMessageId");
+ }
+
+ // Validate payload.
+ error = pipeControlMessages.RunOrClosePipeMessageParams.validate(
+ messageValidator, message.getHeaderNumBytes());
+ if (error != validator.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function runOrClosePipe(message, delegate) {
+ var reader = new codec.MessageReader(message);
+ var runOrClosePipeMessageParams = reader.decodeStruct(
+ pipeControlMessages.RunOrClosePipeMessageParams);
+ var event = runOrClosePipeMessageParams.input
+ .peer_associated_endpoint_closed_event;
+ return delegate.onPeerAssociatedEndpointClosed(event.id,
+ event.disconnect_reason);
+ }
+
+ function isPipeControlMessage(message) {
+ return !types.isValidInterfaceId(message.getInterfaceId());
+ }
+
+ function PipeControlMessageHandler(delegate) {
+ this.delegate_ = delegate;
+ }
+
+ PipeControlMessageHandler.prototype.accept = function(message) {
+ validateControlRequestWithoutResponse(message);
+ return runOrClosePipe(message, this.delegate_);
+ };
+
+ var exports = {};
+ exports.PipeControlMessageHandler = PipeControlMessageHandler;
+ exports.isPipeControlMessage = isPipeControlMessage;
+
+ return exports;
+});
diff --git a/mojo/public/js/lib/pipe_control_message_proxy.js b/mojo/public/js/lib/pipe_control_message_proxy.js
new file mode 100644
index 0000000000..4b8e7a20ea
--- /dev/null
+++ b/mojo/public/js/lib/pipe_control_message_proxy.js
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium 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("mojo/public/js/lib/pipe_control_message_proxy", [
+ "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
+ "mojo/public/js/codec",
+ "mojo/public/js/interface_types",
+], function(pipeControlMessages, codec, types) {
+
+ function constructRunOrClosePipeMessage(runOrClosePipeInput) {
+ var runOrClosePipeMessageParams = new
+ pipeControlMessages.RunOrClosePipeMessageParams();
+ runOrClosePipeMessageParams.input = runOrClosePipeInput;
+
+ var messageName = pipeControlMessages.kRunOrClosePipeMessageId;
+ var payloadSize =
+ pipeControlMessages.RunOrClosePipeMessageParams.encodedSize;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(pipeControlMessages.RunOrClosePipeMessageParams,
+ runOrClosePipeMessageParams);
+ var message = builder.finish();
+ message.setInterfaceId(types.kInvalidInterfaceId);
+ return message;
+ }
+
+ function PipeControlMessageProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+
+ PipeControlMessageProxy.prototype.notifyPeerEndpointClosed = function(
+ interfaceId, reason) {
+ var message = this.constructPeerEndpointClosedMessage(interfaceId, reason);
+ this.receiver_.accept(message);
+ };
+
+ PipeControlMessageProxy.prototype.constructPeerEndpointClosedMessage =
+ function(interfaceId, reason) {
+ var event = new pipeControlMessages.PeerAssociatedEndpointClosedEvent();
+ event.id = interfaceId;
+ if (reason) {
+ event.disconnect_reason = new pipeControlMessages.DisconnectReason({
+ custom_reason: reason.custom_reason,
+ description: reason.description});
+ }
+ var runOrClosePipeInput = new pipeControlMessages.RunOrClosePipeInput();
+ runOrClosePipeInput.peer_associated_endpoint_closed_event = event;
+ return constructRunOrClosePipeMessage(runOrClosePipeInput);
+ };
+
+ var exports = {};
+ exports.PipeControlMessageProxy = PipeControlMessageProxy;
+
+ return exports;
+});
diff --git a/mojo/public/js/new_bindings/base.js b/mojo/public/js/new_bindings/base.js
new file mode 100644
index 0000000000..db72d489db
--- /dev/null
+++ b/mojo/public/js/new_bindings/base.js
@@ -0,0 +1,111 @@
+// Copyright 2017 The Chromium 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 strict';
+
+if (mojo && mojo.internal) {
+ throw new Error('The Mojo bindings library has been initialized.');
+}
+
+var mojo = mojo || {};
+mojo.internal = {};
+mojo.internal.global = this;
+mojo.config = {
+ // Whether to automatically load mojom dependencies.
+ // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to
+ // true means that loading foo.mojom.js will insert a <script> tag to load
+ // bar.mojom.js, if it hasn't been loaded.
+ //
+ // The URL of bar.mojom.js is determined by the relative path of bar.mojom
+ // (relative to the position of foo.mojom at build time) and the URL of
+ // foo.mojom.js. For exmple, if at build time the two mojom files are
+ // located at:
+ // a/b/c/foo.mojom
+ // a/b/d/bar.mojom
+ // and the URL of foo.mojom.js is:
+ // http://example.org/scripts/b/c/foo.mojom.js
+ // then the URL of bar.mojom.js will be:
+ // http://example.org/scripts/b/d/bar.mojom.js
+ //
+ // If you would like bar.mojom.js to live at a different location, you need
+ // to turn off |autoLoadMojomDeps| before loading foo.mojom.js, and manually
+ // load bar.mojom.js yourself. Similarly, you need to turn off the option if
+ // you merge bar.mojom.js and foo.mojom.js into a single file.
+ //
+ // Performance tip: Avoid loading the same mojom.js file multiple times.
+ // Assume that |autoLoadMojomDeps| is set to true:
+ // <!-- No duplicate loading; recommended. -->
+ // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+ //
+ // <!-- No duplicate loading, although unnecessary. -->
+ // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+ // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+ //
+ // <!-- Load bar.mojom.js twice; should be avoided. -->
+ // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+ // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+ autoLoadMojomDeps: true
+};
+
+(function() {
+ var internal = mojo.internal;
+
+ var LoadState = {
+ PENDING_LOAD: 1,
+ LOADED: 2
+ };
+
+ var mojomRegistry = new Map();
+
+ function exposeNamespace(namespace) {
+ var current = internal.global;
+ var parts = namespace.split('.');
+
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!current[part]) {
+ current[part] = {};
+ }
+ current = current[part];
+ }
+
+ return current;
+ }
+
+ function isMojomPendingLoad(id) {
+ return mojomRegistry.get(id) === LoadState.PENDING_LOAD;
+ }
+
+ function isMojomLoaded(id) {
+ return mojomRegistry.get(id) === LoadState.LOADED;
+ }
+
+ function markMojomPendingLoad(id) {
+ if (isMojomLoaded(id)) {
+ throw new Error('The following mojom file has been loaded: ' + id);
+ }
+
+ mojomRegistry.set(id, LoadState.PENDING_LOAD);
+ }
+
+ function markMojomLoaded(id) {
+ mojomRegistry.set(id, LoadState.LOADED);
+ }
+
+ function loadMojomIfNecessary(id, url) {
+ if (mojomRegistry.has(id)) {
+ return;
+ }
+
+ markMojomPendingLoad(id);
+ internal.global.document.write('<script type="text/javascript" src="' +
+ url + '"></script>');
+ }
+
+ internal.exposeNamespace = exposeNamespace;
+ internal.isMojomPendingLoad = isMojomPendingLoad;
+ internal.isMojomLoaded = isMojomLoaded;
+ internal.markMojomPendingLoad = markMojomPendingLoad;
+ internal.markMojomLoaded = markMojomLoaded;
+ internal.loadMojomIfNecessary = loadMojomIfNecessary;
+})();
diff --git a/mojo/public/js/new_bindings/bindings.js b/mojo/public/js/new_bindings/bindings.js
new file mode 100644
index 0000000000..5b3b66ecea
--- /dev/null
+++ b/mojo/public/js/new_bindings/bindings.js
@@ -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.
+
+(function() {
+ var internal = mojo.internal;
+ // ---------------------------------------------------------------------------
+
+ function makeRequest(interfacePtr) {
+ var pipe = Mojo.createMessagePipe();
+ interfacePtr.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0));
+ return new mojo.InterfaceRequest(pipe.handle1);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // Operations used to setup/configure an interface pointer. Exposed as the
+ // |ptr| field of generated interface pointer classes.
+ // |ptrInfoOrHandle| could be omitted and passed into bind() later.
+ function InterfacePtrController(interfaceType, ptrInfoOrHandle) {
+ this.version = 0;
+
+ this.interfaceType_ = interfaceType;
+ this.router_ = null;
+ this.proxy_ = null;
+
+ // |router_| is lazily initialized. |handle_| is valid between bind() and
+ // the initialization of |router_|.
+ this.handle_ = null;
+ this.controlMessageProxy_ = null;
+
+ if (ptrInfoOrHandle)
+ this.bind(ptrInfoOrHandle);
+ }
+
+ InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
+ this.reset();
+
+ if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) {
+ this.version = ptrInfoOrHandle.version;
+ this.handle_ = ptrInfoOrHandle.handle;
+ } else {
+ this.handle_ = ptrInfoOrHandle;
+ }
+ };
+
+ InterfacePtrController.prototype.isBound = function() {
+ return this.router_ !== null || this.handle_ !== null;
+ };
+
+ // Although users could just discard the object, reset() closes the pipe
+ // immediately.
+ InterfacePtrController.prototype.reset = function() {
+ this.version = 0;
+ if (this.router_) {
+ this.router_.close();
+ this.router_ = null;
+
+ this.proxy_ = null;
+ }
+ if (this.handle_) {
+ this.handle_.close();
+ this.handle_ = null;
+ }
+ };
+
+ InterfacePtrController.prototype.setConnectionErrorHandler
+ = function(callback) {
+ if (!this.isBound())
+ throw new Error("Cannot set connection error handler if not bound.");
+
+ this.configureProxyIfNecessary_();
+ this.router_.setErrorHandler(callback);
+ };
+
+ InterfacePtrController.prototype.passInterface = function() {
+ var result;
+ if (this.router_) {
+ // TODO(yzshen): Fix Router interface to support extracting handle.
+ result = new mojo.InterfacePtrInfo(
+ this.router_.connector_.handle_, this.version);
+ this.router_.connector_.handle_ = null;
+ } else {
+ // This also handles the case when this object is not bound.
+ result = new mojo.InterfacePtrInfo(this.handle_, this.version);
+ this.handle_ = null;
+ }
+
+ this.reset();
+ return result;
+ };
+
+ InterfacePtrController.prototype.getProxy = function() {
+ this.configureProxyIfNecessary_();
+ return this.proxy_;
+ };
+
+ InterfacePtrController.prototype.enableTestingMode = function() {
+ this.configureProxyIfNecessary_();
+ return this.router_.enableTestingMode();
+ };
+
+ InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
+ if (!this.handle_)
+ return;
+
+ this.router_ = new internal.Router(this.handle_);
+ this.handle_ = null;
+ this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]);
+
+ this.controlMessageProxy_ = new internal.ControlMessageProxy(this.router_);
+
+ this.proxy_ = new this.interfaceType_.proxyClass(this.router_);
+ };
+
+ InterfacePtrController.prototype.queryVersion = function() {
+ function onQueryVersion(version) {
+ this.version = version;
+ return version;
+ }
+
+ this.configureProxyIfNecessary_();
+ return this.controlMessageProxy_.queryVersion().then(
+ onQueryVersion.bind(this));
+ };
+
+ InterfacePtrController.prototype.requireVersion = function(version) {
+ this.configureProxyIfNecessary_();
+
+ if (this.version >= version) {
+ return;
+ }
+ this.version = version;
+ this.controlMessageProxy_.requireVersion(version);
+ };
+
+ // ---------------------------------------------------------------------------
+
+ // |request| could be omitted and passed into bind() later.
+ //
+ // Example:
+ //
+ // // FooImpl implements mojom.Foo.
+ // function FooImpl() { ... }
+ // FooImpl.prototype.fooMethod1 = function() { ... }
+ // FooImpl.prototype.fooMethod2 = function() { ... }
+ //
+ // var fooPtr = new mojom.FooPtr();
+ // var request = makeRequest(fooPtr);
+ // var binding = new Binding(mojom.Foo, new FooImpl(), request);
+ // fooPtr.fooMethod1();
+ function Binding(interfaceType, impl, requestOrHandle) {
+ this.interfaceType_ = interfaceType;
+ this.impl_ = impl;
+ this.router_ = null;
+ this.stub_ = null;
+
+ if (requestOrHandle)
+ this.bind(requestOrHandle);
+ }
+
+ Binding.prototype.isBound = function() {
+ return this.router_ !== null;
+ };
+
+ Binding.prototype.createInterfacePtrAndBind = function() {
+ var ptr = new this.interfaceType_.ptrClass();
+ // TODO(yzshen): Set the version of the interface pointer.
+ this.bind(makeRequest(ptr));
+ return ptr;
+ }
+
+ Binding.prototype.bind = function(requestOrHandle) {
+ this.close();
+
+ var handle = requestOrHandle instanceof mojo.InterfaceRequest ?
+ requestOrHandle.handle : requestOrHandle;
+ if (!(handle instanceof MojoHandle))
+ return;
+
+ this.stub_ = new this.interfaceType_.stubClass(this.impl_);
+ this.router_ = new internal.Router(handle, this.interfaceType_.kVersion);
+ this.router_.setIncomingReceiver(this.stub_);
+ this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]);
+ };
+
+ Binding.prototype.close = function() {
+ if (!this.isBound())
+ return;
+
+ this.router_.close();
+ this.router_ = null;
+ this.stub_ = null;
+ };
+
+ Binding.prototype.setConnectionErrorHandler
+ = function(callback) {
+ if (!this.isBound())
+ throw new Error("Cannot set connection error handler if not bound.");
+ this.router_.setErrorHandler(callback);
+ };
+
+ Binding.prototype.unbind = function() {
+ if (!this.isBound())
+ return new mojo.InterfaceRequest(null);
+
+ var result = new mojo.InterfaceRequest(this.router_.connector_.handle_);
+ this.router_.connector_.handle_ = null;
+ this.close();
+ return result;
+ };
+
+ Binding.prototype.enableTestingMode = function() {
+ return this.router_.enableTestingMode();
+ };
+
+ // ---------------------------------------------------------------------------
+
+ function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle,
+ bindingId) {
+ this.bindingSet_ = bindingSet;
+ this.bindingId_ = bindingId;
+ this.binding_ = new Binding(interfaceType, impl, requestOrHandle);
+
+ this.binding_.setConnectionErrorHandler(function() {
+ this.bindingSet_.onConnectionError(bindingId);
+ }.bind(this));
+ }
+
+ BindingSetEntry.prototype.close = function() {
+ this.binding_.close();
+ };
+
+ function BindingSet(interfaceType) {
+ this.interfaceType_ = interfaceType;
+ this.nextBindingId_ = 0;
+ this.bindings_ = new Map();
+ this.errorHandler_ = null;
+ }
+
+ BindingSet.prototype.isEmpty = function() {
+ return this.bindings_.size == 0;
+ };
+
+ BindingSet.prototype.addBinding = function(impl, requestOrHandle) {
+ this.bindings_.set(
+ this.nextBindingId_,
+ new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle,
+ this.nextBindingId_));
+ ++this.nextBindingId_;
+ };
+
+ BindingSet.prototype.closeAllBindings = function() {
+ for (var entry of this.bindings_.values())
+ entry.close();
+ this.bindings_.clear();
+ };
+
+ BindingSet.prototype.setConnectionErrorHandler = function(callback) {
+ this.errorHandler_ = callback;
+ };
+
+ BindingSet.prototype.onConnectionError = function(bindingId) {
+ this.bindings_.delete(bindingId);
+
+ if (this.errorHandler_)
+ this.errorHandler_();
+ };
+
+
+ mojo.makeRequest = makeRequest;
+ mojo.Binding = Binding;
+ mojo.BindingSet = BindingSet;
+ mojo.InterfacePtrController = InterfacePtrController;
+})();
diff --git a/mojo/public/js/new_bindings/buffer.js b/mojo/public/js/new_bindings/buffer.js
new file mode 100644
index 0000000000..c44058bd0f
--- /dev/null
+++ b/mojo/public/js/new_bindings/buffer.js
@@ -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.
+
+(function() {
+ var internal = mojo.internal;
+
+ var kHostIsLittleEndian = (function () {
+ var endianArrayBuffer = new ArrayBuffer(2);
+ var endianUint8Array = new Uint8Array(endianArrayBuffer);
+ var endianUint16Array = new Uint16Array(endianArrayBuffer);
+ endianUint16Array[0] = 1;
+ return endianUint8Array[0] == 1;
+ })();
+
+ var kHighWordMultiplier = 0x100000000;
+
+ function Buffer(sizeOrArrayBuffer) {
+ if (sizeOrArrayBuffer instanceof ArrayBuffer)
+ this.arrayBuffer = sizeOrArrayBuffer;
+ else
+ this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer);
+
+ this.dataView = new DataView(this.arrayBuffer);
+ this.next = 0;
+ }
+
+ Object.defineProperty(Buffer.prototype, "byteLength", {
+ get: function() { return this.arrayBuffer.byteLength; }
+ });
+
+ Buffer.prototype.alloc = function(size) {
+ var pointer = this.next;
+ this.next += size;
+ if (this.next > this.byteLength) {
+ var newSize = (1.5 * (this.byteLength + size)) | 0;
+ this.grow(newSize);
+ }
+ return pointer;
+ };
+
+ function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) {
+ (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer));
+ }
+
+ Buffer.prototype.grow = function(size) {
+ var newArrayBuffer = new ArrayBuffer(size);
+ copyArrayBuffer(newArrayBuffer, this.arrayBuffer);
+ this.arrayBuffer = newArrayBuffer;
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.trim = function() {
+ this.arrayBuffer = this.arrayBuffer.slice(0, this.next);
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.getUint8 = function(offset) {
+ return this.dataView.getUint8(offset);
+ }
+ Buffer.prototype.getUint16 = function(offset) {
+ return this.dataView.getUint16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint32 = function(offset) {
+ return this.dataView.getUint32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getInt8 = function(offset) {
+ return this.dataView.getInt8(offset);
+ }
+ Buffer.prototype.getInt16 = function(offset) {
+ return this.dataView.getInt16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt32 = function(offset) {
+ return this.dataView.getInt32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getInt32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getFloat32 = function(offset) {
+ return this.dataView.getFloat32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getFloat64 = function(offset) {
+ return this.dataView.getFloat64(offset, kHostIsLittleEndian);
+ }
+
+ Buffer.prototype.setUint8 = function(offset, value) {
+ this.dataView.setUint8(offset, value);
+ }
+ Buffer.prototype.setUint16 = function(offset, value) {
+ this.dataView.setUint16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint32 = function(offset, value) {
+ this.dataView.setUint32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint64 = function(offset, value) {
+ var hi = (value / kHighWordMultiplier) | 0;
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setInt8 = function(offset, value) {
+ this.dataView.setInt8(offset, value);
+ }
+ Buffer.prototype.setInt16 = function(offset, value) {
+ this.dataView.setInt16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt32 = function(offset, value) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt64 = function(offset, value) {
+ var hi = Math.floor(value / kHighWordMultiplier);
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setFloat32 = function(offset, value) {
+ this.dataView.setFloat32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setFloat64 = function(offset, value) {
+ this.dataView.setFloat64(offset, value, kHostIsLittleEndian);
+ }
+
+ internal.Buffer = Buffer;
+})();
diff --git a/mojo/public/js/new_bindings/codec.js b/mojo/public/js/new_bindings/codec.js
new file mode 100644
index 0000000000..339fc169da
--- /dev/null
+++ b/mojo/public/js/new_bindings/codec.js
@@ -0,0 +1,917 @@
+// Copyright 2014 The Chromium 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() {
+ var internal = mojo.internal;
+
+ var kErrorUnsigned = "Passing negative value to unsigned";
+ var kErrorArray = "Passing non Array for array type";
+ var kErrorString = "Passing non String for string type";
+ var kErrorMap = "Passing non Map for map type";
+
+ // Memory -------------------------------------------------------------------
+
+ var kAlignment = 8;
+
+ function align(size) {
+ return size + (kAlignment - (size % kAlignment)) % kAlignment;
+ }
+
+ function isAligned(offset) {
+ return offset >= 0 && (offset % kAlignment) === 0;
+ }
+
+ // Constants ----------------------------------------------------------------
+
+ var kArrayHeaderSize = 8;
+ var kStructHeaderSize = 8;
+ var kMessageHeaderSize = 24;
+ var kMessageWithRequestIDHeaderSize = 32;
+ var kMapStructPayloadSize = 16;
+
+ var kStructHeaderNumBytesOffset = 0;
+ var kStructHeaderVersionOffset = 4;
+
+ var kEncodedInvalidHandleValue = 0xFFFFFFFF;
+
+ // Decoder ------------------------------------------------------------------
+
+ function Decoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Decoder.prototype.align = function() {
+ this.next = align(this.next);
+ };
+
+ Decoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Decoder.prototype.readInt8 = function() {
+ var result = this.buffer.getInt8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readUint8 = function() {
+ var result = this.buffer.getUint8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readInt16 = function() {
+ var result = this.buffer.getInt16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readUint16 = function() {
+ var result = this.buffer.getUint16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readInt32 = function() {
+ var result = this.buffer.getInt32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readUint32 = function() {
+ var result = this.buffer.getUint32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readInt64 = function() {
+ var result = this.buffer.getInt64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readUint64 = function() {
+ var result = this.buffer.getUint64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readFloat = function() {
+ var result = this.buffer.getFloat32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readDouble = function() {
+ var result = this.buffer.getFloat64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.decodePointer = function() {
+ // TODO(abarth): To correctly decode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offsetPointer = this.next;
+ var offset = this.readUint64();
+ if (!offset)
+ return 0;
+ return offsetPointer + offset;
+ };
+
+ Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
+ return new Decoder(this.buffer, this.handles, pointer);
+ };
+
+ Decoder.prototype.decodeHandle = function() {
+ return this.handles[this.readUint32()] || null;
+ };
+
+ Decoder.prototype.decodeString = function() {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var base = this.next;
+ this.next += numberOfElements;
+ return internal.decodeUtf8String(
+ new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
+ };
+
+ Decoder.prototype.decodeArray = function(cls) {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var val = new Array(numberOfElements);
+ if (cls === PackedBool) {
+ var byte;
+ for (var i = 0; i < numberOfElements; ++i) {
+ if (i % 8 === 0)
+ byte = this.readUint8();
+ val[i] = (byte & (1 << i % 8)) ? true : false;
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i) {
+ val[i] = cls.decode(this);
+ }
+ }
+ return val;
+ };
+
+ Decoder.prototype.decodeStruct = function(cls) {
+ return cls.decode(this);
+ };
+
+ Decoder.prototype.decodeStructPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return cls.decode(this.decodeAndCreateDecoder(pointer));
+ };
+
+ Decoder.prototype.decodeArrayPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
+ };
+
+ Decoder.prototype.decodeStringPointer = function() {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeString();
+ };
+
+ Decoder.prototype.decodeMap = function(keyClass, valueClass) {
+ this.skip(4); // numberOfBytes
+ this.skip(4); // version
+ var keys = this.decodeArrayPointer(keyClass);
+ var values = this.decodeArrayPointer(valueClass);
+ var val = new Map();
+ for (var i = 0; i < keys.length; i++)
+ val.set(keys[i], values[i]);
+ return val;
+ };
+
+ Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ var decoder = this.decodeAndCreateDecoder(pointer);
+ return decoder.decodeMap(keyClass, valueClass);
+ };
+
+ // Encoder ------------------------------------------------------------------
+
+ function Encoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Encoder.prototype.align = function() {
+ this.next = align(this.next);
+ };
+
+ Encoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Encoder.prototype.writeInt8 = function(val) {
+ this.buffer.setInt8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeUint8 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeInt16 = function(val) {
+ this.buffer.setInt16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeUint16 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeInt32 = function(val) {
+ this.buffer.setInt32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeUint32 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeInt64 = function(val) {
+ this.buffer.setInt64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeUint64 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeFloat = function(val) {
+ this.buffer.setFloat32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeDouble = function(val) {
+ this.buffer.setFloat64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.encodePointer = function(pointer) {
+ if (!pointer)
+ return this.writeUint64(0);
+ // TODO(abarth): To correctly encode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offset = pointer - this.next;
+ this.writeUint64(offset);
+ };
+
+ Encoder.prototype.createAndEncodeEncoder = function(size) {
+ var pointer = this.buffer.alloc(align(size));
+ this.encodePointer(pointer);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ Encoder.prototype.encodeHandle = function(handle) {
+ if (handle) {
+ this.handles.push(handle);
+ this.writeUint32(this.handles.length - 1);
+ } else {
+ this.writeUint32(kEncodedInvalidHandleValue);
+ }
+ };
+
+ Encoder.prototype.encodeString = function(val) {
+ var base = this.next + kArrayHeaderSize;
+ var numberOfElements = internal.encodeUtf8String(
+ val, new Uint8Array(this.buffer.arrayBuffer, base));
+ var numberOfBytes = kArrayHeaderSize + numberOfElements;
+ this.writeUint32(numberOfBytes);
+ this.writeUint32(numberOfElements);
+ this.next += numberOfElements;
+ };
+
+ Encoder.prototype.encodeArray =
+ function(cls, val, numberOfElements, encodedSize) {
+ if (numberOfElements === undefined)
+ numberOfElements = val.length;
+ if (encodedSize === undefined)
+ encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
+
+ this.writeUint32(encodedSize);
+ this.writeUint32(numberOfElements);
+
+ if (cls === PackedBool) {
+ var byte = 0;
+ for (i = 0; i < numberOfElements; ++i) {
+ if (val[i])
+ byte |= (1 << i % 8);
+ if (i % 8 === 7 || i == numberOfElements - 1) {
+ Uint8.encode(this, byte);
+ byte = 0;
+ }
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i)
+ cls.encode(this, val[i]);
+ }
+ };
+
+ Encoder.prototype.encodeStruct = function(cls, val) {
+ return cls.encode(this, val);
+ };
+
+ Encoder.prototype.encodeStructPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encoder = this.createAndEncodeEncoder(cls.encodedSize);
+ cls.encode(encoder, val);
+ };
+
+ Encoder.prototype.encodeArrayPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+
+ var numberOfElements = val.length;
+ if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0)
+ throw new Error(kErrorArray);
+
+ var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
+ Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeArray(cls, val, numberOfElements, encodedSize);
+ };
+
+ Encoder.prototype.encodeStringPointer = function(val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ // Only accepts string primivites, not String Objects like new String("foo")
+ if (typeof(val) !== "string") {
+ throw new Error(kErrorString);
+ }
+ var encodedSize = kArrayHeaderSize + internal.utf8Length(val);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeString(val);
+ };
+
+ Encoder.prototype.encodeMap = function(keyClass, valueClass, val) {
+ var keys = new Array(val.size);
+ var values = new Array(val.size);
+ var i = 0;
+ val.forEach(function(value, key) {
+ values[i] = value;
+ keys[i++] = key;
+ });
+ this.writeUint32(kStructHeaderSize + kMapStructPayloadSize);
+ this.writeUint32(0); // version
+ this.encodeArrayPointer(keyClass, keys);
+ this.encodeArrayPointer(valueClass, values);
+ }
+
+ Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ if (!(val instanceof Map)) {
+ throw new Error(kErrorMap);
+ }
+ var encodedSize = kStructHeaderSize + kMapStructPayloadSize;
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeMap(keyClass, valueClass, val);
+ };
+
+ // Message ------------------------------------------------------------------
+
+ var kMessageInterfaceIdOffset = kStructHeaderSize;
+ var kMessageNameOffset = kMessageInterfaceIdOffset + 4;
+ var kMessageFlagsOffset = kMessageNameOffset + 4;
+ var kMessageRequestIDOffset = kMessageFlagsOffset + 8;
+
+ var kMessageExpectsResponse = 1 << 0;
+ var kMessageIsResponse = 1 << 1;
+
+ function Message(buffer, handles) {
+ this.buffer = buffer;
+ this.handles = handles;
+ }
+
+ Message.prototype.getHeaderNumBytes = function() {
+ return this.buffer.getUint32(kStructHeaderNumBytesOffset);
+ };
+
+ Message.prototype.getHeaderVersion = function() {
+ return this.buffer.getUint32(kStructHeaderVersionOffset);
+ };
+
+ Message.prototype.getName = function() {
+ return this.buffer.getUint32(kMessageNameOffset);
+ };
+
+ Message.prototype.getFlags = function() {
+ return this.buffer.getUint32(kMessageFlagsOffset);
+ };
+
+ Message.prototype.isResponse = function() {
+ return (this.getFlags() & kMessageIsResponse) != 0;
+ };
+
+ Message.prototype.expectsResponse = function() {
+ return (this.getFlags() & kMessageExpectsResponse) != 0;
+ };
+
+ Message.prototype.setRequestID = function(requestID) {
+ // TODO(darin): Verify that space was reserved for this field!
+ this.buffer.setUint64(kMessageRequestIDOffset, requestID);
+ };
+
+
+ // MessageBuilder -----------------------------------------------------------
+
+ function MessageBuilder(messageName, payloadSize) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageHeaderSize + payloadSize;
+ this.buffer = new internal.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageHeaderSize);
+ encoder.writeUint32(kMessageHeaderSize);
+ encoder.writeUint32(0); // version.
+ encoder.writeUint32(0); // interface ID.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(0); // flags.
+ encoder.writeUint32(0); // padding.
+ }
+
+ MessageBuilder.prototype.createEncoder = function(size) {
+ var pointer = this.buffer.alloc(size);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ MessageBuilder.prototype.encodeStruct = function(cls, val) {
+ cls.encode(this.createEncoder(cls.encodedSize), val);
+ };
+
+ MessageBuilder.prototype.finish = function() {
+ // TODO(abarth): Rather than resizing the buffer at the end, we could
+ // compute the size we need ahead of time, like we do in C++.
+ this.buffer.trim();
+ var message = new Message(this.buffer, this.handles);
+ this.buffer = null;
+ this.handles = null;
+ this.encoder = null;
+ return message;
+ };
+
+ // MessageWithRequestIDBuilder -----------------------------------------------
+
+ function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+ requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
+ this.buffer = new internal.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(1); // version.
+ encoder.writeUint32(0); // interface ID.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(flags);
+ encoder.writeUint32(0); // padding.
+ encoder.writeUint64(requestID);
+ }
+
+ MessageWithRequestIDBuilder.prototype =
+ Object.create(MessageBuilder.prototype);
+
+ MessageWithRequestIDBuilder.prototype.constructor =
+ MessageWithRequestIDBuilder;
+
+ // MessageReader ------------------------------------------------------------
+
+ function MessageReader(message) {
+ this.decoder = new Decoder(message.buffer, message.handles, 0);
+ var messageHeaderSize = this.decoder.readUint32();
+ this.payloadSize = message.buffer.byteLength - messageHeaderSize;
+ var version = this.decoder.readUint32();
+ var interface_id = this.decoder.readUint32();
+ if (interface_id != 0) {
+ throw new Error("Receiving non-zero interface ID. Associated interfaces " +
+ "are not yet supported.");
+ }
+ this.messageName = this.decoder.readUint32();
+ this.flags = this.decoder.readUint32();
+ // Skip the padding.
+ this.decoder.skip(4);
+ if (version >= 1)
+ this.requestID = this.decoder.readUint64();
+ this.decoder.skip(messageHeaderSize - this.decoder.next);
+ }
+
+ MessageReader.prototype.decodeStruct = function(cls) {
+ return cls.decode(this.decoder);
+ };
+
+ // Built-in types -----------------------------------------------------------
+
+ // This type is only used with ArrayOf(PackedBool).
+ function PackedBool() {
+ }
+
+ function Int8() {
+ }
+
+ Int8.encodedSize = 1;
+
+ Int8.decode = function(decoder) {
+ return decoder.readInt8();
+ };
+
+ Int8.encode = function(encoder, val) {
+ encoder.writeInt8(val);
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Uint8() {
+ }
+
+ Uint8.encodedSize = 1;
+
+ Uint8.decode = function(decoder) {
+ return decoder.readUint8();
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Int16() {
+ }
+
+ Int16.encodedSize = 2;
+
+ Int16.decode = function(decoder) {
+ return decoder.readInt16();
+ };
+
+ Int16.encode = function(encoder, val) {
+ encoder.writeInt16(val);
+ };
+
+ function Uint16() {
+ }
+
+ Uint16.encodedSize = 2;
+
+ Uint16.decode = function(decoder) {
+ return decoder.readUint16();
+ };
+
+ Uint16.encode = function(encoder, val) {
+ encoder.writeUint16(val);
+ };
+
+ function Int32() {
+ }
+
+ Int32.encodedSize = 4;
+
+ Int32.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Int32.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function Uint32() {
+ }
+
+ Uint32.encodedSize = 4;
+
+ Uint32.decode = function(decoder) {
+ return decoder.readUint32();
+ };
+
+ Uint32.encode = function(encoder, val) {
+ encoder.writeUint32(val);
+ };
+
+ function Int64() {
+ }
+
+ Int64.encodedSize = 8;
+
+ Int64.decode = function(decoder) {
+ return decoder.readInt64();
+ };
+
+ Int64.encode = function(encoder, val) {
+ encoder.writeInt64(val);
+ };
+
+ function Uint64() {
+ }
+
+ Uint64.encodedSize = 8;
+
+ Uint64.decode = function(decoder) {
+ return decoder.readUint64();
+ };
+
+ Uint64.encode = function(encoder, val) {
+ encoder.writeUint64(val);
+ };
+
+ function String() {
+ };
+
+ String.encodedSize = 8;
+
+ String.decode = function(decoder) {
+ return decoder.decodeStringPointer();
+ };
+
+ String.encode = function(encoder, val) {
+ encoder.encodeStringPointer(val);
+ };
+
+ function NullableString() {
+ }
+
+ NullableString.encodedSize = String.encodedSize;
+
+ NullableString.decode = String.decode;
+
+ NullableString.encode = String.encode;
+
+ function Float() {
+ }
+
+ Float.encodedSize = 4;
+
+ Float.decode = function(decoder) {
+ return decoder.readFloat();
+ };
+
+ Float.encode = function(encoder, val) {
+ encoder.writeFloat(val);
+ };
+
+ function Double() {
+ }
+
+ Double.encodedSize = 8;
+
+ Double.decode = function(decoder) {
+ return decoder.readDouble();
+ };
+
+ Double.encode = function(encoder, val) {
+ encoder.writeDouble(val);
+ };
+
+ function Enum(cls) {
+ this.cls = cls;
+ }
+
+ Enum.prototype.encodedSize = 4;
+
+ Enum.prototype.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Enum.prototype.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function PointerTo(cls) {
+ this.cls = cls;
+ }
+
+ PointerTo.prototype.encodedSize = 8;
+
+ PointerTo.prototype.decode = function(decoder) {
+ var pointer = decoder.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
+ };
+
+ PointerTo.prototype.encode = function(encoder, val) {
+ if (!val) {
+ encoder.encodePointer(val);
+ return;
+ }
+ var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
+ this.cls.encode(objectEncoder, val);
+ };
+
+ function NullablePointerTo(cls) {
+ PointerTo.call(this, cls);
+ }
+
+ NullablePointerTo.prototype = Object.create(PointerTo.prototype);
+
+ function ArrayOf(cls, length) {
+ this.cls = cls;
+ this.length = length || 0;
+ }
+
+ ArrayOf.prototype.encodedSize = 8;
+
+ ArrayOf.prototype.dimensions = function() {
+ return [this.length].concat(
+ (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []);
+ }
+
+ ArrayOf.prototype.decode = function(decoder) {
+ return decoder.decodeArrayPointer(this.cls);
+ };
+
+ ArrayOf.prototype.encode = function(encoder, val) {
+ encoder.encodeArrayPointer(this.cls, val);
+ };
+
+ function NullableArrayOf(cls) {
+ ArrayOf.call(this, cls);
+ }
+
+ NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
+
+ function Handle() {
+ }
+
+ Handle.encodedSize = 4;
+
+ Handle.decode = function(decoder) {
+ return decoder.decodeHandle();
+ };
+
+ Handle.encode = function(encoder, val) {
+ encoder.encodeHandle(val);
+ };
+
+ function NullableHandle() {
+ }
+
+ NullableHandle.encodedSize = Handle.encodedSize;
+
+ NullableHandle.decode = Handle.decode;
+
+ NullableHandle.encode = Handle.encode;
+
+ function Interface(cls) {
+ this.cls = cls;
+ }
+
+ Interface.prototype.encodedSize = 8;
+
+ Interface.prototype.decode = function(decoder) {
+ var interfacePtrInfo = new mojo.InterfacePtrInfo(
+ decoder.decodeHandle(), decoder.readUint32());
+ var interfacePtr = new this.cls();
+ interfacePtr.ptr.bind(interfacePtrInfo);
+ return interfacePtr;
+ };
+
+ Interface.prototype.encode = function(encoder, val) {
+ var interfacePtrInfo =
+ val ? val.ptr.passInterface() : new mojo.InterfacePtrInfo(null, 0);
+ encoder.encodeHandle(interfacePtrInfo.handle);
+ encoder.writeUint32(interfacePtrInfo.version);
+ };
+
+ function NullableInterface(cls) {
+ Interface.call(this, cls);
+ }
+
+ NullableInterface.prototype = Object.create(Interface.prototype);
+
+ function InterfaceRequest() {
+ }
+
+ InterfaceRequest.encodedSize = 4;
+
+ InterfaceRequest.decode = function(decoder) {
+ return new mojo.InterfaceRequest(decoder.decodeHandle());
+ };
+
+ InterfaceRequest.encode = function(encoder, val) {
+ encoder.encodeHandle(val ? val.handle : null);
+ };
+
+ function NullableInterfaceRequest() {
+ }
+
+ NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize;
+
+ NullableInterfaceRequest.decode = InterfaceRequest.decode;
+
+ NullableInterfaceRequest.encode = InterfaceRequest.encode;
+
+ function MapOf(keyClass, valueClass) {
+ this.keyClass = keyClass;
+ this.valueClass = valueClass;
+ }
+
+ MapOf.prototype.encodedSize = 8;
+
+ MapOf.prototype.decode = function(decoder) {
+ return decoder.decodeMapPointer(this.keyClass, this.valueClass);
+ };
+
+ MapOf.prototype.encode = function(encoder, val) {
+ encoder.encodeMapPointer(this.keyClass, this.valueClass, val);
+ };
+
+ function NullableMapOf(keyClass, valueClass) {
+ MapOf.call(this, keyClass, valueClass);
+ }
+
+ NullableMapOf.prototype = Object.create(MapOf.prototype);
+
+ internal.align = align;
+ internal.isAligned = isAligned;
+ internal.Message = Message;
+ internal.MessageBuilder = MessageBuilder;
+ internal.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+ internal.MessageReader = MessageReader;
+ internal.kArrayHeaderSize = kArrayHeaderSize;
+ internal.kMapStructPayloadSize = kMapStructPayloadSize;
+ internal.kStructHeaderSize = kStructHeaderSize;
+ internal.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
+ internal.kMessageHeaderSize = kMessageHeaderSize;
+ internal.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
+ internal.kMessageExpectsResponse = kMessageExpectsResponse;
+ internal.kMessageIsResponse = kMessageIsResponse;
+ internal.Int8 = Int8;
+ internal.Uint8 = Uint8;
+ internal.Int16 = Int16;
+ internal.Uint16 = Uint16;
+ internal.Int32 = Int32;
+ internal.Uint32 = Uint32;
+ internal.Int64 = Int64;
+ internal.Uint64 = Uint64;
+ internal.Float = Float;
+ internal.Double = Double;
+ internal.String = String;
+ internal.Enum = Enum;
+ internal.NullableString = NullableString;
+ internal.PointerTo = PointerTo;
+ internal.NullablePointerTo = NullablePointerTo;
+ internal.ArrayOf = ArrayOf;
+ internal.NullableArrayOf = NullableArrayOf;
+ internal.PackedBool = PackedBool;
+ internal.Handle = Handle;
+ internal.NullableHandle = NullableHandle;
+ internal.Interface = Interface;
+ internal.NullableInterface = NullableInterface;
+ internal.InterfaceRequest = InterfaceRequest;
+ internal.NullableInterfaceRequest = NullableInterfaceRequest;
+ internal.MapOf = MapOf;
+ internal.NullableMapOf = NullableMapOf;
+})();
diff --git a/mojo/public/js/new_bindings/connector.js b/mojo/public/js/new_bindings/connector.js
new file mode 100644
index 0000000000..7fa4822f89
--- /dev/null
+++ b/mojo/public/js/new_bindings/connector.js
@@ -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.
+
+(function() {
+ var internal = mojo.internal;
+
+ function Connector(handle) {
+ if (!(handle instanceof MojoHandle))
+ throw new Error("Connector: not a handle " + handle);
+ this.handle_ = handle;
+ this.dropWrites_ = false;
+ this.error_ = false;
+ this.incomingReceiver_ = null;
+ this.readWatcher_ = null;
+ this.errorHandler_ = null;
+
+ if (handle) {
+ this.readWatcher_ = handle.watch({readable: true},
+ this.readMore_.bind(this));
+ }
+ }
+
+ Connector.prototype.close = function() {
+ if (this.readWatcher_) {
+ this.readWatcher_.cancel();
+ this.readWatcher_ = null;
+ }
+ if (this.handle_ != null) {
+ this.handle_.close();
+ this.handle_ = null;
+ }
+ };
+
+ Connector.prototype.accept = function(message) {
+ if (this.error_)
+ return false;
+
+ if (this.dropWrites_)
+ return true;
+
+ var result = this.handle_.writeMessage(
+ new Uint8Array(message.buffer.arrayBuffer), message.handles);
+ switch (result) {
+ case Mojo.RESULT_OK:
+ // The handles were successfully transferred, so we don't own them
+ // anymore.
+ message.handles = [];
+ break;
+ case Mojo.RESULT_FAILED_PRECONDITION:
+ // There's no point in continuing to write to this pipe since the other
+ // end is gone. Avoid writing any future messages. Hide write failures
+ // from the caller since we'd like them to continue consuming any
+ // backlog of incoming messages before regarding the message pipe as
+ // closed.
+ this.dropWrites_ = true;
+ break;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
+ }
+ return true;
+ };
+
+ Connector.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Connector.prototype.setErrorHandler = function(handler) {
+ this.errorHandler_ = handler;
+ };
+
+ Connector.prototype.encounteredError = function() {
+ return this.error_;
+ };
+
+ Connector.prototype.waitForNextMessageForTesting = function() {
+ // TODO(yzshen): Change the tests that use this method.
+ throw new Error("Not supported!");
+ };
+
+ Connector.prototype.readMore_ = function(result) {
+ for (;;) {
+ var read = this.handle_.readMessage();
+ if (this.handle_ == null) // The connector has been closed.
+ return;
+ if (read.result == Mojo.RESULT_SHOULD_WAIT)
+ return;
+ if (read.result != Mojo.RESULT_OK) {
+ this.error_ = true;
+ if (this.errorHandler_)
+ this.errorHandler_.onError(read.result);
+ return;
+ }
+ var messageBuffer = new internal.Buffer(read.buffer);
+ var message = new internal.Message(messageBuffer, read.handles);
+ if (this.incomingReceiver_)
+ this.incomingReceiver_.accept(message);
+ }
+ };
+
+ internal.Connector = Connector;
+})();
diff --git a/mojo/public/js/new_bindings/interface_types.js b/mojo/public/js/new_bindings/interface_types.js
new file mode 100644
index 0000000000..c52f6c7e55
--- /dev/null
+++ b/mojo/public/js/new_bindings/interface_types.js
@@ -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.
+
+(function() {
+ // ---------------------------------------------------------------------------
+
+ function InterfacePtrInfo(handle, version) {
+ this.handle = handle;
+ this.version = version;
+ }
+
+ InterfacePtrInfo.prototype.isValid = function() {
+ return this.handle instanceof MojoHandle;
+ };
+
+ InterfacePtrInfo.prototype.close = function() {
+ if (!this.isValid())
+ return;
+
+ this.handle.close();
+ this.handle = null;
+ this.version = 0;
+ };
+
+ // ---------------------------------------------------------------------------
+
+ function InterfaceRequest(handle) {
+ this.handle = handle;
+ }
+
+ InterfaceRequest.prototype.isValid = function() {
+ return this.handle instanceof MojoHandle;
+ };
+
+ InterfaceRequest.prototype.close = function() {
+ if (!this.isValid())
+ return;
+
+ this.handle.close();
+ this.handle = null;
+ };
+
+ mojo.InterfacePtrInfo = InterfacePtrInfo;
+ mojo.InterfaceRequest = InterfaceRequest;
+})();
diff --git a/mojo/public/js/new_bindings/lib/control_message_handler.js b/mojo/public/js/new_bindings/lib/control_message_handler.js
new file mode 100644
index 0000000000..3f122fb379
--- /dev/null
+++ b/mojo/public/js/new_bindings/lib/control_message_handler.js
@@ -0,0 +1,106 @@
+// Copyright 2017 The Chromium 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() {
+ var internal = mojo.internal;
+
+ function validateControlRequestWithResponse(message) {
+ var messageValidator = new internal.Validator(message);
+ var error = messageValidator.validateMessageIsRequestExpectingResponse();
+ if (error !== internal.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != mojo.interface_control2.kRunMessageId) {
+ throw new Error("Control message name is not kRunMessageId");
+ }
+
+ // Validate payload.
+ error = mojo.interface_control2.RunMessageParams.validate(messageValidator,
+ message.getHeaderNumBytes());
+ if (error != internal.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function validateControlRequestWithoutResponse(message) {
+ var messageValidator = new internal.Validator(message);
+ var error = messageValidator.validateMessageIsRequestWithoutResponse();
+ if (error != internal.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != mojo.interface_control2.kRunOrClosePipeMessageId) {
+ throw new Error("Control message name is not kRunOrClosePipeMessageId");
+ }
+
+ // Validate payload.
+ error = mojo.interface_control2.RunOrClosePipeMessageParams.validate(
+ messageValidator, message.getHeaderNumBytes());
+ if (error != internal.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function runOrClosePipe(message, interface_version) {
+ var reader = new internal.MessageReader(message);
+ var runOrClosePipeMessageParams = reader.decodeStruct(
+ mojo.interface_control2.RunOrClosePipeMessageParams);
+ return interface_version >=
+ runOrClosePipeMessageParams.input.require_version.version;
+ }
+
+ function run(message, responder, interface_version) {
+ var reader = new internal.MessageReader(message);
+ var runMessageParams =
+ reader.decodeStruct(mojo.interface_control2.RunMessageParams);
+ var runOutput = null;
+
+ if (runMessageParams.input.query_version) {
+ runOutput = new mojo.interface_control2.RunOutput();
+ runOutput.query_version_result = new
+ mojo.interface_control2.QueryVersionResult(
+ {'version': interface_version});
+ }
+
+ var runResponseMessageParams = new
+ mojo.interface_control2.RunResponseMessageParams();
+ runResponseMessageParams.output = runOutput;
+
+ var messageName = mojo.interface_control2.kRunMessageId;
+ var payloadSize =
+ mojo.interface_control2.RunResponseMessageParams.encodedSize;
+ var requestID = reader.requestID;
+ var builder = new internal.MessageWithRequestIDBuilder(messageName,
+ payloadSize, internal.kMessageIsResponse, requestID);
+ builder.encodeStruct(mojo.interface_control2.RunResponseMessageParams,
+ runResponseMessageParams);
+ responder.accept(builder.finish());
+ return true;
+ }
+
+ function isInterfaceControlMessage(message) {
+ return message.getName() == mojo.interface_control2.kRunMessageId ||
+ message.getName() ==
+ mojo.interface_control2.kRunOrClosePipeMessageId;
+ }
+
+ function ControlMessageHandler(interface_version) {
+ this.interface_version = interface_version;
+ }
+
+ ControlMessageHandler.prototype.accept = function(message) {
+ validateControlRequestWithoutResponse(message);
+ return runOrClosePipe(message, this.interface_version);
+ };
+
+ ControlMessageHandler.prototype.acceptWithResponder = function(message,
+ responder) {
+ validateControlRequestWithResponse(message);
+ return run(message, responder, this.interface_version);
+ };
+
+ internal.ControlMessageHandler = ControlMessageHandler;
+ internal.isInterfaceControlMessage = isInterfaceControlMessage;
+})();
diff --git a/mojo/public/js/new_bindings/lib/control_message_proxy.js b/mojo/public/js/new_bindings/lib/control_message_proxy.js
new file mode 100644
index 0000000000..1d57557ae2
--- /dev/null
+++ b/mojo/public/js/new_bindings/lib/control_message_proxy.js
@@ -0,0 +1,97 @@
+// Copyright 2017 The Chromium 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() {
+ var internal = mojo.internal;
+
+ function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) {
+ var messageName = mojo.interface_control2.kRunOrClosePipeMessageId;
+ var payloadSize =
+ mojo.interface_control2.RunOrClosePipeMessageParams.encodedSize;
+ var builder = new internal.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(mojo.interface_control2.RunOrClosePipeMessageParams,
+ runOrClosePipeMessageParams);
+ var message = builder.finish();
+ receiver.accept(message);
+ }
+
+ function validateControlResponse(message) {
+ var messageValidator = new internal.Validator(message);
+ var error = messageValidator.validateMessageIsResponse();
+ if (error != internal.validationError.NONE) {
+ throw error;
+ }
+
+ if (message.getName() != mojo.interface_control2.kRunMessageId) {
+ throw new Error("Control message name is not kRunMessageId");
+ }
+
+ // Validate payload.
+ error = mojo.interface_control2.RunResponseMessageParams.validate(
+ messageValidator, message.getHeaderNumBytes());
+ if (error != internal.validationError.NONE) {
+ throw error;
+ }
+ }
+
+ function acceptRunResponse(message) {
+ validateControlResponse(message);
+
+ var reader = new internal.MessageReader(message);
+ var runResponseMessageParams = reader.decodeStruct(
+ mojo.interface_control2.RunResponseMessageParams);
+
+ return Promise.resolve(runResponseMessageParams);
+ }
+
+ /**
+ * Sends the given run message through the receiver.
+ * Accepts the response message from the receiver and decodes the message
+ * struct to RunResponseMessageParams.
+ *
+ * @param {Router} receiver.
+ * @param {RunMessageParams} runMessageParams to be sent via a message.
+ * @return {Promise} that resolves to a RunResponseMessageParams.
+ */
+ function sendRunMessage(receiver, runMessageParams) {
+ var messageName = mojo.interface_control2.kRunMessageId;
+ var payloadSize = mojo.interface_control2.RunMessageParams.encodedSize;
+ // |requestID| is set to 0, but is later properly set by Router.
+ var builder = new internal.MessageWithRequestIDBuilder(messageName,
+ payloadSize, internal.kMessageExpectsResponse, 0);
+ builder.encodeStruct(mojo.interface_control2.RunMessageParams,
+ runMessageParams);
+ var message = builder.finish();
+
+ return receiver.acceptAndExpectResponse(message).then(acceptRunResponse);
+ }
+
+ function ControlMessageProxy(receiver) {
+ this.receiver = receiver;
+ }
+
+ ControlMessageProxy.prototype.queryVersion = function() {
+ var runMessageParams = new mojo.interface_control2.RunMessageParams();
+ runMessageParams.input = new mojo.interface_control2.RunInput();
+ runMessageParams.input.query_version =
+ new mojo.interface_control2.QueryVersion();
+
+ return sendRunMessage(this.receiver, runMessageParams).then(function(
+ runResponseMessageParams) {
+ return runResponseMessageParams.output.query_version_result.version;
+ });
+ };
+
+ ControlMessageProxy.prototype.requireVersion = function(version) {
+ var runOrClosePipeMessageParams = new
+ mojo.interface_control2.RunOrClosePipeMessageParams();
+ runOrClosePipeMessageParams.input = new
+ mojo.interface_control2.RunOrClosePipeInput();
+ runOrClosePipeMessageParams.input.require_version = new
+ mojo.interface_control2.RequireVersion({'version': version});
+ sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams);
+ };
+
+ internal.ControlMessageProxy = ControlMessageProxy;
+})();
diff --git a/mojo/public/js/new_bindings/router.js b/mojo/public/js/new_bindings/router.js
new file mode 100644
index 0000000000..1272407c1e
--- /dev/null
+++ b/mojo/public/js/new_bindings/router.js
@@ -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.
+
+(function() {
+ var internal = mojo.internal;
+
+ function Router(handle, interface_version, connectorFactory) {
+ if (!(handle instanceof MojoHandle))
+ throw new Error("Router constructor: Not a handle");
+ if (connectorFactory === undefined)
+ connectorFactory = internal.Connector;
+ this.connector_ = new connectorFactory(handle);
+ this.incomingReceiver_ = null;
+ this.errorHandler_ = null;
+ this.nextRequestID_ = 0;
+ this.completers_ = new Map();
+ this.payloadValidators_ = [];
+ this.testingController_ = null;
+
+ if (interface_version !== undefined) {
+ this.controlMessageHandler_ = new
+ internal.ControlMessageHandler(interface_version);
+ }
+
+ this.connector_.setIncomingReceiver({
+ accept: this.handleIncomingMessage_.bind(this),
+ });
+ this.connector_.setErrorHandler({
+ onError: this.handleConnectionError_.bind(this),
+ });
+ }
+
+ Router.prototype.close = function() {
+ this.completers_.clear(); // Drop any responders.
+ this.connector_.close();
+ this.testingController_ = null;
+ };
+
+ Router.prototype.accept = function(message) {
+ this.connector_.accept(message);
+ };
+
+ Router.prototype.reject = function(message) {
+ // TODO(mpcomplete): no way to trasmit errors over a Connection.
+ };
+
+ Router.prototype.acceptAndExpectResponse = function(message) {
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ var requestID = this.nextRequestID_++;
+ if (requestID == 0)
+ requestID = this.nextRequestID_++;
+
+ message.setRequestID(requestID);
+ var result = this.connector_.accept(message);
+ if (!result)
+ return Promise.reject(Error("Connection error"));
+
+ var completer = {};
+ this.completers_.set(requestID, completer);
+ return new Promise(function(resolve, reject) {
+ completer.resolve = resolve;
+ completer.reject = reject;
+ });
+ };
+
+ Router.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Router.prototype.setPayloadValidators = function(payloadValidators) {
+ this.payloadValidators_ = payloadValidators;
+ };
+
+ Router.prototype.setErrorHandler = function(handler) {
+ this.errorHandler_ = handler;
+ };
+
+ Router.prototype.encounteredError = function() {
+ return this.connector_.encounteredError();
+ };
+
+ Router.prototype.enableTestingMode = function() {
+ this.testingController_ = new RouterTestingController(this.connector_);
+ return this.testingController_;
+ };
+
+ Router.prototype.handleIncomingMessage_ = function(message) {
+ var noError = internal.validationError.NONE;
+ var messageValidator = new internal.Validator(message);
+ var err = messageValidator.validateMessageHeader();
+ for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
+ err = this.payloadValidators_[i](messageValidator);
+
+ if (err == noError)
+ this.handleValidIncomingMessage_(message);
+ else
+ this.handleInvalidIncomingMessage_(message, err);
+ };
+
+ Router.prototype.handleValidIncomingMessage_ = function(message) {
+ if (this.testingController_)
+ return;
+
+ if (message.expectsResponse()) {
+ if (internal.isInterfaceControlMessage(message)) {
+ if (this.controlMessageHandler_) {
+ this.controlMessageHandler_.acceptWithResponder(message, this);
+ } else {
+ this.close();
+ }
+ } else if (this.incomingReceiver_) {
+ this.incomingReceiver_.acceptWithResponder(message, this);
+ } else {
+ // If we receive a request expecting a response when the client is not
+ // listening, then we have no choice but to tear down the pipe.
+ this.close();
+ }
+ } else if (message.isResponse()) {
+ var reader = new internal.MessageReader(message);
+ var requestID = reader.requestID;
+ var completer = this.completers_.get(requestID);
+ if (completer) {
+ this.completers_.delete(requestID);
+ completer.resolve(message);
+ } else {
+ console.log("Unexpected response with request ID: " + requestID);
+ }
+ } else {
+ if (internal.isInterfaceControlMessage(message)) {
+ if (this.controlMessageHandler_) {
+ var ok = this.controlMessageHandler_.accept(message);
+ if (ok) return;
+ }
+ this.close();
+ } else if (this.incomingReceiver_) {
+ this.incomingReceiver_.accept(message);
+ }
+ }
+ };
+
+ Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
+ if (!this.testingController_) {
+ // TODO(yzshen): Consider notifying the embedder.
+ // TODO(yzshen): This should also trigger connection error handler.
+ // Consider making accept() return a boolean and let the connector deal
+ // with this, as the C++ code does.
+ console.log("Invalid message: " + internal.validationError[error]);
+
+ this.close();
+ return;
+ }
+
+ this.testingController_.onInvalidIncomingMessage(error);
+ };
+
+ Router.prototype.handleConnectionError_ = function(result) {
+ this.completers_.forEach(function(value) {
+ value.reject(result);
+ });
+ if (this.errorHandler_)
+ this.errorHandler_();
+ this.close();
+ };
+
+ // The RouterTestingController is used in unit tests. It defeats valid message
+ // handling and delgates invalid message handling.
+
+ function RouterTestingController(connector) {
+ this.connector_ = connector;
+ this.invalidMessageHandler_ = null;
+ }
+
+ RouterTestingController.prototype.waitForNextMessage = function() {
+ this.connector_.waitForNextMessageForTesting();
+ };
+
+ RouterTestingController.prototype.setInvalidIncomingMessageHandler =
+ function(callback) {
+ this.invalidMessageHandler_ = callback;
+ };
+
+ RouterTestingController.prototype.onInvalidIncomingMessage =
+ function(error) {
+ if (this.invalidMessageHandler_)
+ this.invalidMessageHandler_(error);
+ };
+
+ internal.Router = Router;
+})();
diff --git a/mojo/public/js/new_bindings/unicode.js b/mojo/public/js/new_bindings/unicode.js
new file mode 100644
index 0000000000..6ed8839c3f
--- /dev/null
+++ b/mojo/public/js/new_bindings/unicode.js
@@ -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.
+
+/**
+ * Defines functions for translating between JavaScript strings and UTF8 strings
+ * stored in ArrayBuffers. There is much room for optimization in this code if
+ * it proves necessary.
+ */
+(function() {
+ var internal = mojo.internal;
+
+ /**
+ * Decodes the UTF8 string from the given buffer.
+ * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
+ * @return {string} The corresponding JavaScript string.
+ */
+ function decodeUtf8String(buffer) {
+ return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer)));
+ }
+
+ /**
+ * Encodes the given JavaScript string into UTF8.
+ * @param {string} str The string to encode.
+ * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
+ * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
+ * how much space is required.
+ * @return {number} The number of bytes written to |outputBuffer|.
+ */
+ function encodeUtf8String(str, outputBuffer) {
+ var utf8String = unescape(encodeURIComponent(str));
+ if (outputBuffer.length < utf8String.length)
+ throw new Error("Buffer too small for encodeUtf8String");
+ for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++)
+ outputBuffer[i] = utf8String.charCodeAt(i);
+ return i;
+ }
+
+ /**
+ * Returns the number of bytes that a UTF8 encoding of the JavaScript string
+ * |str| would occupy.
+ */
+ function utf8Length(str) {
+ var utf8String = unescape(encodeURIComponent(str));
+ return utf8String.length;
+ }
+
+ internal.decodeUtf8String = decodeUtf8String;
+ internal.encodeUtf8String = encodeUtf8String;
+ internal.utf8Length = utf8Length;
+})();
diff --git a/mojo/public/js/new_bindings/validator.js b/mojo/public/js/new_bindings/validator.js
new file mode 100644
index 0000000000..610112b58e
--- /dev/null
+++ b/mojo/public/js/new_bindings/validator.js
@@ -0,0 +1,511 @@
+// Copyright 2014 The Chromium 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() {
+ var internal = mojo.internal;
+
+ var validationError = {
+ NONE: 'VALIDATION_ERROR_NONE',
+ MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
+ ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
+ UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
+ UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
+ ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
+ UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
+ ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
+ UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
+ MESSAGE_HEADER_INVALID_FLAGS:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS',
+ MESSAGE_HEADER_MISSING_REQUEST_ID:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
+ DIFFERENT_SIZED_ARRAYS_IN_MAP:
+ 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
+ INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
+ UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
+ UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
+ };
+
+ var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
+
+ function isEnumClass(cls) {
+ return cls instanceof internal.Enum;
+ }
+
+ function isStringClass(cls) {
+ return cls === internal.String || cls === internal.NullableString;
+ }
+
+ function isHandleClass(cls) {
+ return cls === internal.Handle || cls === internal.NullableHandle;
+ }
+
+ function isInterfaceClass(cls) {
+ return cls instanceof internal.Interface;
+ }
+
+ function isInterfaceRequestClass(cls) {
+ return cls === internal.InterfaceRequest ||
+ cls === internal.NullableInterfaceRequest;
+ }
+
+ function isNullable(type) {
+ return type === internal.NullableString ||
+ type === internal.NullableHandle ||
+ type === internal.NullableInterface ||
+ type === internal.NullableInterfaceRequest ||
+ type instanceof internal.NullableArrayOf ||
+ type instanceof internal.NullablePointerTo;
+ }
+
+ function Validator(message) {
+ this.message = message;
+ this.offset = 0;
+ this.handleIndex = 0;
+ }
+
+ Object.defineProperty(Validator.prototype, "offsetLimit", {
+ get: function() { return this.message.buffer.byteLength; }
+ });
+
+ Object.defineProperty(Validator.prototype, "handleIndexLimit", {
+ get: function() { return this.message.handles.length; }
+ });
+
+ // True if we can safely allocate a block of bytes from start to
+ // to start + numBytes.
+ Validator.prototype.isValidRange = function(start, numBytes) {
+ // Only positive JavaScript integers that are less than 2^53
+ // (Number.MAX_SAFE_INTEGER) can be represented exactly.
+ if (start < this.offset || numBytes <= 0 ||
+ !Number.isSafeInteger(start) ||
+ !Number.isSafeInteger(numBytes))
+ return false;
+
+ var newOffset = start + numBytes;
+ if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
+ return false;
+
+ return true;
+ };
+
+ Validator.prototype.claimRange = function(start, numBytes) {
+ if (this.isValidRange(start, numBytes)) {
+ this.offset = start + numBytes;
+ return true;
+ }
+ return false;
+ };
+
+ Validator.prototype.claimHandle = function(index) {
+ if (index === internal.kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < this.handleIndex || index >= this.handleIndexLimit)
+ return false;
+
+ // This is safe because handle indices are uint32.
+ this.handleIndex = index + 1;
+ return true;
+ };
+
+ Validator.prototype.validateEnum = function(offset, enumClass) {
+ // Note: Assumes that enums are always 32 bits! But this matches
+ // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
+ var value = this.message.buffer.getInt32(offset);
+ return enumClass.validate(value);
+ }
+
+ Validator.prototype.validateHandle = function(offset, nullable) {
+ var index = this.message.buffer.getUint32(offset);
+
+ if (index === internal.kEncodedInvalidHandleValue)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
+
+ if (!this.claimHandle(index))
+ return validationError.ILLEGAL_HANDLE;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterface = function(offset, nullable) {
+ return this.validateHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
+ return this.validateHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
+ if (!internal.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, internal.kStructHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+
+ if (numBytes < minNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStructVersion = function(offset, versionSizes) {
+ var numBytes = this.message.buffer.getUint32(offset);
+ var version = this.message.buffer.getUint32(offset + 4);
+
+ if (version <= versionSizes[versionSizes.length - 1].version) {
+ // Scan in reverse order to optimize for more recent versionSizes.
+ for (var i = versionSizes.length - 1; i >= 0; --i) {
+ if (version >= versionSizes[i].version) {
+ if (numBytes == versionSizes[i].numBytes)
+ break;
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+ }
+ } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
+ var structVersion = this.message.buffer.getUint32(offset + 4);
+ return fieldVersion <= structVersion;
+ };
+
+ Validator.prototype.validateMessageHeader = function() {
+
+ var err = this.validateStructHeader(0, internal.kMessageHeaderSize);
+ if (err != validationError.NONE)
+ return err;
+
+ var numBytes = this.message.getHeaderNumBytes();
+ var version = this.message.getHeaderVersion();
+
+ var validVersionAndNumBytes =
+ (version == 0 && numBytes == internal.kMessageHeaderSize) ||
+ (version == 1 &&
+ numBytes == internal.kMessageWithRequestIDHeaderSize) ||
+ (version > 1 &&
+ numBytes >= internal.kMessageWithRequestIDHeaderSize);
+ if (!validVersionAndNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ var expectsResponse = this.message.expectsResponse();
+ var isResponse = this.message.isResponse();
+
+ if (version == 0 && (expectsResponse || isResponse))
+ return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
+
+ if (isResponse && expectsResponse)
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
+ if (this.message.isResponse() || this.message.expectsResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
+ if (this.message.isResponse() || !this.message.expectsResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsResponse = function() {
+ if (this.message.expectsResponse() || !this.message.isResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ // Returns the message.buffer relative offset this pointer "points to",
+ // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
+ // pointer's value is not valid.
+ Validator.prototype.decodePointer = function(offset) {
+ var pointerValue = this.message.buffer.getUint64(offset);
+ if (pointerValue === 0)
+ return NULL_MOJO_POINTER;
+ var bufferOffset = offset + pointerValue;
+ return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
+ };
+
+ Validator.prototype.decodeUnionSize = function(offset) {
+ return this.message.buffer.getUint32(offset);
+ };
+
+ Validator.prototype.decodeUnionTag = function(offset) {
+ return this.message.buffer.getUint32(offset + 4);
+ };
+
+ Validator.prototype.validateArrayPointer = function(
+ offset, elementSize, elementType, nullable, expectedDimensionSizes,
+ currentDimension) {
+ var arrayOffset = this.decodePointer(offset);
+ if (arrayOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (arrayOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return this.validateArray(arrayOffset, elementSize, elementType,
+ expectedDimensionSizes, currentDimension);
+ };
+
+ Validator.prototype.validateStructPointer = function(
+ offset, structClass, nullable) {
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return structClass.validate(this, structOffset);
+ };
+
+ Validator.prototype.validateUnion = function(
+ offset, unionClass, nullable) {
+ var size = this.message.buffer.getUint32(offset);
+ if (size == 0) {
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
+ }
+
+ return unionClass.validate(this, offset);
+ };
+
+ Validator.prototype.validateNestedUnion = function(
+ offset, unionClass, nullable) {
+ var unionOffset = this.decodePointer(offset);
+ if (unionOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (unionOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
+
+ return this.validateUnion(unionOffset, unionClass, nullable);
+ };
+
+ // This method assumes that the array at arrayPointerOffset has
+ // been validated.
+
+ Validator.prototype.arrayLength = function(arrayPointerOffset) {
+ var arrayOffset = this.decodePointer(arrayPointerOffset);
+ return this.message.buffer.getUint32(arrayOffset + 4);
+ };
+
+ Validator.prototype.validateMapPointer = function(
+ offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
+ // Validate the implicit map struct:
+ // struct {array<keyClass> keys; array<valueClass> values};
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return mapIsNullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ var mapEncodedSize = internal.kStructHeaderSize +
+ internal.kMapStructPayloadSize;
+ var err = this.validateStructHeader(structOffset, mapEncodedSize);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the keys array.
+ var keysArrayPointerOffset = structOffset + internal.kStructHeaderSize;
+ err = this.validateArrayPointer(
+ keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the values array.
+ var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
+ var valuesArrayDimensions = [0]; // Validate the actual length below.
+ if (valueClass instanceof internal.ArrayOf)
+ valuesArrayDimensions =
+ valuesArrayDimensions.concat(valueClass.dimensions());
+ var err = this.validateArrayPointer(valuesArrayPointerOffset,
+ valueClass.encodedSize,
+ valueClass,
+ valueIsNullable,
+ valuesArrayDimensions,
+ 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the lengths of the keys and values arrays.
+ var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
+ var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
+ if (keysArrayLength != valuesArrayLength)
+ return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStringPointer = function(offset, nullable) {
+ return this.validateArrayPointer(
+ offset, internal.Uint8.encodedSize, internal.Uint8, nullable, [0], 0);
+ };
+
+ // Similar to Array_Data<T>::Validate()
+ // mojo/public/cpp/bindings/lib/array_internal.h
+
+ Validator.prototype.validateArray =
+ function (offset, elementSize, elementType, expectedDimensionSizes,
+ currentDimension) {
+ if (!internal.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, internal.kArrayHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numElements = this.message.buffer.getUint32(offset + 4);
+
+ // Note: this computation is "safe" because elementSize <= 8 and
+ // numElements is a uint32.
+ var elementsTotalSize = (elementType === internal.PackedBool) ?
+ Math.ceil(numElements / 8) : (elementSize * numElements);
+
+ if (numBytes < internal.kArrayHeaderSize + elementsTotalSize)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (expectedDimensionSizes[currentDimension] != 0 &&
+ numElements != expectedDimensionSizes[currentDimension]) {
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+ }
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ // Validate the array's elements if they are pointers or handles.
+
+ var elementsOffset = offset + internal.kArrayHeaderSize;
+ var nullable = isNullable(elementType);
+
+ if (isHandleClass(elementType))
+ return this.validateHandleElements(elementsOffset, numElements, nullable);
+ if (isInterfaceClass(elementType))
+ return this.validateInterfaceElements(
+ elementsOffset, numElements, nullable);
+ if (isInterfaceRequestClass(elementType))
+ return this.validateInterfaceRequestElements(
+ elementsOffset, numElements, nullable);
+ if (isStringClass(elementType))
+ return this.validateArrayElements(
+ elementsOffset, numElements, internal.Uint8, nullable, [0], 0);
+ if (elementType instanceof internal.PointerTo)
+ return this.validateStructElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+ if (elementType instanceof internal.ArrayOf)
+ return this.validateArrayElements(
+ elementsOffset, numElements, elementType.cls, nullable,
+ expectedDimensionSizes, currentDimension + 1);
+ if (isEnumClass(elementType))
+ return this.validateEnumElements(elementsOffset, numElements,
+ elementType.cls);
+
+ return validationError.NONE;
+ };
+
+ // Note: the |offset + i * elementSize| computation in the validateFooElements
+ // methods below is "safe" because elementSize <= 8, offset and
+ // numElements are uint32, and 0 <= i < numElements.
+
+ Validator.prototype.validateHandleElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.Handle.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateHandle(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterfaceElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.Interface.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateInterface(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterfaceRequestElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.InterfaceRequest.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateInterfaceRequest(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ // The elementClass parameter is the element type of the element arrays.
+ Validator.prototype.validateArrayElements =
+ function(offset, numElements, elementClass, nullable,
+ expectedDimensionSizes, currentDimension) {
+ var elementSize = internal.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateArrayPointer(
+ elementOffset, elementClass.encodedSize, elementClass, nullable,
+ expectedDimensionSizes, currentDimension);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStructElements =
+ function(offset, numElements, structClass, nullable) {
+ var elementSize = internal.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err =
+ this.validateStructPointer(elementOffset, structClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateEnumElements =
+ function(offset, numElements, enumClass) {
+ var elementSize = internal.Enum.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateEnum(elementOffset, enumClass);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ internal.validationError = validationError;
+ internal.Validator = Validator;
+})();
diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js
new file mode 100644
index 0000000000..89d9a2f66b
--- /dev/null
+++ b/mojo/public/js/router.js
@@ -0,0 +1,269 @@
+// Copyright 2014 The Chromium 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("mojo/public/js/router", [
+ "mojo/public/js/connector",
+ "mojo/public/js/core",
+ "mojo/public/js/interface_types",
+ "mojo/public/js/lib/interface_endpoint_handle",
+ "mojo/public/js/lib/pipe_control_message_handler",
+ "mojo/public/js/lib/pipe_control_message_proxy",
+ "mojo/public/js/validator",
+ "timer",
+], function(connector, core, types, interfaceEndpointHandle,
+ controlMessageHandler, controlMessageProxy, validator, timer) {
+
+ var Connector = connector.Connector;
+ var PipeControlMessageHandler =
+ controlMessageHandler.PipeControlMessageHandler;
+ var PipeControlMessageProxy = controlMessageProxy.PipeControlMessageProxy;
+ var Validator = validator.Validator;
+ var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
+
+ /**
+ * The state of |endpoint|. If both the endpoint and its peer have been
+ * closed, removes it from |endpoints_|.
+ * @enum {string}
+ */
+ var EndpointStateUpdateType = {
+ ENDPOINT_CLOSED: 'endpoint_closed',
+ PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed'
+ };
+
+ function check(condition, output) {
+ if (!condition) {
+ // testharness.js does not rethrow errors so the error stack needs to be
+ // included as a string in the error we throw for debugging layout tests.
+ throw new Error((new Error()).stack);
+ }
+ }
+
+ function InterfaceEndpoint(router, interfaceId) {
+ this.router_ = router;
+ this.id = interfaceId;
+ this.closed = false;
+ this.peerClosed = false;
+ this.handleCreated = false;
+ this.disconnectReason = null;
+ this.client = null;
+ }
+
+ InterfaceEndpoint.prototype.sendMessage = function(message) {
+ message.setInterfaceId(this.id);
+ return this.router_.connector_.accept(message);
+ };
+
+ function Router(handle, setInterfaceIdNamespaceBit) {
+ if (!core.isHandle(handle)) {
+ throw new Error("Router constructor: Not a handle");
+ }
+ if (setInterfaceIdNamespaceBit === undefined) {
+ setInterfaceIdNamespaceBit = false;
+ }
+
+ this.connector_ = new Connector(handle);
+
+ this.connector_.setIncomingReceiver({
+ accept: this.accept.bind(this),
+ });
+ this.connector_.setErrorHandler({
+ onError: this.onPipeConnectionError.bind(this),
+ });
+
+ this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit;
+ this.controlMessageHandler_ = new PipeControlMessageHandler(this);
+ this.controlMessageProxy_ = new PipeControlMessageProxy(this.connector_);
+ this.nextInterfaceIdValue = 1;
+ this.encounteredError_ = false;
+ this.endpoints_ = new Map();
+ }
+
+ Router.prototype.attachEndpointClient = function(
+ interfaceEndpointHandle, interfaceEndpointClient) {
+ check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
+ check(interfaceEndpointClient);
+
+ var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
+ check(endpoint);
+ check(!endpoint.client);
+ check(!endpoint.closed);
+ endpoint.client = interfaceEndpointClient;
+
+ if (endpoint.peerClosed) {
+ timer.createOneShot(0,
+ endpoint.client.notifyError.bind(endpoint.client));
+ }
+
+ return endpoint;
+ };
+
+ Router.prototype.detachEndpointClient = function(
+ interfaceEndpointHandle) {
+ check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
+ var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
+ check(endpoint);
+ check(endpoint.client);
+ check(!endpoint.closed);
+
+ endpoint.client = null;
+ };
+
+ Router.prototype.createLocalEndpointHandle = function(
+ interfaceId) {
+ if (!types.isValidInterfaceId(interfaceId)) {
+ return new InterfaceEndpointHandle();
+ }
+
+ var endpoint = this.endpoints_.get(interfaceId);
+
+ if (!endpoint) {
+ endpoint = new InterfaceEndpoint(this, interfaceId);
+ this.endpoints_.set(interfaceId, endpoint);
+
+ check(!endpoint.handleCreated);
+
+ if (this.encounteredError_) {
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+ }
+ } else {
+ // If the endpoint already exist, it is because we have received a
+ // notification that the peer endpoint has closed.
+ check(!endpoint.closed);
+ check(endpoint.peerClosed);
+
+ if (endpoint.handleCreated) {
+ return new InterfaceEndpointHandle();
+ }
+ }
+
+ endpoint.handleCreated = true;
+ return new InterfaceEndpointHandle(interfaceId, this);
+ };
+
+ Router.prototype.accept = function(message) {
+ var messageValidator = new Validator(message);
+ var err = messageValidator.validateMessageHeader();
+
+ var ok = false;
+ if (err !== validator.validationError.NONE) {
+ validator.reportValidationError(err);
+ } else if (controlMessageHandler.isPipeControlMessage(message)) {
+ ok = this.controlMessageHandler_.accept(message);
+ } else {
+ var interfaceId = message.getInterfaceId();
+ var endpoint = this.endpoints_.get(interfaceId);
+ if (!endpoint || endpoint.closed) {
+ return true;
+ }
+
+ if (!endpoint.client) {
+ // We need to wait until a client is attached in order to dispatch
+ // further messages.
+ return false;
+ }
+ ok = endpoint.client.handleIncomingMessage_(message);
+ }
+
+ if (!ok) {
+ this.handleInvalidIncomingMessage_();
+ }
+ return ok;
+ };
+
+ Router.prototype.close = function() {
+ this.connector_.close();
+ // Closing the message pipe won't trigger connection error handler.
+ // Explicitly call onPipeConnectionError() so that associated endpoints
+ // will get notified.
+ this.onPipeConnectionError();
+ };
+
+ Router.prototype.waitForNextMessageForTesting = function() {
+ this.connector_.waitForNextMessageForTesting();
+ };
+
+ Router.prototype.handleInvalidIncomingMessage_ = function(message) {
+ if (!validator.isTestingMode()) {
+ // TODO(yzshen): Consider notifying the embedder.
+ // TODO(yzshen): This should also trigger connection error handler.
+ // Consider making accept() return a boolean and let the connector deal
+ // with this, as the C++ code does.
+ this.close();
+ return;
+ }
+ };
+
+ Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId,
+ reason) {
+ check(!types.isMasterInterfaceId(interfaceId) || reason);
+
+ var endpoint = this.endpoints_.get(interfaceId);
+ if (!endpoint) {
+ endpoint = new InterfaceEndpoint(this, interfaceId);
+ this.endpoints_.set(interfaceId, endpoint);
+ }
+
+ if (reason) {
+ endpoint.disconnectReason = reason;
+ }
+
+ if (!endpoint.peerClosed) {
+ if (endpoint.client) {
+ timer.createOneShot(0,
+ endpoint.client.notifyError.bind(endpoint.client, reason));
+ }
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+ }
+ return true;
+ };
+
+ Router.prototype.onPipeConnectionError = function() {
+ this.encounteredError_ = true;
+
+ for (var endpoint of this.endpoints_.values()) {
+ if (endpoint.client) {
+ timer.createOneShot(0,
+ endpoint.client.notifyError.bind(endpoint.client,
+ endpoint.disconnectReason));
+ }
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+ }
+ };
+
+ Router.prototype.closeEndpointHandle = function(interfaceId, reason) {
+ if (!types.isValidInterfaceId(interfaceId)) {
+ return;
+ }
+ var endpoint = this.endpoints_.get(interfaceId);
+ check(endpoint);
+ check(!endpoint.client);
+ check(!endpoint.closed);
+
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.ENDPOINT_CLOSED);
+
+ if (!types.isMasterInterfaceId(interfaceId) || reason) {
+ this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason);
+ }
+ };
+
+ Router.prototype.updateEndpointStateMayRemove = function(endpoint,
+ endpointStateUpdateType) {
+ if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) {
+ endpoint.closed = true;
+ } else {
+ endpoint.peerClosed = true;
+ }
+ if (endpoint.closed && endpoint.peerClosed) {
+ this.endpoints_.delete(endpoint.id);
+ }
+ };
+
+ var exports = {};
+ exports.Router = Router;
+ return exports;
+});
diff --git a/mojo/public/js/support.js b/mojo/public/js/support.js
new file mode 100644
index 0000000000..7e27504fbe
--- /dev/null
+++ b/mojo/public/js/support.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.
+
+// Module "mojo/public/js/support"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+
+while (1);
+
+/* @deprecated Please use watch()/cancelWatch() instead of
+ * asyncWait()/cancelWait().
+ *
+ * Waits on the given handle until the state indicated by |signals| is
+ * satisfied.
+ *
+ * @param {MojoHandle} handle The handle to wait on.
+ * @param {MojoHandleSignals} signals Specifies the condition to wait for.
+ * @param {function (mojoResult)} callback Called with the result the wait is
+ * complete. See MojoWait for possible result codes.
+ *
+ * @return {MojoWaitId} A waitId that can be passed to cancelWait to cancel the
+ * wait.
+ */
+function asyncWait(handle, signals, callback) { [native code] }
+
+/* @deprecated Please use watch()/cancelWatch() instead of
+ * asyncWait()/cancelWait().
+ *
+ * Cancels the asyncWait operation specified by the given |waitId|.
+ *
+ * @param {MojoWaitId} waitId The waitId returned by asyncWait.
+ */
+function cancelWait(waitId) { [native code] }
+
+/* Begins watching a handle for |signals| to be satisfied or unsatisfiable.
+ *
+ * @param {MojoHandle} handle The handle to watch.
+ * @param {MojoHandleSignals} signals The signals to watch.
+ * @param {function (mojoResult)} calback Called with a result any time
+ * the watched signals become satisfied or unsatisfiable.
+ *
+ * @param {MojoWatchId} watchId An opaque identifier that identifies this
+ * watch.
+ */
+function watch(handle, signals, callback) { [native code] }
+
+/* Cancels a handle watch initiated by watch().
+ *
+ * @param {MojoWatchId} watchId The watch identifier returned by watch().
+ */
+function cancelWatch(watchId) { [native code] }
diff --git a/mojo/public/js/tests/core_unittest.js b/mojo/public/js/tests/core_unittest.js
new file mode 100644
index 0000000000..86a997f1e7
--- /dev/null
+++ b/mojo/public/js/tests/core_unittest.js
@@ -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.
+
+define([
+ "gin/test/expect",
+ "mojo/public/js/core",
+ "gc",
+ ], function(expect, core, gc) {
+
+ var HANDLE_SIGNAL_READWRITABLE = core.HANDLE_SIGNAL_WRITABLE |
+ core.HANDLE_SIGNAL_READABLE;
+ var HANDLE_SIGNAL_ALL = core.HANDLE_SIGNAL_WRITABLE |
+ core.HANDLE_SIGNAL_READABLE |
+ core.HANDLE_SIGNAL_PEER_CLOSED;
+
+ runWithMessagePipe(testNop);
+ runWithMessagePipe(testReadAndWriteMessage);
+ runWithMessagePipeWithOptions(testNop);
+ runWithMessagePipeWithOptions(testReadAndWriteMessage);
+ runWithDataPipe(testNop);
+ runWithDataPipe(testReadAndWriteDataPipe);
+ runWithDataPipeWithOptions(testNop);
+ runWithDataPipeWithOptions(testReadAndWriteDataPipe);
+ runWithMessagePipe(testIsHandleMessagePipe);
+ runWithDataPipe(testIsHandleDataPipe);
+ runWithSharedBuffer(testSharedBuffer);
+ gc.collectGarbage(); // should not crash
+ this.result = "PASS";
+
+ function runWithMessagePipe(test) {
+ var pipe = core.createMessagePipe();
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+ }
+
+ function runWithMessagePipeWithOptions(test) {
+ var pipe = core.createMessagePipe({
+ flags: core.CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE
+ });
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+ }
+
+ function runWithDataPipe(test) {
+ var pipe = core.createDataPipe();
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
+ }
+
+ function runWithDataPipeWithOptions(test) {
+ var pipe = core.createDataPipe({
+ flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ elementNumBytes: 1,
+ capacityNumBytes: 64
+ });
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
+ }
+
+ function runWithSharedBuffer(test) {
+ let buffer_size = 32;
+ let sharedBuffer = core.createSharedBuffer(buffer_size,
+ core.CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE);
+
+ expect(sharedBuffer.result).toBe(core.RESULT_OK);
+ expect(core.isHandle(sharedBuffer.handle)).toBeTruthy();
+
+ test(sharedBuffer, buffer_size);
+ }
+
+ function testNop(pipe) {
+ }
+
+ function testReadAndWriteMessage(pipe) {
+ var state0 = core.queryHandleSignalsState(pipe.handle0);
+ expect(state0.result).toBe(core.RESULT_OK);
+ expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
+ expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+ var state1 = core.queryHandleSignalsState(pipe.handle1);
+ expect(state1.result).toBe(core.RESULT_OK);
+ expect(state1.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
+ expect(state1.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+ var senderData = new Uint8Array(42);
+ for (var i = 0; i < senderData.length; ++i) {
+ senderData[i] = i * i;
+ }
+
+ var result = core.writeMessage(
+ pipe.handle0, senderData, [],
+ core.WRITE_MESSAGE_FLAG_NONE);
+
+ expect(result).toBe(core.RESULT_OK);
+
+ state0 = core.queryHandleSignalsState(pipe.handle0);
+ expect(state0.result).toBe(core.RESULT_OK);
+ expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
+ expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+ var wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE);
+ expect(wait.result).toBe(core.RESULT_OK);
+ expect(wait.signalsState.satisfiedSignals).toBe(HANDLE_SIGNAL_READWRITABLE);
+ expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+ var read = core.readMessage(pipe.handle1, core.READ_MESSAGE_FLAG_NONE);
+
+ expect(read.result).toBe(core.RESULT_OK);
+ expect(read.buffer.byteLength).toBe(42);
+ expect(read.handles.length).toBe(0);
+
+ var memory = new Uint8Array(read.buffer);
+ for (var i = 0; i < memory.length; ++i)
+ expect(memory[i]).toBe((i * i) & 0xFF);
+ }
+
+ function testReadAndWriteDataPipe(pipe) {
+ var senderData = new Uint8Array(42);
+ for (var i = 0; i < senderData.length; ++i) {
+ senderData[i] = i * i;
+ }
+
+ var write = core.writeData(
+ pipe.producerHandle, senderData,
+ core.WRITE_DATA_FLAG_ALL_OR_NONE);
+
+ expect(write.result).toBe(core.RESULT_OK);
+ expect(write.numBytes).toBe(42);
+
+ var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE);
+ expect(wait.result).toBe(core.RESULT_OK);
+ var peeked = core.readData(
+ pipe.consumerHandle,
+ core.READ_DATA_FLAG_PEEK | core.READ_DATA_FLAG_ALL_OR_NONE);
+ expect(peeked.result).toBe(core.RESULT_OK);
+ expect(peeked.buffer.byteLength).toBe(42);
+
+ var peeked_memory = new Uint8Array(peeked.buffer);
+ for (var i = 0; i < peeked_memory.length; ++i)
+ expect(peeked_memory[i]).toBe((i * i) & 0xFF);
+
+ var read = core.readData(
+ pipe.consumerHandle, core.READ_DATA_FLAG_ALL_OR_NONE);
+
+ expect(read.result).toBe(core.RESULT_OK);
+ expect(read.buffer.byteLength).toBe(42);
+
+ var memory = new Uint8Array(read.buffer);
+ for (var i = 0; i < memory.length; ++i)
+ expect(memory[i]).toBe((i * i) & 0xFF);
+ }
+
+ function testIsHandleMessagePipe(pipe) {
+ expect(core.isHandle(123).toBeFalsy);
+ expect(core.isHandle("123").toBeFalsy);
+ expect(core.isHandle({}).toBeFalsy);
+ expect(core.isHandle([]).toBeFalsy);
+ expect(core.isHandle(undefined).toBeFalsy);
+ expect(core.isHandle(pipe).toBeFalsy);
+ expect(core.isHandle(pipe.handle0)).toBeTruthy();
+ expect(core.isHandle(pipe.handle1)).toBeTruthy();
+ expect(core.isHandle(null)).toBeTruthy();
+ }
+
+ function testIsHandleDataPipe(pipe) {
+ expect(core.isHandle(pipe.consumerHandle)).toBeTruthy();
+ expect(core.isHandle(pipe.producerHandle)).toBeTruthy();
+ }
+
+ function testSharedBuffer(sharedBuffer, buffer_size) {
+ let offset = 0;
+ let mappedBuffer0 = core.mapBuffer(sharedBuffer.handle,
+ offset,
+ buffer_size,
+ core.MAP_BUFFER_FLAG_NONE);
+
+ expect(mappedBuffer0.result).toBe(core.RESULT_OK);
+
+ let dupedBufferHandle = core.duplicateBufferHandle(sharedBuffer.handle,
+ core.DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE);
+
+ expect(dupedBufferHandle.result).toBe(core.RESULT_OK);
+ expect(core.isHandle(dupedBufferHandle.handle)).toBeTruthy();
+
+ let mappedBuffer1 = core.mapBuffer(dupedBufferHandle.handle,
+ offset,
+ buffer_size,
+ core.MAP_BUFFER_FLAG_NONE);
+
+ expect(mappedBuffer1.result).toBe(core.RESULT_OK);
+
+ let buffer0 = new Uint8Array(mappedBuffer0.buffer);
+ let buffer1 = new Uint8Array(mappedBuffer1.buffer);
+ for(let i = 0; i < buffer0.length; ++i) {
+ buffer0[i] = i;
+ expect(buffer1[i]).toBe(i);
+ }
+
+ expect(core.unmapBuffer(mappedBuffer0.buffer)).toBe(core.RESULT_OK);
+ expect(core.unmapBuffer(mappedBuffer1.buffer)).toBe(core.RESULT_OK);
+
+ expect(core.close(dupedBufferHandle.handle)).toBe(core.RESULT_OK);
+ expect(core.close(sharedBuffer.handle)).toBe(core.RESULT_OK);
+ }
+
+});
diff --git a/mojo/public/js/tests/validation_test_input_parser.js b/mojo/public/js/tests/validation_test_input_parser.js
new file mode 100644
index 0000000000..f5a57f9172
--- /dev/null
+++ b/mojo/public/js/tests/validation_test_input_parser.js
@@ -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.
+
+// Support for parsing binary sequences encoded as readable strings
+// or ".data" files. The input format is described here:
+// mojo/public/cpp/bindings/tests/validation_test_input_parser.h
+
+define([
+ "mojo/public/js/buffer"
+ ], function(buffer) {
+
+ // Files and Lines represent the raw text from an input string
+ // or ".data" file.
+
+ function InputError(message, line) {
+ this.message = message;
+ this.line = line;
+ }
+
+ InputError.prototype.toString = function() {
+ var s = 'Error: ' + this.message;
+ if (this.line)
+ s += ', at line ' +
+ (this.line.number + 1) + ': "' + this.line.contents + '"';
+ return s;
+ }
+
+ function File(contents) {
+ this.contents = contents;
+ this.index = 0;
+ this.lineNumber = 0;
+ }
+
+ File.prototype.endReached = function() {
+ return this.index >= this.contents.length;
+ }
+
+ File.prototype.nextLine = function() {
+ if (this.endReached())
+ return null;
+ var start = this.index;
+ var end = this.contents.indexOf('\n', start);
+ if (end == -1)
+ end = this.contents.length;
+ this.index = end + 1;
+ return new Line(this.contents.substring(start, end), this.lineNumber++);
+ }
+
+ function Line(contents, number) {
+ var i = contents.indexOf('//');
+ var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim();
+ this.contents = contents;
+ this.items = (s.length > 0) ? s.split(/\s+/) : [];
+ this.index = 0;
+ this.number = number;
+ }
+
+ Line.prototype.endReached = function() {
+ return this.index >= this.items.length;
+ }
+
+ var ITEM_TYPE_SIZES = {
+ u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8,
+ dist4: 4, dist8: 8, anchr: 0, handles: 0
+ };
+
+ function isValidItemType(type) {
+ return ITEM_TYPE_SIZES[type] !== undefined;
+ }
+
+ Line.prototype.nextItem = function() {
+ if (this.endReached())
+ return null;
+
+ var itemString = this.items[this.index++];
+ var type = 'u1';
+ var value = itemString;
+
+ if (itemString.charAt(0) == '[') {
+ var i = itemString.indexOf(']');
+ if (i != -1 && i + 1 < itemString.length) {
+ type = itemString.substring(1, i);
+ value = itemString.substring(i + 1);
+ } else {
+ throw new InputError('invalid item', this);
+ }
+ }
+ if (!isValidItemType(type))
+ throw new InputError('invalid item type', this);
+
+ return new Item(this, type, value);
+ }
+
+ // The text for each whitespace delimited binary data "item" is represented
+ // by an Item.
+
+ function Item(line, type, value) {
+ this.line = line;
+ this.type = type;
+ this.value = value;
+ this.size = ITEM_TYPE_SIZES[type];
+ }
+
+ Item.prototype.isFloat = function() {
+ return this.type == 'f' || this.type == 'd';
+ }
+
+ Item.prototype.isInteger = function() {
+ return ['u1', 'u2', 'u4', 'u8',
+ 's1', 's2', 's4', 's8'].indexOf(this.type) != -1;
+ }
+
+ Item.prototype.isNumber = function() {
+ return this.isFloat() || this.isInteger();
+ }
+
+ Item.prototype.isByte = function() {
+ return this.type == 'b';
+ }
+
+ Item.prototype.isDistance = function() {
+ return this.type == 'dist4' || this.type == 'dist8';
+ }
+
+ Item.prototype.isAnchor = function() {
+ return this.type == 'anchr';
+ }
+
+ Item.prototype.isHandles = function() {
+ return this.type == 'handles';
+ }
+
+ // A TestMessage represents the complete binary message loaded from an input
+ // string or ".data" file. The parseTestMessage() function below constructs
+ // a TestMessage from a File.
+
+ function TestMessage(byteLength) {
+ this.index = 0;
+ this.buffer = new buffer.Buffer(byteLength);
+ this.distances = {};
+ this.handleCount = 0;
+ }
+
+ function checkItemNumberValue(item, n, min, max) {
+ if (n < min || n > max)
+ throw new InputError('invalid item value', item.line);
+ }
+
+ TestMessage.prototype.addNumber = function(item) {
+ var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value);
+ if (Number.isNaN(n))
+ throw new InputError("can't parse item value", item.line);
+
+ switch(item.type) {
+ case 'u1':
+ checkItemNumberValue(item, n, 0, 0xFF);
+ this.buffer.setUint8(this.index, n);
+ break;
+ case 'u2':
+ checkItemNumberValue(item, n, 0, 0xFFFF);
+ this.buffer.setUint16(this.index, n);
+ break;
+ case 'u4':
+ checkItemNumberValue(item, n, 0, 0xFFFFFFFF);
+ this.buffer.setUint32(this.index, n);
+ break;
+ case 'u8':
+ checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER);
+ this.buffer.setUint64(this.index, n);
+ break;
+ case 's1':
+ checkItemNumberValue(item, n, -128, 127);
+ this.buffer.setInt8(this.index, n);
+ break;
+ case 's2':
+ checkItemNumberValue(item, n, -32768, 32767);
+ this.buffer.setInt16(this.index, n);
+ break;
+ case 's4':
+ checkItemNumberValue(item, n, -2147483648, 2147483647);
+ this.buffer.setInt32(this.index, n);
+ break;
+ case 's8':
+ checkItemNumberValue(item, n,
+ Number.MIN_SAFE_INTEGER,
+ Number.MAX_SAFE_INTEGER);
+ this.buffer.setInt64(this.index, n);
+ break;
+ case 'f':
+ this.buffer.setFloat32(this.index, n);
+ break;
+ case 'd':
+ this.buffer.setFloat64(this.index, n);
+ break;
+
+ default:
+ throw new InputError('unrecognized item type', item.line);
+ }
+ }
+
+ TestMessage.prototype.addByte = function(item) {
+ if (!/^[01]{8}$/.test(item.value))
+ throw new InputError('invalid byte item value', item.line);
+ function b(i) {
+ return (item.value.charAt(7 - i) == '1') ? 1 << i : 0;
+ }
+ var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7);
+ this.buffer.setUint8(this.index, n);
+ }
+
+ TestMessage.prototype.addDistance = function(item) {
+ if (this.distances[item.value])
+ throw new InputError('duplicate distance item', item.line);
+ this.distances[item.value] = {index: this.index, item: item};
+ }
+
+ TestMessage.prototype.addAnchor = function(item) {
+ var dist = this.distances[item.value];
+ if (!dist)
+ throw new InputError('unmatched anchor item', item.line);
+ delete this.distances[item.value];
+
+ var n = this.index - dist.index;
+ // TODO(hansmuller): validate n
+
+ if (dist.item.type == 'dist4')
+ this.buffer.setUint32(dist.index, n);
+ else if (dist.item.type == 'dist8')
+ this.buffer.setUint64(dist.index, n);
+ else
+ throw new InputError('unrecognzed distance item type', dist.item.line);
+ }
+
+ TestMessage.prototype.addHandles = function(item) {
+ this.handleCount = parseInt(item.value);
+ if (Number.isNaN(this.handleCount))
+ throw new InputError("can't parse handleCount", item.line);
+ }
+
+ TestMessage.prototype.addItem = function(item) {
+ if (item.isNumber())
+ this.addNumber(item);
+ else if (item.isByte())
+ this.addByte(item);
+ else if (item.isDistance())
+ this.addDistance(item);
+ else if (item.isAnchor())
+ this.addAnchor(item);
+ else if (item.isHandles())
+ this.addHandles(item);
+ else
+ throw new InputError('unrecognized item type', item.line);
+
+ this.index += item.size;
+ }
+
+ TestMessage.prototype.unanchoredDistances = function() {
+ var names = null;
+ for (var name in this.distances) {
+ if (this.distances.hasOwnProperty(name))
+ names = (names === null) ? name : names + ' ' + name;
+ }
+ return names;
+ }
+
+ function parseTestMessage(text) {
+ var file = new File(text);
+ var items = [];
+ var messageLength = 0;
+ while(!file.endReached()) {
+ var line = file.nextLine();
+ while (!line.endReached()) {
+ var item = line.nextItem();
+ if (item.isHandles() && items.length > 0)
+ throw new InputError('handles item is not first');
+ messageLength += item.size;
+ items.push(item);
+ }
+ }
+
+ var msg = new TestMessage(messageLength);
+ for (var i = 0; i < items.length; i++)
+ msg.addItem(items[i]);
+
+ if (messageLength != msg.index)
+ throw new InputError('failed to compute message length');
+ var names = msg.unanchoredDistances();
+ if (names)
+ throw new InputError('no anchors for ' + names, 0);
+
+ return msg;
+ }
+
+ var exports = {};
+ exports.parseTestMessage = parseTestMessage;
+ exports.InputError = InputError;
+ return exports;
+});
diff --git a/mojo/public/js/tests/validation_unittest.js b/mojo/public/js/tests/validation_unittest.js
new file mode 100644
index 0000000000..2a07315436
--- /dev/null
+++ b/mojo/public/js/tests/validation_unittest.js
@@ -0,0 +1,334 @@
+// Copyright 2014 The Chromium 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([
+ "console",
+ "file",
+ "gin/test/expect",
+ "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom",
+ "mojo/public/js/bindings",
+ "mojo/public/js/buffer",
+ "mojo/public/js/codec",
+ "mojo/public/js/core",
+ "mojo/public/js/tests/validation_test_input_parser",
+ "mojo/public/js/validator",
+], function(console,
+ file,
+ expect,
+ testInterface,
+ bindings,
+ buffer,
+ codec,
+ core,
+ parser,
+ validator) {
+
+ var noError = validator.validationError.NONE;
+
+ function checkTestMessageParser() {
+ function TestMessageParserFailure(message, input) {
+ this.message = message;
+ this.input = input;
+ }
+
+ TestMessageParserFailure.prototype.toString = function() {
+ return 'Error: ' + this.message + ' for "' + this.input + '"';
+ };
+
+ function checkData(data, expectedData, input) {
+ if (data.byteLength != expectedData.byteLength) {
+ var s = "message length (" + data.byteLength + ") doesn't match " +
+ "expected length: " + expectedData.byteLength;
+ throw new TestMessageParserFailure(s, input);
+ }
+
+ for (var i = 0; i < data.byteLength; i++) {
+ if (data.getUint8(i) != expectedData.getUint8(i)) {
+ var s = 'message data mismatch at byte offset ' + i;
+ throw new TestMessageParserFailure(s, input);
+ }
+ }
+ }
+
+ function testFloatItems() {
+ var input = '[f]+.3e9 [d]-10.03';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(12);
+ expectedData.setFloat32(0, +.3e9);
+ expectedData.setFloat64(4, -10.03);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testUnsignedIntegerItems() {
+ var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' +
+ '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(17);
+ expectedData.setUint8(0, 0x10);
+ expectedData.setUint16(1, 65535);
+ expectedData.setUint32(3, 65536);
+ expectedData.setUint64(7, 0xFFFFFFFFFFFFF);
+ expectedData.setUint8(15, 0);
+ expectedData.setUint8(16, 0xff);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testSignedIntegerItems() {
+ var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(15);
+ expectedData.setInt64(0, -0x800);
+ expectedData.setInt8(8, -128);
+ expectedData.setInt16(9, 0);
+ expectedData.setInt32(11, -40);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testByteItems() {
+ var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(3);
+ expectedData.setUint8(0, 11);
+ expectedData.setUint8(1, 128);
+ expectedData.setUint8(2, 0);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testAnchors() {
+ var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(14);
+ expectedData.setUint32(0, 14);
+ expectedData.setUint8(4, 0);
+ expectedData.setUint64(5, 9);
+ expectedData.setUint8(13, 0);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testHandles() {
+ var input = '// This message has handles! \n[handles]50 [u8]2';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(8);
+ expectedData.setUint64(0, 2);
+
+ if (msg.handleCount != 50) {
+ var s = 'wrong handle count (' + msg.handleCount + ')';
+ throw new TestMessageParserFailure(s, input);
+ }
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testEmptyInput() {
+ var msg = parser.parseTestMessage('');
+ if (msg.buffer.byteLength != 0)
+ throw new TestMessageParserFailure('expected empty message', '');
+ }
+
+ function testBlankInput() {
+ var input = ' \t // hello world \n\r \t// the answer is 42 ';
+ var msg = parser.parseTestMessage(input);
+ if (msg.buffer.byteLength != 0)
+ throw new TestMessageParserFailure('expected empty message', input);
+ }
+
+ function testInvalidInput() {
+ function parserShouldFail(input) {
+ try {
+ parser.parseTestMessage(input);
+ } catch (e) {
+ if (e instanceof parser.InputError)
+ return;
+ throw new TestMessageParserFailure(
+ 'unexpected exception ' + e.toString(), input);
+ }
+ throw new TestMessageParserFailure("didn't detect invalid input", file);
+ }
+
+ ['/ hello world',
+ '[u1]x',
+ '[u2]-1000',
+ '[u1]0x100',
+ '[s2]-0x8001',
+ '[b]1',
+ '[b]1111111k',
+ '[dist4]unmatched',
+ '[anchr]hello [dist8]hello',
+ '[dist4]a [dist4]a [anchr]a',
+ // '[dist4]a [anchr]a [dist4]a [anchr]a',
+ '0 [handles]50'
+ ].forEach(parserShouldFail);
+ }
+
+ try {
+ testFloatItems();
+ testUnsignedIntegerItems();
+ testSignedIntegerItems();
+ testByteItems();
+ testInvalidInput();
+ testEmptyInput();
+ testBlankInput();
+ testHandles();
+ testAnchors();
+ } catch (e) {
+ return e.toString();
+ }
+ return null;
+ }
+
+ function getMessageTestFiles(prefix) {
+ var sourceRoot = file.getSourceRootDirectory();
+ expect(sourceRoot).not.toBeNull();
+
+ var testDir = sourceRoot +
+ "/mojo/public/interfaces/bindings/tests/data/validation/";
+ var testFiles = file.getFilesInDirectory(testDir);
+ expect(testFiles).not.toBeNull();
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ // The matching ".data" pathnames with the extension removed.
+ return testFiles.filter(function(s) {
+ return s.substr(-5) == ".data" && s.indexOf(prefix) == 0;
+ }).map(function(s) {
+ return testDir + s.slice(0, -5);
+ });
+ }
+
+ function readTestMessage(filename) {
+ var contents = file.readFileToString(filename + ".data");
+ expect(contents).not.toBeNull();
+ return parser.parseTestMessage(contents);
+ }
+
+ function readTestExpected(filename) {
+ var contents = file.readFileToString(filename + ".expected");
+ expect(contents).not.toBeNull();
+ return contents.trim();
+ }
+
+ function checkValidationResult(testFile, err) {
+ var actualResult = (err === noError) ? "PASS" : err;
+ var expectedResult = readTestExpected(testFile);
+ if (actualResult != expectedResult)
+ console.log("[Test message validation failed: " + testFile + " ]");
+ expect(actualResult).toEqual(expectedResult);
+ }
+
+ function testMessageValidation(prefix, filters) {
+ var testFiles = getMessageTestFiles(prefix);
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ for (var i = 0; i < testFiles.length; i++) {
+ // TODO(hansmuller) Temporarily skipping array pointer overflow tests
+ // because JS numbers are limited to 53 bits.
+ // TODO(rudominer): Temporarily skipping 'no-such-method',
+ // 'invalid_request_flags', and 'invalid_response_flags' until additional
+ // logic in *RequestValidator and *ResponseValidator is ported from
+ // cpp to js.
+ // TODO(crbug/640298): Implement max recursion depth for JS.
+ // TODO(crbug/628104): Support struct map keys for JS.
+ if (testFiles[i].indexOf("overflow") != -1 ||
+ testFiles[i].indexOf("conformance_mthd19") != -1 ||
+ testFiles[i].indexOf("conformance_mthd20") != -1 ||
+ testFiles[i].indexOf("no_such_method") != -1 ||
+ testFiles[i].indexOf("invalid_request_flags") != -1 ||
+ testFiles[i].indexOf("invalid_response_flags") != -1) {
+ console.log("[Skipping " + testFiles[i] + "]");
+ continue;
+ }
+
+ var testMessage = readTestMessage(testFiles[i]);
+ var handles = new Array(testMessage.handleCount);
+ var message = new codec.Message(testMessage.buffer, handles);
+ var messageValidator = new validator.Validator(message);
+
+ var err = messageValidator.validateMessageHeader();
+ for (var j = 0; err === noError && j < filters.length; ++j)
+ err = filters[j](messageValidator);
+
+ checkValidationResult(testFiles[i], err);
+ }
+ }
+
+ function testConformanceMessageValidation() {
+ testMessageValidation("conformance_", [
+ testInterface.ConformanceTestInterface.validateRequest]);
+ }
+
+ function testBoundsCheckMessageValidation() {
+ testMessageValidation("boundscheck_", [
+ testInterface.BoundsCheckTestInterface.validateRequest]);
+ }
+
+ function testResponseConformanceMessageValidation() {
+ testMessageValidation("resp_conformance_", [
+ testInterface.ConformanceTestInterface.validateResponse]);
+ }
+
+ function testResponseBoundsCheckMessageValidation() {
+ testMessageValidation("resp_boundscheck_", [
+ testInterface.BoundsCheckTestInterface.validateResponse]);
+ }
+
+ function testIntegratedMessageValidation(testFilesPattern, endpoint) {
+ var testFiles = getMessageTestFiles(testFilesPattern);
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ var testMessagePipe = core.createMessagePipe();
+ expect(testMessagePipe.result).toBe(core.RESULT_OK);
+
+ endpoint.bind(testMessagePipe.handle1);
+ var observer = validator.ValidationErrorObserverForTesting.getInstance();
+
+ for (var i = 0; i < testFiles.length; i++) {
+ var testMessage = readTestMessage(testFiles[i]);
+ var handles = new Array(testMessage.handleCount);
+
+ var writeMessageValue = core.writeMessage(
+ testMessagePipe.handle0,
+ new Uint8Array(testMessage.buffer.arrayBuffer),
+ new Array(testMessage.handleCount),
+ core.WRITE_MESSAGE_FLAG_NONE);
+ expect(writeMessageValue).toBe(core.RESULT_OK);
+
+ endpoint.waitForNextMessageForTesting();
+ checkValidationResult(testFiles[i], observer.lastError);
+ observer.reset();
+ }
+
+ expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
+ }
+
+ function testIntegratedMessageHeaderValidation() {
+ testIntegratedMessageValidation(
+ "integration_msghdr",
+ new bindings.Binding(testInterface.IntegrationTestInterface, {}));
+ testIntegratedMessageValidation(
+ "integration_msghdr",
+ new testInterface.IntegrationTestInterfacePtr().ptr);
+ }
+
+ function testIntegratedRequestMessageValidation() {
+ testIntegratedMessageValidation(
+ "integration_intf_rqst",
+ new bindings.Binding(testInterface.IntegrationTestInterface, {}));
+ }
+
+ function testIntegratedResponseMessageValidation() {
+ testIntegratedMessageValidation(
+ "integration_intf_resp",
+ new testInterface.IntegrationTestInterfacePtr().ptr);
+ }
+
+ expect(checkTestMessageParser()).toBeNull();
+ testConformanceMessageValidation();
+ testBoundsCheckMessageValidation();
+ testResponseConformanceMessageValidation();
+ testResponseBoundsCheckMessageValidation();
+ testIntegratedMessageHeaderValidation();
+ testIntegratedResponseMessageValidation();
+ testIntegratedRequestMessageValidation();
+ validator.clearTestingMode();
+
+ this.result = "PASS";
+});
diff --git a/mojo/public/js/threading.js b/mojo/public/js/threading.js
new file mode 100644
index 0000000000..49ab5c9142
--- /dev/null
+++ b/mojo/public/js/threading.js
@@ -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.
+
+// Module "mojo/public/js/threading"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+//
+// This module provides a way for a Service implemented in JS
+// to exit by quitting the current message loop. This module is not
+// intended to be used by Mojo JS application started by the JS
+// content handler.
+
+while (1);
+
+/**
+ * Quits the current message loop, esssentially:
+ * base::MessageLoop::current()->QuitNow();
+*/
+function quit() { [native code] }
diff --git a/mojo/public/js/unicode.js b/mojo/public/js/unicode.js
new file mode 100644
index 0000000000..be2ba0e63c
--- /dev/null
+++ b/mojo/public/js/unicode.js
@@ -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.
+
+/**
+ * Defines functions for translating between JavaScript strings and UTF8 strings
+ * stored in ArrayBuffers. There is much room for optimization in this code if
+ * it proves necessary.
+ */
+define("mojo/public/js/unicode", function() {
+ /**
+ * Decodes the UTF8 string from the given buffer.
+ * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
+ * @return {string} The corresponding JavaScript string.
+ */
+ function decodeUtf8String(buffer) {
+ return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer)));
+ }
+
+ /**
+ * Encodes the given JavaScript string into UTF8.
+ * @param {string} str The string to encode.
+ * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
+ * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
+ * how much space is required.
+ * @return {number} The number of bytes written to |outputBuffer|.
+ */
+ function encodeUtf8String(str, outputBuffer) {
+ var utf8String = unescape(encodeURIComponent(str));
+ if (outputBuffer.length < utf8String.length)
+ throw new Error("Buffer too small for encodeUtf8String");
+ for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++)
+ outputBuffer[i] = utf8String.charCodeAt(i);
+ return i;
+ }
+
+ /**
+ * Returns the number of bytes that a UTF8 encoding of the JavaScript string
+ * |str| would occupy.
+ */
+ function utf8Length(str) {
+ var utf8String = unescape(encodeURIComponent(str));
+ return utf8String.length;
+ }
+
+ var exports = {};
+ exports.decodeUtf8String = decodeUtf8String;
+ exports.encodeUtf8String = encodeUtf8String;
+ exports.utf8Length = utf8Length;
+ return exports;
+});
diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js
new file mode 100644
index 0000000000..283546d4f1
--- /dev/null
+++ b/mojo/public/js/validator.js
@@ -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.
+
+define("mojo/public/js/validator", [
+ "mojo/public/js/codec",
+], function(codec) {
+
+ var validationError = {
+ NONE: 'VALIDATION_ERROR_NONE',
+ MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
+ ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
+ UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
+ UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
+ ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
+ UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
+ ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
+ UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
+ MESSAGE_HEADER_INVALID_FLAGS:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS',
+ MESSAGE_HEADER_MISSING_REQUEST_ID:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
+ DIFFERENT_SIZED_ARRAYS_IN_MAP:
+ 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
+ INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
+ UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
+ UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
+ };
+
+ var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
+ var gValidationErrorObserver = null;
+
+ function reportValidationError(error) {
+ if (gValidationErrorObserver) {
+ gValidationErrorObserver.setLastError(error);
+ }
+ }
+
+ var ValidationErrorObserverForTesting = (function() {
+ function Observer() {
+ this.lastError = validationError.NONE;
+ this.callback = null;
+ }
+
+ Observer.prototype.setLastError = function(error) {
+ this.lastError = error;
+ if (this.callback) {
+ this.callback(error);
+ }
+ };
+
+ Observer.prototype.reset = function(error) {
+ this.lastError = validationError.NONE;
+ this.callback = null;
+ };
+
+ return {
+ getInstance: function() {
+ if (!gValidationErrorObserver) {
+ gValidationErrorObserver = new Observer();
+ }
+ return gValidationErrorObserver;
+ }
+ };
+ })();
+
+ function isTestingMode() {
+ return Boolean(gValidationErrorObserver);
+ }
+
+ function clearTestingMode() {
+ gValidationErrorObserver = null;
+ }
+
+ function isEnumClass(cls) {
+ return cls instanceof codec.Enum;
+ }
+
+ function isStringClass(cls) {
+ return cls === codec.String || cls === codec.NullableString;
+ }
+
+ function isHandleClass(cls) {
+ return cls === codec.Handle || cls === codec.NullableHandle;
+ }
+
+ function isInterfaceClass(cls) {
+ return cls instanceof codec.Interface;
+ }
+
+ function isInterfaceRequestClass(cls) {
+ return cls === codec.InterfaceRequest ||
+ cls === codec.NullableInterfaceRequest;
+ }
+
+ function isNullable(type) {
+ return type === codec.NullableString || type === codec.NullableHandle ||
+ type === codec.NullableInterface ||
+ type === codec.NullableInterfaceRequest ||
+ type instanceof codec.NullableArrayOf ||
+ type instanceof codec.NullablePointerTo;
+ }
+
+ function Validator(message) {
+ this.message = message;
+ this.offset = 0;
+ this.handleIndex = 0;
+ }
+
+ Object.defineProperty(Validator.prototype, "offsetLimit", {
+ get: function() { return this.message.buffer.byteLength; }
+ });
+
+ Object.defineProperty(Validator.prototype, "handleIndexLimit", {
+ get: function() { return this.message.handles.length; }
+ });
+
+ // True if we can safely allocate a block of bytes from start to
+ // to start + numBytes.
+ Validator.prototype.isValidRange = function(start, numBytes) {
+ // Only positive JavaScript integers that are less than 2^53
+ // (Number.MAX_SAFE_INTEGER) can be represented exactly.
+ if (start < this.offset || numBytes <= 0 ||
+ !Number.isSafeInteger(start) ||
+ !Number.isSafeInteger(numBytes))
+ return false;
+
+ var newOffset = start + numBytes;
+ if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
+ return false;
+
+ return true;
+ };
+
+ Validator.prototype.claimRange = function(start, numBytes) {
+ if (this.isValidRange(start, numBytes)) {
+ this.offset = start + numBytes;
+ return true;
+ }
+ return false;
+ };
+
+ Validator.prototype.claimHandle = function(index) {
+ if (index === codec.kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < this.handleIndex || index >= this.handleIndexLimit)
+ return false;
+
+ // This is safe because handle indices are uint32.
+ this.handleIndex = index + 1;
+ return true;
+ };
+
+ Validator.prototype.validateEnum = function(offset, enumClass) {
+ // Note: Assumes that enums are always 32 bits! But this matches
+ // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
+ var value = this.message.buffer.getInt32(offset);
+ return enumClass.validate(value);
+ }
+
+ Validator.prototype.validateHandle = function(offset, nullable) {
+ var index = this.message.buffer.getUint32(offset);
+
+ if (index === codec.kEncodedInvalidHandleValue)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
+
+ if (!this.claimHandle(index))
+ return validationError.ILLEGAL_HANDLE;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterface = function(offset, nullable) {
+ return this.validateHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
+ return this.validateHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
+ if (!codec.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, codec.kStructHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+
+ if (numBytes < minNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStructVersion = function(offset, versionSizes) {
+ var numBytes = this.message.buffer.getUint32(offset);
+ var version = this.message.buffer.getUint32(offset + 4);
+
+ if (version <= versionSizes[versionSizes.length - 1].version) {
+ // Scan in reverse order to optimize for more recent versionSizes.
+ for (var i = versionSizes.length - 1; i >= 0; --i) {
+ if (version >= versionSizes[i].version) {
+ if (numBytes == versionSizes[i].numBytes)
+ break;
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+ }
+ } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
+ var structVersion = this.message.buffer.getUint32(offset + 4);
+ return fieldVersion <= structVersion;
+ };
+
+ // TODO(wangjimmy): Add support for v2 messages.
+ Validator.prototype.validateMessageHeader = function() {
+
+ var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
+ if (err != validationError.NONE)
+ return err;
+
+ var numBytes = this.message.getHeaderNumBytes();
+ var version = this.message.getHeaderVersion();
+
+ var validVersionAndNumBytes =
+ (version == 0 && numBytes == codec.kMessageHeaderSize) ||
+ (version == 1 &&
+ numBytes == codec.kMessageWithRequestIDHeaderSize) ||
+ (version > 1 &&
+ numBytes >= codec.kMessageWithRequestIDHeaderSize);
+ if (!validVersionAndNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ var expectsResponse = this.message.expectsResponse();
+ var isResponse = this.message.isResponse();
+
+ if (version == 0 && (expectsResponse || isResponse))
+ return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
+
+ if (isResponse && expectsResponse)
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
+ if (this.message.isResponse() || this.message.expectsResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
+ if (this.message.isResponse() || !this.message.expectsResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsResponse = function() {
+ if (this.message.expectsResponse() || !this.message.isResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ // Returns the message.buffer relative offset this pointer "points to",
+ // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
+ // pointer's value is not valid.
+ Validator.prototype.decodePointer = function(offset) {
+ var pointerValue = this.message.buffer.getUint64(offset);
+ if (pointerValue === 0)
+ return NULL_MOJO_POINTER;
+ var bufferOffset = offset + pointerValue;
+ return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
+ };
+
+ Validator.prototype.decodeUnionSize = function(offset) {
+ return this.message.buffer.getUint32(offset);
+ };
+
+ Validator.prototype.decodeUnionTag = function(offset) {
+ return this.message.buffer.getUint32(offset + 4);
+ };
+
+ Validator.prototype.validateArrayPointer = function(
+ offset, elementSize, elementType, nullable, expectedDimensionSizes,
+ currentDimension) {
+ var arrayOffset = this.decodePointer(offset);
+ if (arrayOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (arrayOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return this.validateArray(arrayOffset, elementSize, elementType,
+ expectedDimensionSizes, currentDimension);
+ };
+
+ Validator.prototype.validateStructPointer = function(
+ offset, structClass, nullable) {
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return structClass.validate(this, structOffset);
+ };
+
+ Validator.prototype.validateUnion = function(
+ offset, unionClass, nullable) {
+ var size = this.message.buffer.getUint32(offset);
+ if (size == 0) {
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
+ }
+
+ return unionClass.validate(this, offset);
+ };
+
+ Validator.prototype.validateNestedUnion = function(
+ offset, unionClass, nullable) {
+ var unionOffset = this.decodePointer(offset);
+ if (unionOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (unionOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
+
+ return this.validateUnion(unionOffset, unionClass, nullable);
+ };
+
+ // This method assumes that the array at arrayPointerOffset has
+ // been validated.
+
+ Validator.prototype.arrayLength = function(arrayPointerOffset) {
+ var arrayOffset = this.decodePointer(arrayPointerOffset);
+ return this.message.buffer.getUint32(arrayOffset + 4);
+ };
+
+ Validator.prototype.validateMapPointer = function(
+ offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
+ // Validate the implicit map struct:
+ // struct {array<keyClass> keys; array<valueClass> values};
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return mapIsNullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
+ var err = this.validateStructHeader(structOffset, mapEncodedSize);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the keys array.
+ var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
+ err = this.validateArrayPointer(
+ keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the values array.
+ var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
+ var valuesArrayDimensions = [0]; // Validate the actual length below.
+ if (valueClass instanceof codec.ArrayOf)
+ valuesArrayDimensions =
+ valuesArrayDimensions.concat(valueClass.dimensions());
+ var err = this.validateArrayPointer(valuesArrayPointerOffset,
+ valueClass.encodedSize,
+ valueClass,
+ valueIsNullable,
+ valuesArrayDimensions,
+ 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the lengths of the keys and values arrays.
+ var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
+ var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
+ if (keysArrayLength != valuesArrayLength)
+ return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStringPointer = function(offset, nullable) {
+ return this.validateArrayPointer(
+ offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
+ };
+
+ // Similar to Array_Data<T>::Validate()
+ // mojo/public/cpp/bindings/lib/array_internal.h
+
+ Validator.prototype.validateArray =
+ function (offset, elementSize, elementType, expectedDimensionSizes,
+ currentDimension) {
+ if (!codec.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, codec.kArrayHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numElements = this.message.buffer.getUint32(offset + 4);
+
+ // Note: this computation is "safe" because elementSize <= 8 and
+ // numElements is a uint32.
+ var elementsTotalSize = (elementType === codec.PackedBool) ?
+ Math.ceil(numElements / 8) : (elementSize * numElements);
+
+ if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (expectedDimensionSizes[currentDimension] != 0 &&
+ numElements != expectedDimensionSizes[currentDimension]) {
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+ }
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ // Validate the array's elements if they are pointers or handles.
+
+ var elementsOffset = offset + codec.kArrayHeaderSize;
+ var nullable = isNullable(elementType);
+
+ if (isHandleClass(elementType))
+ return this.validateHandleElements(elementsOffset, numElements, nullable);
+ if (isInterfaceClass(elementType))
+ return this.validateInterfaceElements(
+ elementsOffset, numElements, nullable);
+ if (isInterfaceRequestClass(elementType))
+ return this.validateInterfaceRequestElements(
+ elementsOffset, numElements, nullable);
+ if (isStringClass(elementType))
+ return this.validateArrayElements(
+ elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
+ if (elementType instanceof codec.PointerTo)
+ return this.validateStructElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+ if (elementType instanceof codec.ArrayOf)
+ return this.validateArrayElements(
+ elementsOffset, numElements, elementType.cls, nullable,
+ expectedDimensionSizes, currentDimension + 1);
+ if (isEnumClass(elementType))
+ return this.validateEnumElements(elementsOffset, numElements,
+ elementType.cls);
+
+ return validationError.NONE;
+ };
+
+ // Note: the |offset + i * elementSize| computation in the validateFooElements
+ // methods below is "safe" because elementSize <= 8, offset and
+ // numElements are uint32, and 0 <= i < numElements.
+
+ Validator.prototype.validateHandleElements =
+ function(offset, numElements, nullable) {
+ var elementSize = codec.Handle.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateHandle(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterfaceElements =
+ function(offset, numElements, nullable) {
+ var elementSize = codec.Interface.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateInterface(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterfaceRequestElements =
+ function(offset, numElements, nullable) {
+ var elementSize = codec.InterfaceRequest.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateInterfaceRequest(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ // The elementClass parameter is the element type of the element arrays.
+ Validator.prototype.validateArrayElements =
+ function(offset, numElements, elementClass, nullable,
+ expectedDimensionSizes, currentDimension) {
+ var elementSize = codec.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateArrayPointer(
+ elementOffset, elementClass.encodedSize, elementClass, nullable,
+ expectedDimensionSizes, currentDimension);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStructElements =
+ function(offset, numElements, structClass, nullable) {
+ var elementSize = codec.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err =
+ this.validateStructPointer(elementOffset, structClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateEnumElements =
+ function(offset, numElements, enumClass) {
+ var elementSize = codec.Enum.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateEnum(elementOffset, enumClass);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ var exports = {};
+ exports.validationError = validationError;
+ exports.Validator = Validator;
+ exports.ValidationErrorObserverForTesting = ValidationErrorObserverForTesting;
+ exports.reportValidationError = reportValidationError;
+ exports.isTestingMode = isTestingMode;
+ exports.clearTestingMode = clearTestingMode;
+ return exports;
+});
diff --git a/mojo/public/tests/test_support_private.cc b/mojo/public/tests/test_support_private.cc
new file mode 100644
index 0000000000..00819846c0
--- /dev/null
+++ b/mojo/public/tests/test_support_private.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 "mojo/public/tests/test_support_private.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static mojo::test::TestSupport* g_test_support = NULL;
+
+extern "C" {
+
+void MojoTestSupportLogPerfResult(const char* test_name,
+ const char* sub_test_name,
+ double value,
+ const char* units) {
+ if (g_test_support) {
+ g_test_support->LogPerfResult(test_name, sub_test_name, value, units);
+ } else {
+ if (sub_test_name) {
+ printf("[no test runner]\t%s/%s\t%g\t%s\n", test_name, sub_test_name,
+ value, units);
+ } else {
+ printf("[no test runner]\t%s\t%g\t%s\n", test_name, value, units);
+ }
+ }
+}
+
+FILE* MojoTestSupportOpenSourceRootRelativeFile(const char* relative_path) {
+ if (g_test_support)
+ return g_test_support->OpenSourceRootRelativeFile(relative_path);
+ printf("[no test runner]\n");
+ return NULL;
+}
+
+char** MojoTestSupportEnumerateSourceRootRelativeDirectory(
+ const char* relative_path) {
+ if (g_test_support)
+ return g_test_support->EnumerateSourceRootRelativeDirectory(relative_path);
+
+ printf("[no test runner]\n");
+
+ // Return empty list:
+ char** rv = static_cast<char**>(calloc(1, sizeof(char*)));
+ rv[0] = NULL;
+ return rv;
+}
+
+} // extern "C"
+
+namespace mojo {
+namespace test {
+
+TestSupport::~TestSupport() {
+}
+
+// static
+void TestSupport::Init(TestSupport* test_support) {
+ assert(!g_test_support);
+ g_test_support = test_support;
+}
+
+// static
+TestSupport* TestSupport::Get() {
+ return g_test_support;
+}
+
+// static
+void TestSupport::Reset() {
+ g_test_support = NULL;
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/tests/test_support_private.h b/mojo/public/tests/test_support_private.h
new file mode 100644
index 0000000000..27426055fa
--- /dev/null
+++ b/mojo/public/tests/test_support_private.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 MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_
+#define MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_
+
+#include <stdio.h>
+
+#include "mojo/public/c/test_support/test_support.h"
+
+namespace mojo {
+namespace test {
+
+// Implementors of the test support APIs can use this interface to install their
+// implementation into the mojo_test_support dynamic library.
+class TestSupport {
+ public:
+ virtual ~TestSupport();
+
+ static void Init(TestSupport* test_support);
+ static TestSupport* Get();
+ static void Reset();
+
+ virtual void LogPerfResult(const char* test_name,
+ const char* sub_test_name,
+ double value,
+ const char* units) = 0;
+ virtual FILE* OpenSourceRootRelativeFile(const char* relative_path) = 0;
+ virtual char** EnumerateSourceRootRelativeDirectory(
+ const char* relative_path) = 0;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn
new file mode 100644
index 0000000000..153d110332
--- /dev/null
+++ b/mojo/public/tools/bindings/BUILD.gn
@@ -0,0 +1,77 @@
+# 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("//mojo/public/tools/bindings/mojom.gni")
+
+action("precompile_templates") {
+ sources = mojom_generator_sources
+ sources += [
+ "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl",
+ "$mojom_generator_root/generators/java_templates/constant_definition.tmpl",
+ "$mojom_generator_root/generators/java_templates/constants.java.tmpl",
+ "$mojom_generator_root/generators/java_templates/data_types_definition.tmpl",
+ "$mojom_generator_root/generators/java_templates/enum.java.tmpl",
+ "$mojom_generator_root/generators/java_templates/enum_definition.tmpl",
+ "$mojom_generator_root/generators/java_templates/header.java.tmpl",
+ "$mojom_generator_root/generators/java_templates/interface.java.tmpl",
+ "$mojom_generator_root/generators/java_templates/interface_definition.tmpl",
+ "$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl",
+ "$mojom_generator_root/generators/java_templates/struct.java.tmpl",
+ "$mojom_generator_root/generators/java_templates/union.java.tmpl",
+ "$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/module.amd.tmpl",
+ "$mojom_generator_root/generators/js_templates/module_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/struct_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/union_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/validation_macros.tmpl",
+ ]
+ script = mojom_generator_script
+ outputs = [
+ "$target_gen_dir/cpp_templates.zip",
+ "$target_gen_dir/java_templates.zip",
+ "$target_gen_dir/js_templates.zip",
+ ]
+ args = [
+ "--use_bundled_pylibs",
+ "precompile",
+ "-o",
+ rebase_path(target_gen_dir),
+ ]
+}
diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md
new file mode 100644
index 0000000000..737c7e61df
--- /dev/null
+++ b/mojo/public/tools/bindings/README.md
@@ -0,0 +1,749 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojom IDL and Bindings Generator
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+
+Mojom is the IDL for Mojo bindings interfaces. Given a `.mojom` file, the
+[bindings generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings)
+outputs bindings for all supported languages: **C++**, **JavaScript**, and
+**Java**.
+
+For a trivial example consider the following hypothetical Mojom file we write to
+`//services/widget/public/interfaces/frobinator.mojom`:
+
+```
+module widget.mojom;
+
+interface Frobinator {
+ Frobinate();
+};
+```
+
+This defines a single [interface](#Interfaces) named `Frobinator` in a
+[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as
+`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of
+definitions may be included in a single Mojom file.
+
+If we add a corresponding GN target to
+`//services/widget/public/interfaces/BUILD.gn`:
+
+```
+import("mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+ sources = [
+ "frobinator.mojom",
+ ]
+}
+```
+
+and then build this target:
+
+```
+ninja -C out/r services/widget/public/interfaces
+```
+
+we'll find several generated sources in our output directory:
+
+```
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.cc
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.h
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.js
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.srcjar
+...
+```
+
+Each of these generated source modules includes a set of definitions
+representing the Mojom contents within the target language. For more details
+regarding the generated outputs please see
+[documentation for individual target languages](#Generated-Code-For-Target-Languages).
+
+## Mojom Syntax
+
+Mojom IDL allows developers to define **structs**, **unions**, **interfaces**,
+**constants**, and **enums**, all within the context of a **module**. These
+definitions are used to generate code in the supported target languages at build
+time.
+
+Mojom files may **import** other Mojom files in order to reference their
+definitions.
+
+### Primitive Types
+Mojom supports a few basic data types which may be composed into structs or used
+for message parameters.
+
+| Type | Description
+|-------------------------------|-------------------------------------------------------|
+| `bool` | Boolean type (`true` or `false`.)
+| `int8`, `uint8` | Signed or unsigned 8-bit integer.
+| `int16`, `uint16` | Signed or unsigned 16-bit integer.
+| `int32`, `uint32` | Signed or unsigned 32-bit integer.
+| `int64`, `uint64` | Signed or unsigned 64-bit integer.
+| `float`, `double` | 32- or 64-bit floating point number.
+| `string` | UTF-8 encoded string.
+| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.
+| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.
+| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
+| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.
+| `handle<message_pipe>` | Generic message pipe handle.
+| `handle<shared_buffer>` | Shared buffer handle.
+| `handle<data_pipe_producer>` | Data pipe producer handle.
+| `handle<data_pipe_consumer>` | Data pipe consumer handle.
+| *`InterfaceType`* | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface.
+| *`InterfaceType&`* | An interface request for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface.
+| *`associated InterfaceType`* | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces)
+| *`associated InterfaceType&`* | An associated interface request. See [Associated Interfaces](#Associated-Interfaces)
+| *T*? | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable.
+
+### Modules
+
+Every Mojom file may optionally specify a single **module** to which it belongs.
+
+This is used strictly for aggregaging all defined symbols therein within a
+common Mojom namespace. The specific impact this has on generated binidngs code
+varies for each target language. For example, if the following Mojom is used to
+generate bindings:
+
+```
+module business.stuff;
+
+interface MoneyGenerator {
+ GenerateMoney();
+};
+```
+
+Generated C++ bindings will define a class interface `MoneyGenerator` in the
+`business::stuff` namespace, while Java bindings will define an interface
+`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript
+bindings at this time are unaffected by module declarations.
+
+**NOTE:** By convention in the Chromium codebase, **all** Mojom files should
+declare a module name with at least (and preferrably exactly) one top-level name
+as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`,
+`business.mojom`, *etc.*
+
+This convention makes it easy to tell which symbols are generated by Mojom when
+reading non-Mojom code, and it also avoids namespace collisions in the fairly
+common scenario where you have a real C++ or Java `Foo` along with a
+corresponding Mojom `Foo` for its serialized representation.
+
+### Imports
+
+If your Mojom references definitions from other Mojom files, you must **import**
+those files. Import syntax is as follows:
+
+```
+import "services/widget/public/interfaces/frobinator.mojom";
+```
+
+Import paths are always relative to the top-level directory.
+
+Note that circular imports are **not** supported.
+
+### Structs
+
+Structs are defined using the **struct** keyword, and they provide a way to
+group related fields together:
+
+``` cpp
+struct StringPair {
+ string first;
+ string second;
+};
+```
+
+Struct fields may be comprised of any of the types listed above in the
+[Primitive Types](#Primitive-Types) section.
+
+Default values may be specified as long as they are constant:
+
+``` cpp
+struct Request {
+ int32 id = -1;
+ string details;
+};
+```
+
+What follows is a fairly
+comprehensive example using the supported field types:
+
+``` cpp
+struct StringPair {
+ string first;
+ string second;
+};
+
+enum AnEnum {
+ YES,
+ NO
+};
+
+interface SampleInterface {
+ DoStuff();
+};
+
+struct AllTheThings {
+ // Note that these types can never be marked nullable!
+ bool boolean_value;
+ int8 signed_8bit_value = 42;
+ uint8 unsigned_8bit_value;
+ int16 signed_16bit_value;
+ uint16 unsigned_16bit_value;
+ int32 signed_32bit_value;
+ uint32 unsigned_32bit_value;
+ int64 signed_64bit_value;
+ uint64 unsigned_64bit_value;
+ float float_value_32bit;
+ double float_value_64bit;
+ AnEnum enum_value = AnEnum.YES;
+
+ // Strings may be nullable.
+ string? maybe_a_string_maybe_not;
+
+ // Structs may contain other structs. These may also be nullable.
+ StringPair some_strings;
+ StringPair? maybe_some_more_strings;
+
+ // In fact structs can also be nested, though in practice you must always make
+ // such fields nullable -- otherwise messages would need to be infinitely long
+ // in order to pass validation!
+ AllTheThings? more_things;
+
+ // Arrays may be templated over any Mojom type, and are always nullable:
+ array<int32> numbers;
+ array<int32>? maybe_more_numbers;
+
+ // Arrays of arrays of arrays... are fine.
+ array<array<array<AnEnum>>> this_works_but_really_plz_stop;
+
+ // The element type may be nullable if it's a type which is allowed to be
+ // nullable.
+ array<AllTheThings?> more_maybe_things;
+
+ // Fixed-size arrays get some extra validation on the receiving end to ensure
+ // that the correct number of elements is always received.
+ array<uint64, 2> uuid;
+
+ // Maps follow many of the same rules as arrays. Key types may be any
+ // non-handle, non-collection type, and value types may be any supported
+ // struct field type. Maps may also be nullable.
+ map<string, int32> one_map;
+ map<AnEnum, string>? maybe_another_map;
+ map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map;
+ map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous;
+
+ // And finally, all handle types are valid as struct fields and may be
+ // nullable. Note that interfaces and interface requests (the "Foo" and
+ // "Foo&" type syntax respectively) are just strongly-typed message pipe
+ // handles.
+ handle generic_handle;
+ handle<data_pipe_consumer> reader;
+ handle<data_pipe_producer>? maybe_writer;
+ handle<shared_buffer> dumping_ground;
+ handle<message_pipe> raw_message_pipe;
+ SampleInterface? maybe_a_sample_interface_client_pipe;
+ SampleInterface& non_nullable_sample_interface_request;
+ SampleInterface&? nullable_sample_interface_request;
+ associated SampleInterface associated_interface_client;
+ associated SampleInterface& associated_interface_request;
+ assocaited SampleInterface&? maybe_another_associated_request;
+};
+```
+
+For details on how all of these different types translate to usable generated
+code, see
+[documentation for individual target languages](#Generated-Code-For-Target-Languages).
+
+### Enumeration Types
+
+Enumeration types may be defined using the **enum** keyword either directly
+within a module or within the namespace of some struct or interface:
+
+```
+module business.mojom;
+
+enum Department {
+ SALES = 0,
+ DEV,
+};
+
+struct Employee {
+ enum Type {
+ FULL_TIME,
+ PART_TIME,
+ };
+
+ Type type;
+ // ...
+};
+```
+
+That that similar to C-style enums, individual values may be explicitly assigned
+within an enum definition. By default values are based at zero and incremenet by
+1 sequentially.
+
+The effect of nested definitions on generated bindings varies depending on the
+target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)
+
+### Constants
+
+Constants may be defined using the **const** keyword either directly within a
+module or within the namespace of some struct or interface:
+
+```
+module business.mojom;
+
+const string kServiceName = "business";
+
+struct Employee {
+ const uint64 kInvalidId = 0;
+
+ enum Type {
+ FULL_TIME,
+ PART_TIME,
+ };
+
+ uint64 id = kInvalidId;
+ Type type;
+};
+```
+
+The effect of nested definitions on generated bindings varies depending on the
+target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)
+
+### Interfaces
+
+An **interface** is a logical bundle of parameterized request messages. Each
+request message may optionally define a parameterized response message. Here's
+syntax to define an interface `Foo` with various kinds of requests:
+
+```
+interface Foo {
+ // A request which takes no arguments and expects no response.
+ MyMessage();
+
+ // A request which has some arguments and expects no response.
+ MyOtherMessage(string name, array<uint8> bytes);
+
+ // A request which expects a single-argument response.
+ MyMessageWithResponse(string command) => (bool success);
+
+ // A request which expects a response with multiple arguments.
+ MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d);
+};
+```
+
+Anything which is a valid struct field type (see [Structs](#Structs)) is also a
+valid request or response argument type. The type notation is the same for both.
+
+### Attributes
+
+Mojom definitions may have their meaning altered by **attributes**, specified
+with a syntax similar to Java or C# attributes. There are a handle of
+interesting attributes supported today.
+
+**`[Sync]`**
+: The `Sync` attribute may be specified for any interface method which expects
+ a response. This makes it so that callers of the method can wait
+ synchronously for a response. See
+ [Synchronous Calls](/mojo/public/cpp/bindings#Synchronous-Calls) in the C++
+ bindings documentation. Note that sync calls are not currently supported in
+ other target languages.
+
+**`[Extensible]`**
+: The `Extensible` attribute may be specified for any enum definition. This
+ essentially disables builtin range validation when receiving values of the
+ enum type in a message, allowing older bindings to tolerate unrecognized
+ values from newer versions of the enum.
+
+**`[Native]`**
+: The `Native` attribute may be specified for an empty struct declaration to
+ provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or
+ `IPC_STRUCT_TRAITS*` macros.
+ See [Using Legacy IPC Traits](/ipc#Using-Legacy-IPC-Traits) for more
+ details. Note support for this attribute is strictly limited to C++ bindings
+ generation.
+
+**`[MinVersion=N]`**
+: The `MinVersion` attribute is used to specify the version at which a given
+ field, enum value, interface method, or method parameter was introduced.
+ See [Versioning](#Versioning) for more details.
+
+## Generated Code For Target Languages
+
+When the bindings generator successfully processes an input Mojom file, it emits
+corresponding code for each supported target language. For more details on how
+Mojom concepts translate to a given target langauge, please refer to the
+bindings API documentation for that language:
+
+* [C++ Bindings](/mojo/public/cpp/bindings)
+* [JavaScript Bindings](/mojo/public/js)
+* [Java Bindings](/mojo/public/java/bindings)
+
+## Message Validation
+
+Regardless of target language, all interface messages are validated during
+deserialization before they are dispatched to a receiving implementation of the
+interface. This helps to ensure consitent validation across interfaces without
+leaving the burden to developers and security reviewers every time a new message
+is added.
+
+If a message fails validation, it is never dispatched. Instead a **connection
+error** is raised on the binding object (see
+[C++ Connection Errors](/mojo/public/cpp/bindings#Connection-Errors),
+[Java Connection Errors](/mojo/public/java/bindings#Connection-Errors), or
+[JavaScript Connection Errors](/mojo/public/js#Connection-Errors) for details.)
+
+Some baseline level of validation is done automatically for primitive Mojom
+types.
+
+### Non-Nullable Objects
+
+Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*)
+may be marked nullable in Mojom definitions (see
+[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked
+nullable but a message is received with a null value in its place, that message
+will fail validation.
+
+### Enums
+
+Enums declared in Mojom are automatically validated against the range of legal
+values. For example if a Mojom declares the enum:
+
+``` cpp
+enum AdvancedBoolean {
+ TRUE = 0,
+ FALSE = 1,
+ FILE_NOT_FOUND = 2,
+};
+```
+
+and a message is received with the integral value 3 (or anything other than 0,
+1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will
+fail validation.
+
+*** note
+NOTE: It's possible to avoid this type of validation error by explicitly marking
+an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged
+between two different versions of the binding interface. See
+[Versioning](#Versioning).
+***
+
+### Other failures
+
+There are a host of internal validation errors that may occur when a malformed
+message is received, but developers should not be concerned with these
+specifically; in general they can only result from internal bindings bugs,
+compromised processes, or some remote endpoint making a dubious effort to
+manually encode their own bindings messages.
+
+### Custom Validation
+
+It's also possible for developers to define custom validation logic for specific
+Mojom struct types by exploiting the
+[type mapping](/mojo/public/cpp/bindings#Type-Mapping) system for C++ bindings.
+Messages rejected by custom validation logic trigger the same validation failure
+behavior as the built-in type validation routines.
+
+## Associated Interfaces
+
+As mentioned in the [Primitive Types](#Primitive-Types) section above, interface
+and interface request fields and parameters may be marked as `associated`. This
+essentially means that they are piggy-backed on some other interface's message
+pipe.
+
+Because individual interface message pipes operate independently there can be no
+relative ordering guarantees among them. Associated interfaces are useful when
+one interface needs to guarantee strict FIFO ordering with respect to one or
+more other interfaces, as they allow interfaces to share a single pipe.
+
+Currenly associated interfaces are only supported in generated C++ bindings.
+See the documentation for
+[C++ Associated Interfaces](/mojo/public/cpp/bindings#Associated-Interfaces).
+
+## Versioning
+
+### Overview
+
+*** note
+**NOTE:** You don't need to worry about versioning if you don't care about
+backwards compatibility. Specifically, all parts of Chrome are updated
+atomically today and there is not yet any possibility of any two Chrome
+processes communicating with two different versions of any given Mojom
+interface.
+***
+
+Services extend their interfaces to support new features over time, and clients
+want to use those new features when they are available. If services and clients
+are not updated at the same time, it's important for them to be able to
+communicate with each other using different snapshots (versions) of their
+interfaces.
+
+This document shows how to extend Mojom interfaces in a backwards-compatible
+way. Changing interfaces in a non-backwards-compatible way is not discussed,
+because in that case communication between different interface versions is
+impossible anyway.
+
+### Versioned Structs
+
+You can use the `MinVersion` [attribute](#Attributes) to indicate from which
+version a struct field is introduced. Assume you have the following struct:
+
+``` cpp
+struct Employee {
+ uint64 employee_id;
+ string name;
+};
+```
+
+and you would like to add a birthday field. You can do:
+
+``` cpp
+struct Employee {
+ uint64 employee_id;
+ string name;
+ [MinVersion=1] Date? birthday;
+};
+```
+
+By default, fields belong to version 0. New fields must be appended to the
+struct definition (*i.e*., existing fields must not change **ordinal value**)
+with the `MinVersion` attribute set to a number greater than any previous
+existing versions.
+
+**Ordinal value** refers to the relative positional layout of a struct's fields
+(and an interface's methods) when encoded in a message. Implicitly, ordinal
+numbers are assigned to fields according to lexical position. In the example
+above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value
+of 1.
+
+Ordinal values can be specified explicitly using `**@**` notation, subject to
+the following hard constraints:
+
+* For any given struct or interface, if any field or method explicitly specifies
+ an ordinal value, all fields or methods must explicitly specify an ordinal
+ value.
+* For an *N*-field struct or *N*-method interface, the set of explicitly
+ assigned ordinal values must be limited to the range *[0, N-1]*.
+
+You may reorder fields, but you must ensure that the ordinal values of existing
+fields remain unchanged. For example, the following struct remains
+backwards-compatible:
+
+``` cpp
+struct Employee {
+ uint64 employee_id@0;
+ [MinVersion=1] Date? birthday@2;
+ string name@1;
+};
+```
+
+*** note
+**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable.
+See [Primitive Types](#Primitive-Types).
+***
+
+### Versioned Interfaces
+
+There are two dimensions on which an interface can be extended
+
+**Appending New Parameters To Existing Methods**
+: Parameter lists are treated as structs internally, so all the rules of
+ versioned structs apply to method parameter lists. The only difference is
+ that the version number is scoped to the whole interface rather than to any
+ individual parameter list.
+
+ Please note that adding a response to a message which did not previously
+ expect a response is a not a backwards-compatible change.
+
+**Appending New Methods**
+: Similarly, you can reorder methods with explicit ordinal values as long as
+ the ordinal values of existing methods are unchanged.
+
+For example:
+
+``` cpp
+// Old version:
+interface HumanResourceDatabase {
+ AddEmployee(Employee employee) => (bool success);
+ QueryEmployee(uint64 id) => (Employee? employee);
+};
+
+// New version:
+interface HumanResourceDatabase {
+ AddEmployee(Employee employee) => (bool success);
+
+ QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
+ => (Employee? employee,
+ [MinVersion=1] array<uint8>? finger_print);
+
+ [MinVersion=1]
+ AttachFingerPrint(uint64 id, array<uint8> finger_print)
+ => (bool success);
+};
+```
+
+Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
+list of a request or response method to a destination using an older version of
+an interface, unrecognized fields are silently discarded. However, if the method
+call itself is not recognized, it is considered a validation error and the
+receiver will close its end of the interface pipe. For example, if a client on
+version 1 of the above interface sends an `AttachFingerPrint` request to an
+implementation of version 0, the client will be disconnected.
+
+Bindings target languages that support versioning expose means to query or
+assert the remote version from a client handle (*e.g.*, an
+`InterfacePtr<T>` in C++ bindings.)
+
+See
+[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations)
+and [Java Versioning Considerations](/mojo/public/java/bindings#Versioning-Considerations)
+
+### Versioned Enums
+
+**By default, enums are non-extensible**, which means that generated message
+validation code does not expect to see new values in the future. When an unknown
+value is seen for a non-extensible enum field or parameter, a validation error
+is raised.
+
+If you want an enum to be extensible in the future, you can apply the
+`[Extensible]` [attribute](#Attributes):
+
+``` cpp
+[Extensible]
+enum Department {
+ SALES,
+ DEV,
+};
+```
+
+And later you can extend this enum without breaking backwards compatibility:
+
+``` cpp
+[Extensible]
+enum Department {
+ SALES,
+ DEV,
+ [MinVersion=1] RESEARCH,
+};
+```
+
+*** note
+**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute
+is strictly for documentation purposes. It has no impact on the generated code.
+***
+
+With extensible enums, bound interface implementations may receive unknown enum
+values and will need to deal with them gracefully. See
+[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations)
+for details.
+
+## Grammar Reference
+
+Below is the (BNF-ish) context-free grammar of the Mojom language:
+
+```
+MojomFile = StatementList
+StatementList = Statement StatementList | Statement
+Statement = ModuleStatement | ImportStatement | Definition
+
+ModuleStatement = AttributeSection "module" Identifier ";"
+ImportStatement = "import" StringLiteral ";"
+Definition = Struct Union Interface Enum Const
+
+AttributeSection = "[" AttributeList "]"
+AttributeList = <empty> | NonEmptyAttributeList
+NonEmptyAttributeList = Attribute
+ | Attribute "," NonEmptyAttributeList
+Attribute = Name
+ | Name "=" Name
+ | Name "=" Literal
+
+Struct = AttributeSection "struct" Name "{" StructBody "}" ";"
+ | AttributeSection "struct" Name ";"
+StructBody = <empty>
+ | StructBody Const
+ | StructBody Enum
+ | StructBody StructField
+StructField = AttributeSection TypeSpec Name Orginal Default ";"
+
+Union = AttributeSection "union" Name "{" UnionBody "}" ";"
+UnionBody = <empty> | UnionBody UnionField
+UnionField = AttributeSection TypeSpec Name Ordinal ";"
+
+Interface = AttributeSection "interface" Name "{" InterfaceBody "}" ";"
+InterfaceBody = <empty>
+ | InterfaceBody Const
+ | InterfaceBody Enum
+ | InterfaceBody Method
+Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";"
+ParameterList = <empty> | NonEmptyParameterList
+NonEmptyParameterList = Parameter
+ | Parameter "," NonEmptyParameterList
+Parameter = AttributeSection TypeSpec Name Ordinal
+Response = <empty> | "=>" "(" ParameterList ")"
+
+TypeSpec = TypeName "?" | TypeName
+TypeName = BasicTypeName
+ | Array
+ | FixedArray
+ | Map
+ | InterfaceRequest
+BasicTypeName = Identifier | "associated" Identifier | HandleType | NumericType
+NumericType = "bool" | "int8" | "uint8" | "int16" | "uint16" | "int32"
+ | "uint32" | "int64" | "uint64" | "float" | "double"
+HandleType = "handle" | "handle" "<" SpecificHandleType ">"
+SpecificHandleType = "message_pipe"
+ | "shared_buffer"
+ | "data_pipe_consumer"
+ | "data_pipe_producer"
+Array = "array" "<" TypeSpec ">"
+FixedArray = "array" "<" TypeSpec "," IntConstDec ">"
+Map = "map" "<" Identifier "," TypeSpec ">"
+InterfaceRequest = Identifier "&" | "associated" Identifier "&"
+
+Ordinal = <empty> | OrdinalValue
+
+Default = <empty> | "=" Constant
+
+Enum = AttributeSection "enum" Name "{" NonEmptyEnumValueList "}" ";"
+ | AttributeSection "enum" Name "{" NonEmptyEnumValueList "," "}" ";"
+NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList "," EnumValue
+EnumValue = AttributeSection Name
+ | AttributeSection Name "=" Integer
+ | AttributeSection Name "=" Identifier
+
+Const = "const" TypeSpec Name "=" Constant ";"
+
+Constant = Literal | Identifier ";"
+
+Identifier = Name | Name "." Identifier
+
+Literal = Integer | Float | "true" | "false" | "default" | StringLiteral
+
+Integer = IntConst | "+" IntConst | "-" IntConst
+IntConst = IntConstDec | IntConstHex
+
+Float = FloatConst | "+" FloatConst | "-" FloatConst
+
+; The rules below are for tokens matched strictly according to the given regexes
+
+Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/
+IntConstDec = /0|(1-9[0-9]*)/
+IntConstHex = /0[xX][0-9a-fA-F]+/
+OrdinalValue = /@(0|(1-9[0-9]*))/
+FloatConst = ... # Imagine it's close enough to C-style float syntax.
+StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes.
+```
+
+## Additional Documentation
+
+[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit)
+: Describes the wire format used by Mojo bindings interfaces over message
+ pipes.
+
+[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit)
+: Describes a text format used to facilitate bindings message validation
+ tests.
diff --git a/mojo/public/tools/bindings/blink_bindings_configuration.gni b/mojo/public/tools/bindings/blink_bindings_configuration.gni
new file mode 100644
index 0000000000..bb0fc432a3
--- /dev/null
+++ b/mojo/public/tools/bindings/blink_bindings_configuration.gni
@@ -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.
+
+variant = "blink"
+
+for_blink = true
+
+_typemap_imports = [
+ "//mojo/public/cpp/bindings/tests/blink_typemaps.gni",
+ "//third_party/WebKit/Source/platform/mojo/blink_typemaps.gni",
+ "//third_party/WebKit/public/blink_typemaps.gni",
+ "//third_party/WebKit/public/public_typemaps.gni",
+]
+_typemaps = []
+
+foreach(typemap_import, _typemap_imports) {
+ # Avoid reassignment error by assigning to empty scope first.
+ _imported = {
+ }
+ _imported = read_file(typemap_import, "scope")
+ _typemaps += _imported.typemaps
+}
+
+typemaps = []
+foreach(typemap, _typemaps) {
+ typemaps += [ {
+ filename = typemap
+ config = read_file(typemap, "scope")
+ } ]
+}
+
+blacklist = []
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
new file mode 100644
index 0000000000..ca36723fb0
--- /dev/null
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -0,0 +1,83 @@
+# 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.
+
+_typemap_imports = [
+ "//ash/public/interfaces/typemaps.gni",
+ "//cc/ipc/typemaps.gni",
+ "//chrome/browser/media/router/mojo/typemaps.gni",
+ "//chrome/common/extensions/typemaps.gni",
+ "//chrome/common/importer/typemaps.gni",
+ "//chrome/typemaps.gni",
+ "//components/arc/common/typemaps.gni",
+ "//components/metrics/public/cpp/typemaps.gni",
+ "//components/typemaps.gni",
+ "//content/common/bluetooth/typemaps.gni",
+ "//content/common/indexed_db/typemaps.gni",
+ "//content/common/presentation/typemaps.gni",
+ "//content/common/typemaps.gni",
+ "//content/public/common/typemaps.gni",
+ "//device/bluetooth/public/interfaces/typemaps.gni",
+ "//device/gamepad/public/interfaces/typemaps.gni",
+ "//device/generic_sensor/public/interfaces/typemaps.gni",
+ "//device/usb/public/interfaces/typemaps.gni",
+ "//extensions/common/typemaps.gni",
+ "//gpu/ipc/common/typemaps.gni",
+ "//media/capture/mojo/typemaps.gni",
+ "//media/mojo/interfaces/typemaps.gni",
+ "//mojo/common/typemaps.gni",
+ "//mojo/public/cpp/bindings/tests/chromium_typemaps.gni",
+ "//net/interfaces/typemaps.gni",
+ "//services/preferences/public/cpp/typemaps.gni",
+ "//services/resource_coordinator/public/cpp/typemaps.gni",
+ "//services/service_manager/public/cpp/typemaps.gni",
+ "//services/ui/gpu/interfaces/typemaps.gni",
+ "//services/ui/public/interfaces/cursor/typemaps.gni",
+ "//services/ui/public/interfaces/ime/typemaps.gni",
+ "//services/video_capture/public/interfaces/typemaps.gni",
+ "//skia/public/interfaces/typemaps.gni",
+ "//third_party/WebKit/public/public_typemaps.gni",
+ "//ui/base/mojo/typemaps.gni",
+ "//ui/display/mojo/typemaps.gni",
+ "//ui/events/devices/mojo/typemaps.gni",
+ "//ui/events/mojo/typemaps.gni",
+ "//ui/gfx/typemaps.gni",
+ "//ui/latency/mojo/typemaps.gni",
+ "//ui/message_center/mojo/typemaps.gni",
+ "//url/mojo/typemaps.gni",
+]
+
+_typemap_imports_mac = [ "//content/common/typemaps_mac.gni" ]
+
+_typemaps = []
+foreach(typemap_import, _typemap_imports) {
+ # Avoid reassignment error by assigning to empty scope first.
+ _imported = {
+ }
+ _imported = read_file(typemap_import, "scope")
+ _typemaps += _imported.typemaps
+}
+
+typemaps = []
+foreach(typemap, _typemaps) {
+ typemaps += [ {
+ filename = typemap
+ config = read_file(typemap, "scope")
+ } ]
+}
+
+_typemaps_mac = []
+foreach(typemap_import, _typemap_imports_mac) {
+ _imported = {
+ }
+ _imported = read_file(typemap_import, "scope")
+ _typemaps_mac += _imported.typemaps
+}
+
+typemaps_mac = []
+foreach(typemap, _typemaps_mac) {
+ typemaps_mac += [ {
+ filename = typemap
+ config = read_file(typemap, "scope")
+ } ]
+}
diff --git a/mojo/public/tools/bindings/format_typemap_generator_args.py b/mojo/public/tools/bindings/format_typemap_generator_args.py
new file mode 100755
index 0000000000..5057d6cdac
--- /dev/null
+++ b/mojo/public/tools/bindings/format_typemap_generator_args.py
@@ -0,0 +1,34 @@
+#!/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 sys
+
+# This utility converts mojom dependencies into their corresponding typemap
+# paths and formats them to be consumed by generate_type_mappings.py.
+
+
+def FormatTypemap(typemap_filename):
+ # A simple typemap is valid Python with a minor alteration.
+ with open(typemap_filename) as f:
+ typemap_content = f.read().replace('=\n', '=')
+ typemap = {}
+ exec typemap_content in typemap
+
+ for header in typemap.get('public_headers', []):
+ yield 'public_headers=%s' % header
+ for header in typemap.get('traits_headers', []):
+ yield 'traits_headers=%s' % header
+ for header in typemap.get('type_mappings', []):
+ yield 'type_mappings=%s' % header
+
+
+def main():
+ typemaps = sys.argv[1:]
+ print ' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))
+ for typemap in typemaps)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/mojo/public/tools/bindings/generate_type_mappings.py b/mojo/public/tools/bindings/generate_type_mappings.py
new file mode 100755
index 0000000000..824f8045ab
--- /dev/null
+++ b/mojo/public/tools/bindings/generate_type_mappings.py
@@ -0,0 +1,148 @@
+#!/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.
+"""Generates a JSON typemap from its command-line arguments and dependencies.
+
+Each typemap should be specified in an command-line argument of the form
+key=value, with an argument of "--start-typemap" preceding each typemap.
+
+For example,
+generate_type_mappings.py --output=foo.typemap --start-typemap \\
+ public_headers=foo.h traits_headers=foo_traits.h \\
+ type_mappings=mojom.Foo=FooImpl
+
+generates a foo.typemap containing
+{
+ "c++": {
+ "mojom.Foo": {
+ "typename": "FooImpl",
+ "traits_headers": [
+ "foo_traits.h"
+ ],
+ "public_headers": [
+ "foo.h"
+ ]
+ }
+ }
+}
+
+Then,
+generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\
+ --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\
+ type_mappings=mojom.Bar=BarImpl
+
+generates a bar.typemap containing
+{
+ "c++": {
+ "mojom.Bar": {
+ "typename": "BarImpl",
+ "traits_headers": [
+ "bar_traits.h"
+ ],
+ "public_headers": [
+ "bar.h"
+ ]
+ },
+ "mojom.Foo": {
+ "typename": "FooImpl",
+ "traits_headers": [
+ "foo_traits.h"
+ ],
+ "public_headers": [
+ "foo.h"
+ ]
+ }
+ }
+}
+"""
+
+import argparse
+import json
+import os
+import re
+
+
+def ReadTypemap(path):
+ with open(path) as f:
+ return json.load(f)['c++']
+
+
+def ParseTypemapArgs(args):
+ typemaps = [s for s in '\n'.join(args).split('--start-typemap\n') if s]
+ result = {}
+ for typemap in typemaps:
+ result.update(ParseTypemap(typemap))
+ return result
+
+
+def ParseTypemap(typemap):
+ values = {'type_mappings': [], 'public_headers': [], 'traits_headers': []}
+ for line in typemap.split('\n'):
+ if not line:
+ continue
+ key, _, value = line.partition('=')
+ values[key].append(value.lstrip('/'))
+ result = {}
+ mapping_pattern = \
+ re.compile(r"""^([^=]+) # mojom type
+ =
+ ([^[]+) # native type
+ (?:\[([^]]+)\])?$ # optional attribute in square brackets
+ """, re.X)
+ for typename in values['type_mappings']:
+ match_result = mapping_pattern.match(typename)
+ assert match_result, (
+ "Cannot parse entry in the \"type_mappings\" section: %s" % typename)
+
+ mojom_type = match_result.group(1)
+ native_type = match_result.group(2)
+ attributes = []
+ if match_result.group(3):
+ attributes = match_result.group(3).split(',')
+
+ assert mojom_type not in result, (
+ "Cannot map multiple native types (%s, %s) to the same mojom type: %s" %
+ (result[mojom_type]['typename'], native_type, mojom_type))
+
+ result[mojom_type] = {
+ 'typename': native_type,
+ 'non_copyable_non_movable': 'non_copyable_non_movable' in attributes,
+ 'move_only': 'move_only' in attributes,
+ 'copyable_pass_by_value': 'copyable_pass_by_value' in attributes,
+ 'nullable_is_same_type': 'nullable_is_same_type' in attributes,
+ 'hashable': 'hashable' in attributes,
+ 'public_headers': values['public_headers'],
+ 'traits_headers': values['traits_headers'],
+ }
+ return result
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument(
+ '--dependency',
+ type=str,
+ action='append',
+ default=[],
+ help=('A path to another JSON typemap to merge into the output. '
+ 'This may be repeated to merge multiple typemaps.'))
+ parser.add_argument('--output',
+ type=str,
+ required=True,
+ help='The path to which to write the generated JSON.')
+ params, typemap_params = parser.parse_known_args()
+ typemaps = ParseTypemapArgs(typemap_params)
+ missing = [path for path in params.dependency if not os.path.exists(path)]
+ if missing:
+ raise IOError('Missing dependencies: %s' % ', '.join(missing))
+ for path in params.dependency:
+ typemaps.update(ReadTypemap(path))
+ with open(params.output, 'w') as f:
+ json.dump({'c++': typemaps}, f, indent=2)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
new file mode 100644
index 0000000000..f0d503e5e0
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
@@ -0,0 +1,131 @@
+{#---
+ Macro for enum definition, and the declaration of associated functions.
+---#}
+
+{%- macro enum_decl(enum) %}
+{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %}
+enum class {{enum_name}} : int32_t {
+{%- for field in enum.fields %}
+{%- if field.value %}
+ {{field.name}} = {{field.value|expression_to_text}},
+{%- else %}
+ {{field.name}},
+{%- endif %}
+{%- endfor %}
+};
+
+inline std::ostream& operator<<(std::ostream& os, {{enum_name}} value) {
+{%- if enum.fields %}
+ switch(value) {
+{%- for _, values in enum.fields|groupby('numeric_value') %}
+ case {{enum_name}}::{{values[0].name}}:
+ return os << "{{enum_name}}::
+{%- if values|length > 1 -%}
+ {{'{'}}
+{%- endif -%}
+ {{values|map(attribute='name')|join(', ')}}
+{%- if values|length > 1 -%}
+ {{'}'}}
+{%- endif -%}
+ ";
+{%- endfor %}
+ default:
+ return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value);
+ }
+{%- else %}
+ return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value);
+{%- endif %}
+}
+
+{#- Returns true if the given enum value exists in this version of enum. #}
+inline bool IsKnownEnumValue({{enum_name}} value) {
+ return {{enum|get_name_for_kind(internal=True,
+ flatten_nested_kind=True)}}::IsKnownValue(
+ static_cast<int32_t>(value));
+}
+{%- endmacro %}
+
+{%- macro enum_data_decl(enum) %}
+{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %}
+struct {{enum_name}}_Data {
+ public:
+ static bool constexpr kIsExtensible = {% if enum.extensible %}true{% else %}false{% endif %};
+
+ static bool IsKnownValue(int32_t value) {
+{%- if enum.fields %}
+ switch (value) {
+{%- for enum_field in enum.fields|groupby('numeric_value') %}
+ case {{enum_field[0]}}:
+{%- endfor %}
+ return true;
+ }
+{%- endif %}
+ return false;
+ }
+
+ static bool Validate(int32_t value,
+ mojo::internal::ValidationContext* validation_context) {
+ if (kIsExtensible || IsKnownValue(value))
+ return true;
+
+ ReportValidationError(validation_context,
+ mojo::internal::VALIDATION_ERROR_UNKNOWN_ENUM_VALUE);
+ return false;
+ }
+};
+{%- endmacro %}
+
+{%- macro enum_hash(enum) %}
+{%- set enum_name = enum|get_qualified_name_for_kind(
+ flatten_nested_kind=True) %}
+template <>
+struct hash<{{enum_name}}>
+ : public mojo::internal::EnumHashImpl<{{enum_name}}> {};
+{%- endmacro %}
+
+{%- macro enum_hash_blink(enum) %}
+{%- set enum_name = enum|get_qualified_name_for_kind(
+ flatten_nested_kind=True, include_variant=False) %}
+{%- set hash_fn_name = enum|wtf_hash_fn_name_for_enum %}
+{# We need two unused enum values: #}
+{%- set empty_value = -1000000 %}
+{%- set deleted_value = -1000001 %}
+{%- set empty_value_unused = "false" if empty_value in enum|all_enum_values else "true" %}
+{%- set deleted_value_unused = "false" if empty_value in enum|all_enum_values else "true" %}
+namespace WTF {
+struct {{hash_fn_name}} {
+ static unsigned hash(const {{enum_name}}& value) {
+ typedef base::underlying_type<{{enum_name}}>::type utype;
+ return DefaultHash<utype>::Hash().hash(static_cast<utype>(value));
+ }
+ static bool equal(const {{enum_name}}& left, const {{enum_name}}& right) {
+ return left == right;
+ }
+ static const bool safeToCompareToEmptyOrDeleted = true;
+};
+
+template <>
+struct DefaultHash<{{enum_name}}> {
+ using Hash = {{hash_fn_name}};
+};
+
+template <>
+struct HashTraits<{{enum_name}}>
+ : public GenericHashTraits<{{enum_name}}> {
+ static_assert({{empty_value_unused}},
+ "{{empty_value}} is a reserved enum value");
+ static_assert({{deleted_value_unused}},
+ "{{deleted_value}} is a reserved enum value");
+ static const bool hasIsEmptyValueFunction = true;
+ static bool isEmptyValue(const {{enum_name}}& value) {
+ return value == static_cast<{{enum_name}}>({{empty_value}});
+ }
+ static void constructDeletedValue({{enum_name}}& slot, bool) {
+ slot = static_cast<{{enum_name}}>({{deleted_value}});
+ }
+ static bool isDeletedValue(const {{enum_name}}& value) {
+ return value == static_cast<{{enum_name}}>({{deleted_value}});
+ }
+};
+} // namespace WTF
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl
new file mode 100644
index 0000000000..d7d0e5d873
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl
@@ -0,0 +1,29 @@
+{%- set mojom_type = enum|get_qualified_name_for_kind(
+ flatten_nested_kind=True) %}
+
+template <>
+struct EnumTraits<{{mojom_type}}, {{mojom_type}}> {
+ static {{mojom_type}} ToMojom({{mojom_type}} input) { return input; }
+ static bool FromMojom({{mojom_type}} input, {{mojom_type}}* output) {
+ *output = input;
+ return true;
+ }
+};
+
+namespace internal {
+
+template <typename MaybeConstUserType>
+struct Serializer<{{mojom_type}}, MaybeConstUserType> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = EnumTraits<{{mojom_type}}, UserType>;
+
+ static void Serialize(UserType input, int32_t* output) {
+ *output = static_cast<int32_t>(Traits::ToMojom(input));
+ }
+
+ static bool Deserialize(int32_t input, UserType* output) {
+ return Traits::FromMojom(static_cast<{{mojom_type}}>(input), output);
+ }
+};
+
+} // namespace internal
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
new file mode 100644
index 0000000000..7f6497475a
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
@@ -0,0 +1,65 @@
+{%- import "interface_macros.tmpl" as interface_macros %}
+class {{interface.name}}Proxy;
+
+template <typename ImplRefTraits>
+class {{interface.name}}Stub;
+
+class {{interface.name}}RequestValidator;
+{%- if interface|has_callbacks %}
+class {{interface.name}}ResponseValidator;
+{%- endif %}
+
+class {{export_attribute}} {{interface.name}}
+ : public {{interface.name}}InterfaceBase {
+ public:
+ static const char Name_[];
+ static constexpr uint32_t Version_ = {{interface.version}};
+ static constexpr bool PassesAssociatedKinds_ = {% if interface|passes_associated_kinds %}true{% else %}false{% endif %};
+ static constexpr bool HasSyncMethods_ = {% if interface|has_sync_methods %}true{% else %}false{% endif %};
+
+ using Proxy_ = {{interface.name}}Proxy;
+
+ template <typename ImplRefTraits>
+ using Stub_ = {{interface.name}}Stub<ImplRefTraits>;
+
+ using RequestValidator_ = {{interface.name}}RequestValidator;
+{%- if interface|has_callbacks %}
+ using ResponseValidator_ = {{interface.name}}ResponseValidator;
+{%- else %}
+ using ResponseValidator_ = mojo::PassThroughFilter;
+{%- endif %}
+
+{#--- Metadata #}
+ enum MethodMinVersions : uint32_t {
+{%- for method in interface.methods %}
+ k{{method.name}}MinVersion = {{method.min_version|default(0, true)}},
+{%- endfor %}
+ };
+
+{#--- Enums #}
+{%- for enum in interface.enums %}
+ using {{enum.name}} = {{enum|get_name_for_kind(flatten_nested_kind=True)}};
+{%- endfor %}
+
+{#--- Constants #}
+{%- for constant in interface.constants %}
+ static {{constant|format_constant_declaration(nested=True)}};
+{%- endfor %}
+
+{#--- Methods #}
+ virtual ~{{interface.name}}() {}
+
+{%- for method in interface.methods %}
+{% if method.response_parameters != None %}
+{%- if method.sync %}
+ // Sync method. This signature is used by the client side; the service side
+ // should implement the signature with callback below.
+ virtual bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}});
+{%- endif %}
+
+ using {{method.name}}Callback = {{interface_macros.declare_callback(method,
+ for_blink, use_once_callback)}};
+{%- endif %}
+ virtual void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) = 0;
+{%- endfor %}
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
new file mode 100644
index 0000000000..aba18380af
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
@@ -0,0 +1,448 @@
+{%- import "interface_macros.tmpl" as interface_macros %}
+{%- import "struct_macros.tmpl" as struct_macros %}
+
+{%- set class_name = interface.name %}
+{%- set proxy_name = interface.name ~ "Proxy" %}
+{%- set namespace_as_string = "%s"|format(namespace|replace(".","::")) %}
+
+{%- macro alloc_params(struct, params, message, description) %}
+ mojo::internal::SerializationContext serialization_context;
+ serialization_context.handles.Swap(({{message}})->mutable_handles());
+ serialization_context.associated_endpoint_handles.swap(
+ *({{message}})->mutable_associated_endpoint_handles());
+ bool success = true;
+{%- for param in struct.packed.packed_fields_in_ordinal_order %}
+ {{param.field.kind|cpp_wrapper_type}} p_{{param.field.name}}{};
+{%- endfor %}
+ {{struct.name}}DataView input_data_view({{params}}, &serialization_context);
+ {{struct_macros.deserialize(struct, "input_data_view", "p_%s", "success")}}
+ if (!success) {
+ ReportValidationErrorForMessage(
+ {{message}},
+ mojo::internal::VALIDATION_ERROR_DESERIALIZATION_FAILED,
+ "{{description}} deserializer");
+ return false;
+ }
+{%- endmacro %}
+
+{%- macro pass_params(parameters) %}
+{%- for param in parameters %}
+std::move(p_{{param.name}})
+{%- if not loop.last %}, {% endif %}
+{%- endfor %}
+{%- endmacro %}
+
+{%- macro build_message(struct, input_pattern, struct_display_name,
+ serialization_context) -%}
+ {{struct_macros.serialize(struct, struct_display_name, input_pattern,
+ "params", "builder.buffer()",
+ serialization_context)}}
+ ({{serialization_context}})->handles.Swap(
+ builder.message()->mutable_handles());
+ ({{serialization_context}})->associated_endpoint_handles.swap(
+ *builder.message()->mutable_associated_endpoint_handles());
+{%- endmacro %}
+
+{#--- Begin #}
+const char {{class_name}}::Name_[] = "{{namespace_as_string}}::{{class_name}}";
+
+{#--- Constants #}
+{%- for constant in interface.constants %}
+{%- if constant.kind|is_string_kind %}
+const char {{interface.name}}::{{constant.name}}[] = {{constant|constant_value}};
+{%- endif %}
+{%- endfor %}
+
+
+{%- for method in interface.methods %}
+{%- if method.sync %}
+bool {{class_name}}::{{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) {
+ NOTREACHED();
+ return false;
+}
+{%- endif %}
+{%- endfor %}
+
+{#--- ForwardToCallback definition #}
+{%- for method in interface.methods -%}
+{%- if method.response_parameters != None %}
+{%- if method.sync %}
+class {{class_name}}_{{method.name}}_HandleSyncResponse
+ : public mojo::MessageReceiver {
+ public:
+ {{class_name}}_{{method.name}}_HandleSyncResponse(
+ bool* result
+{%- for param in method.response_parameters -%}
+ , {{param.kind|cpp_wrapper_type}}* out_{{param.name}}
+{%- endfor %})
+ : result_(result)
+{%- for param in method.response_parameters -%}
+ , out_{{param.name}}_(out_{{param.name}})
+{%- endfor %} {
+ DCHECK(!*result_);
+ }
+ bool Accept(mojo::Message* message) override;
+ private:
+ bool* result_;
+{%- for param in method.response_parameters %}
+ {{param.kind|cpp_wrapper_type}}* out_{{param.name}}_;
+{%- endfor -%}
+ DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_HandleSyncResponse);
+};
+bool {{class_name}}_{{method.name}}_HandleSyncResponse::Accept(
+ mojo::Message* message) {
+ internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params =
+ reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>(
+ message->mutable_payload());
+
+{%- set desc = class_name~"::"~method.name~" response" %}
+ {{alloc_params(method.response_param_struct, "params", "message", desc)}}
+
+{%- for param in method.response_parameters %}
+ *out_{{param.name}}_ = std::move(p_{{param.name}});
+{%- endfor %}
+ mojo::internal::SyncMessageResponseSetup::SetCurrentSyncResponseMessage(
+ message);
+ *result_ = true;
+ return true;
+}
+{%- endif %}
+
+class {{class_name}}_{{method.name}}_ForwardToCallback
+ : public mojo::MessageReceiver {
+ public:
+ {{class_name}}_{{method.name}}_ForwardToCallback(
+{%- if use_once_callback %}
+ {{class_name}}::{{method.name}}Callback callback
+{%- else %}
+ const {{class_name}}::{{method.name}}Callback& callback
+{%- endif %}
+ ) : callback_(std::move(callback)) {
+ }
+ bool Accept(mojo::Message* message) override;
+ private:
+ {{class_name}}::{{method.name}}Callback callback_;
+ DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ForwardToCallback);
+};
+bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept(
+ mojo::Message* message) {
+ internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params =
+ reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>(
+ message->mutable_payload());
+
+{%- set desc = class_name~"::"~method.name~" response" %}
+ {{alloc_params(method.response_param_struct, "params", "message", desc)}}
+ if (!callback_.is_null()) {
+ mojo::internal::MessageDispatchContext context(message);
+ std::move(callback_).Run({{pass_params(method.response_parameters)}});
+ }
+ return true;
+}
+{%- endif %}
+{%- endfor %}
+
+{{proxy_name}}::{{proxy_name}}(mojo::MessageReceiverWithResponder* receiver)
+ : receiver_(receiver) {
+}
+
+{#--- Proxy definitions #}
+
+{%- for method in interface.methods %}
+{%- set message_name =
+ "internal::k%s_%s_Name"|format(interface.name, method.name) %}
+{%- set params_struct = method.param_struct %}
+{%- set params_description =
+ "%s.%s request"|format(interface.name, method.name) %}
+{%- if method.sync %}
+bool {{proxy_name}}::{{method.name}}(
+ {{interface_macros.declare_sync_method_params("param_", method)}}) {
+ mojo::internal::SerializationContext serialization_context;
+ {{struct_macros.get_serialized_size(params_struct, "param_%s",
+ "&serialization_context")}}
+
+ mojo::internal::MessageBuilder builder(
+ {{message_name}},
+ mojo::Message::kFlagIsSync | mojo::Message::kFlagExpectsResponse,
+ size, serialization_context.associated_endpoint_count);
+
+ {{build_message(params_struct, "param_%s", params_description,
+ "&serialization_context")}}
+
+ bool result = false;
+ std::unique_ptr<mojo::MessageReceiver> responder(
+ new {{class_name}}_{{method.name}}_HandleSyncResponse(
+ &result
+{%- for param in method.response_parameters -%}
+ , param_{{param.name}}
+{%- endfor %}));
+ ignore_result(receiver_->AcceptWithResponder(builder.message(),
+ std::move(responder)));
+ return result;
+}
+{%- endif %}
+
+void {{proxy_name}}::{{method.name}}(
+ {{interface_macros.declare_request_params("in_", method, use_once_callback)}}) {
+ mojo::internal::SerializationContext serialization_context;
+ {{struct_macros.get_serialized_size(params_struct, "in_%s",
+ "&serialization_context")}}
+
+{%- if method.response_parameters != None %}
+ constexpr uint32_t kFlags = mojo::Message::kFlagExpectsResponse;
+{%- else %}
+ constexpr uint32_t kFlags = 0;
+{%- endif %}
+ mojo::internal::MessageBuilder builder(
+ {{message_name}}, kFlags, size,
+ serialization_context.associated_endpoint_count);
+
+ {{build_message(params_struct, "in_%s", params_description,
+ "&serialization_context")}}
+
+{%- if method.response_parameters != None %}
+ std::unique_ptr<mojo::MessageReceiver> responder(
+ new {{class_name}}_{{method.name}}_ForwardToCallback(
+ std::move(callback)));
+ ignore_result(receiver_->AcceptWithResponder(builder.message(),
+ std::move(responder)));
+{%- else %}
+ // This return value may be ignored as false implies the Connector has
+ // encountered an error, which will be visible through other means.
+ ignore_result(receiver_->Accept(builder.message()));
+{%- endif %}
+}
+{%- endfor %}
+
+{#--- ProxyToResponder definition #}
+{%- for method in interface.methods -%}
+{%- if method.response_parameters != None %}
+{%- set message_name =
+ "internal::k%s_%s_Name"|format(interface.name, method.name) %}
+{%- set response_params_struct = method.response_param_struct %}
+{%- set params_description =
+ "%s.%s response"|format(interface.name, method.name) %}
+class {{class_name}}_{{method.name}}_ProxyToResponder {
+ public:
+ static {{class_name}}::{{method.name}}Callback CreateCallback(
+ uint64_t request_id,
+ bool is_sync,
+ std::unique_ptr<mojo::MessageReceiverWithStatus> responder) {
+ std::unique_ptr<{{class_name}}_{{method.name}}_ProxyToResponder> proxy(
+ new {{class_name}}_{{method.name}}_ProxyToResponder(
+ request_id, is_sync, std::move(responder)));
+ return base::Bind(&{{class_name}}_{{method.name}}_ProxyToResponder::Run,
+ base::Passed(&proxy));
+ }
+
+ ~{{class_name}}_{{method.name}}_ProxyToResponder() {
+#if DCHECK_IS_ON()
+ if (responder_) {
+ // Is the Service destroying the callback without running it
+ // and without first closing the pipe?
+ responder_->DCheckInvalid("The callback passed to "
+ "{{class_name}}::{{method.name}}() was never run.");
+ }
+#endif
+ // If the Callback was dropped then deleting the responder will close
+ // the pipe so the calling application knows to stop waiting for a reply.
+ responder_ = nullptr;
+ }
+
+ private:
+ {{class_name}}_{{method.name}}_ProxyToResponder(
+ uint64_t request_id,
+ bool is_sync,
+ std::unique_ptr<mojo::MessageReceiverWithStatus> responder)
+ : request_id_(request_id),
+ is_sync_(is_sync),
+ responder_(std::move(responder)) {
+ }
+
+ void Run(
+ {{interface_macros.declare_responder_params(
+ "in_", method.response_parameters, for_blink)}});
+
+ uint64_t request_id_;
+ bool is_sync_;
+ std::unique_ptr<mojo::MessageReceiverWithStatus> responder_;
+
+ DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder);
+};
+
+void {{class_name}}_{{method.name}}_ProxyToResponder::Run(
+ {{interface_macros.declare_responder_params(
+ "in_", method.response_parameters, for_blink)}}) {
+ mojo::internal::SerializationContext serialization_context;
+ {{struct_macros.get_serialized_size(response_params_struct, "in_%s",
+ "&serialization_context")}}
+
+ uint32_t flags = (is_sync_ ? mojo::Message::kFlagIsSync : 0) |
+ mojo::Message::kFlagIsResponse;
+ mojo::internal::MessageBuilder builder(
+ {{message_name}}, flags, size,
+ serialization_context.associated_endpoint_count);
+ builder.message()->set_request_id(request_id_);
+
+ {{build_message(response_params_struct, "in_%s", params_description,
+ "&serialization_context")}}
+ ignore_result(responder_->Accept(builder.message()));
+ // TODO(darin): Accept() returning false indicates a malformed message, and
+ // that may be good reason to close the connection. However, we don't have a
+ // way to do that from here. We should add a way.
+ responder_ = nullptr;
+}
+{%- endif -%}
+{%- endfor %}
+
+{#--- StubDispatch definition #}
+
+// static
+bool {{class_name}}StubDispatch::Accept(
+ {{interface.name}}* impl,
+ mojo::Message* message) {
+{%- if interface.methods %}
+ switch (message->header()->name) {
+{%- for method in interface.methods %}
+ case internal::k{{class_name}}_{{method.name}}_Name: {
+{%- if method.response_parameters == None %}
+ internal::{{class_name}}_{{method.name}}_Params_Data* params =
+ reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>(
+ message->mutable_payload());
+
+{%- set desc = class_name~"::"~method.name %}
+ {{alloc_params(method.param_struct, "params", "message", desc)|
+ indent(4)}}
+ // A null |impl| means no implementation was bound.
+ assert(impl);
+ TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}");
+ mojo::internal::MessageDispatchContext context(message);
+ impl->{{method.name}}({{pass_params(method.parameters)}});
+ return true;
+{%- else %}
+ break;
+{%- endif %}
+ }
+{%- endfor %}
+ }
+{%- endif %}
+ return false;
+}
+
+// static
+bool {{class_name}}StubDispatch::AcceptWithResponder(
+ {{interface.name}}* impl,
+ mojo::Message* message,
+ std::unique_ptr<mojo::MessageReceiverWithStatus> responder) {
+{%- if interface.methods %}
+ switch (message->header()->name) {
+{%- for method in interface.methods %}
+ case internal::k{{class_name}}_{{method.name}}_Name: {
+{%- if method.response_parameters != None %}
+ internal::{{class_name}}_{{method.name}}_Params_Data* params =
+ reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>(
+ message->mutable_payload());
+
+{%- set desc = class_name~"::"~method.name %}
+ {{alloc_params(method.param_struct, "params", "message", desc)|
+ indent(4)}}
+ {{class_name}}::{{method.name}}Callback callback =
+ {{class_name}}_{{method.name}}_ProxyToResponder::CreateCallback(
+ message->request_id(),
+ message->has_flag(mojo::Message::kFlagIsSync),
+ std::move(responder));
+ // A null |impl| means no implementation was bound.
+ assert(impl);
+ TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}");
+ mojo::internal::MessageDispatchContext context(message);
+ impl->{{method.name}}(
+{%- if method.parameters -%}{{pass_params(method.parameters)}}, {% endif -%}std::move(callback));
+ return true;
+{%- else %}
+ break;
+{%- endif %}
+ }
+{%- endfor %}
+ }
+{%- endif %}
+ return false;
+}
+
+{#--- Request validator definitions #}
+
+bool {{class_name}}RequestValidator::Accept(mojo::Message* message) {
+ if (mojo::internal::ControlMessageHandler::IsControlMessage(message))
+ return true;
+
+ mojo::internal::ValidationContext validation_context(
+ message->payload(), message->payload_num_bytes(),
+ message->handles()->size(), message->payload_num_interface_ids(), message,
+ "{{class_name}} RequestValidator");
+
+ switch (message->header()->name) {
+{%- for method in interface.methods %}
+ case internal::k{{class_name}}_{{method.name}}_Name: {
+{%- if method.response_parameters != None %}
+ if (!mojo::internal::ValidateMessageIsRequestExpectingResponse(
+ message, &validation_context)) {
+ return false;
+ }
+{%- else %}
+ if (!mojo::internal::ValidateMessageIsRequestWithoutResponse(
+ message, &validation_context)) {
+ return false;
+ }
+{%- endif %}
+ if (!mojo::internal::ValidateMessagePayload<
+ internal::{{class_name}}_{{method.name}}_Params_Data>(
+ message, &validation_context)) {
+ return false;
+ }
+ return true;
+ }
+{%- endfor %}
+ default:
+ break;
+ }
+
+ // Unrecognized message.
+ ReportValidationError(
+ &validation_context,
+ mojo::internal::VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD);
+ return false;
+}
+
+{#--- Response validator definitions #}
+{% if interface|has_callbacks %}
+bool {{class_name}}ResponseValidator::Accept(mojo::Message* message) {
+ if (mojo::internal::ControlMessageHandler::IsControlMessage(message))
+ return true;
+
+ mojo::internal::ValidationContext validation_context(
+ message->payload(), message->payload_num_bytes(),
+ message->handles()->size(), message->payload_num_interface_ids(), message,
+ "{{class_name}} ResponseValidator");
+
+ if (!mojo::internal::ValidateMessageIsResponse(message, &validation_context))
+ return false;
+ switch (message->header()->name) {
+{%- for method in interface.methods if method.response_parameters != None %}
+ case internal::k{{class_name}}_{{method.name}}_Name: {
+ if (!mojo::internal::ValidateMessagePayload<
+ internal::{{class_name}}_{{method.name}}_ResponseParams_Data>(
+ message, &validation_context)) {
+ return false;
+ }
+ return true;
+ }
+{%- endfor %}
+ default:
+ break;
+ }
+
+ // Unrecognized message.
+ ReportValidationError(
+ &validation_context,
+ mojo::internal::VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD);
+ return false;
+}
+{%- endif -%}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
new file mode 100644
index 0000000000..8649273633
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
@@ -0,0 +1,49 @@
+{%- macro declare_params(prefix, parameters) %}
+{%- for param in parameters -%}
+{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor %}
+{%- endmacro %}
+
+{%- macro declare_responder_params(prefix, parameters, for_blink) %}
+{%- for param in parameters -%}
+{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor %}
+{%- endmacro %}
+
+{%- macro declare_callback(method, for_blink, use_once_callback) -%}
+{%- if use_once_callback -%}
+base::OnceCallback<void(
+{%- else -%}
+base::Callback<void(
+{%- endif -%}
+{%- for param in method.response_parameters -%}
+{{param.kind|cpp_wrapper_param_type}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor -%}
+)>
+{%- endmacro -%}
+
+{%- macro declare_request_params(prefix, method, use_once_callback) -%}
+{{declare_params(prefix, method.parameters)}}
+{%- if method.response_parameters != None -%}
+{%- if method.parameters %}, {% endif -%}
+{%- if use_once_callback -%}
+{{method.name}}Callback callback
+{%- else -%}
+const {{method.name}}Callback& callback
+{%- endif -%}
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro declare_sync_method_params(prefix, method) -%}
+{{declare_params(prefix, method.parameters)}}
+{%- if method.response_parameters %}
+{%- if method.parameters %}, {% endif %}
+{%- for param in method.response_parameters -%}
+{{param.kind|cpp_wrapper_type}}* {{prefix}}{{param.name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor %}
+{%- endif -%}
+{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
new file mode 100644
index 0000000000..0a158ec3e8
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
@@ -0,0 +1,16 @@
+{%- import "interface_macros.tmpl" as interface_macros %}
+class {{export_attribute}} {{interface.name}}Proxy
+ : public {{interface.name}} {
+ public:
+ explicit {{interface.name}}Proxy(mojo::MessageReceiverWithResponder* receiver);
+
+{%- for method in interface.methods %}
+{%- if method.sync %}
+ bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) override;
+{%- endif %}
+ void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) override;
+{%- endfor %}
+
+ private:
+ mojo::MessageReceiverWithResponder* receiver_;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl
new file mode 100644
index 0000000000..a00d14886d
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl
@@ -0,0 +1,4 @@
+class {{export_attribute}} {{interface.name}}RequestValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) {
+ public:
+ bool Accept(mojo::Message* message) override;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl
new file mode 100644
index 0000000000..e2caa02c79
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl
@@ -0,0 +1,4 @@
+class {{export_attribute}} {{interface.name}}ResponseValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) {
+ public:
+ bool Accept(mojo::Message* message) override;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
new file mode 100644
index 0000000000..79ab46f337
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
@@ -0,0 +1,41 @@
+class {{export_attribute}} {{interface.name}}StubDispatch {
+ public:
+ static bool Accept({{interface.name}}* impl, mojo::Message* message);
+ static bool AcceptWithResponder(
+ {{interface.name}}* impl,
+ mojo::Message* message,
+ std::unique_ptr<mojo::MessageReceiverWithStatus> responder);
+};
+
+template <typename ImplRefTraits =
+ mojo::RawPtrImplRefTraits<{{interface.name}}>>
+class {{interface.name}}Stub
+ : public NON_EXPORTED_BASE(mojo::MessageReceiverWithResponderStatus) {
+ public:
+ using ImplPointerType = typename ImplRefTraits::PointerType;
+
+ {{interface.name}}Stub() {}
+ ~{{interface.name}}Stub() override {}
+
+ void set_sink(ImplPointerType sink) { sink_ = std::move(sink); }
+ ImplPointerType& sink() { return sink_; }
+
+ bool Accept(mojo::Message* message) override {
+ if (ImplRefTraits::IsNull(sink_))
+ return false;
+ return {{interface.name}}StubDispatch::Accept(
+ ImplRefTraits::GetRawPointer(&sink_), message);
+ }
+
+ bool AcceptWithResponder(
+ mojo::Message* message,
+ std::unique_ptr<mojo::MessageReceiverWithStatus> responder) override {
+ if (ImplRefTraits::IsNull(sink_))
+ return false;
+ return {{interface.name}}StubDispatch::AcceptWithResponder(
+ ImplRefTraits::GetRawPointer(&sink_), message, std::move(responder));
+ }
+
+ private:
+ ImplPointerType sink_;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
new file mode 100644
index 0000000000..964b25438e
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
@@ -0,0 +1,96 @@
+// 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.
+
+{%- set header_guard = "%s_SHARED_INTERNAL_H_"|format(
+ module.path|upper|replace("/","_")|replace(".","_")|
+ replace("-", "_")) %}
+
+#ifndef {{header_guard}}
+#define {{header_guard}}
+
+#include <stdint.h>
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/map_data_internal.h"
+#include "mojo/public/cpp/bindings/lib/native_enum_data.h"
+#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+
+{%- for import in imports %}
+#include "{{import.module.path}}-shared-internal.h"
+{%- endfor %}
+
+namespace mojo {
+namespace internal {
+class ValidationContext;
+}
+}
+
+{%- for namespace in namespaces_as_array %}
+namespace {{namespace}} {
+{%- endfor %}
+namespace internal {
+
+{#--- Internal forward declarations #}
+{%- for struct in structs %}
+{%- if struct|is_native_only_kind %}
+using {{struct.name}}_Data = mojo::internal::NativeStruct_Data;
+{%- else %}
+class {{struct.name}}_Data;
+{%- endif %}
+{%- endfor %}
+
+{%- for union in unions %}
+class {{union.name}}_Data;
+{%- endfor %}
+
+{#--- Enums #}
+{%- from "enum_macros.tmpl" import enum_data_decl -%}
+{%- for enum in all_enums %}
+{%- if enum|is_native_only_kind %}
+using {{enum|get_name_for_kind(flatten_nested_kind=True)}}_Data =
+ mojo::internal::NativeEnum_Data;
+{%- else %}
+{{enum_data_decl(enum)}}
+{%- endif %}
+{%- endfor %}
+
+#pragma pack(push, 1)
+
+{#--- Unions must be declared first because they can be members of structs #}
+{#--- Union class declarations #}
+{%- for union in unions %}
+{% include "union_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- Struct class declarations #}
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Interface parameter definitions #}
+{%- for interface in interfaces %}
+{%- for method in interface.methods %}
+{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %}
+constexpr uint32_t {{method_name}} = {{method.ordinal}};
+{%- set struct = method.param_struct %}
+{% include "struct_declaration.tmpl" %}
+{%- if method.response_parameters != None %}
+{%- set struct = method.response_param_struct %}
+{% include "struct_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+{%- endfor %}
+
+#pragma pack(pop)
+
+} // namespace internal
+{%- for namespace in namespaces_as_array|reverse %}
+} // namespace {{namespace}}
+{%- endfor %}
+
+#endif // {{header_guard}}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
new file mode 100644
index 0000000000..645bb692b0
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
@@ -0,0 +1,64 @@
+// 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.
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4065)
+#endif
+
+#include "{{module.path}}-shared.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/lib/validate_params.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/lib/validation_util.h"
+
+{%- for header in extra_traits_headers %}
+#include "{{header}}"
+{%- endfor %}
+
+{%- for namespace in namespaces_as_array %}
+namespace {{namespace}} {
+{%- endfor %}
+
+namespace internal {
+
+{#--- Union definitions #}
+{%- for union in unions %}
+{% include "union_definition.tmpl" %}
+{%- endfor %}
+
+{#--- Struct definitions #}
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Interface parameter definitions #}
+{%- for interface in interfaces %}
+{%- for method in interface.methods %}
+{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %}
+{%- set struct = method.param_struct %}
+{% include "struct_definition.tmpl" %}
+{%- if method.response_parameters != None %}
+{%- set struct = method.response_param_struct %}
+{% include "struct_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+{%- endfor %}
+
+} // namespace internal
+
+{%- for namespace in namespaces_as_array|reverse %}
+} // namespace {{namespace}}
+{%- endfor %}
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
new file mode 100644
index 0000000000..dd13466de1
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
@@ -0,0 +1,212 @@
+// 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.
+
+{%- set header_guard = "%s_SHARED_H_"|format(
+ module.path|upper|replace("/","_")|replace(".","_")|
+ replace("-", "_")) %}
+
+{%- macro mojom_type_traits(kind) %}
+template <>
+struct MojomTypeTraits<{{kind|get_qualified_name_for_kind}}DataView> {
+ using Data = {{kind|get_qualified_name_for_kind(internal=True)}};
+{%- if kind|is_union_kind %}
+ using DataAsArrayElement = Data;
+ static constexpr MojomTypeCategory category = MojomTypeCategory::UNION;
+{%- else %}
+ using DataAsArrayElement = Pointer<Data>;
+ static constexpr MojomTypeCategory category = MojomTypeCategory::STRUCT;
+{%- endif %}
+};
+{%- endmacro %}
+
+{%- macro namespace_begin() %}
+{%- for namespace in namespaces_as_array %}
+namespace {{namespace}} {
+{%- endfor %}
+{%- endmacro %}
+
+{%- macro namespace_end() %}
+{%- for namespace in namespaces_as_array|reverse %}
+} // namespace {{namespace}}
+{%- endfor %}
+{%- endmacro %}
+
+#ifndef {{header_guard}}
+#define {{header_guard}}
+
+#include <stdint.h>
+
+#include <functional>
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "mojo/public/cpp/bindings/array_data_view.h"
+#include "mojo/public/cpp/bindings/enum_traits.h"
+#include "mojo/public/cpp/bindings/interface_data_view.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/map_data_view.h"
+#include "mojo/public/cpp/bindings/native_enum.h"
+#include "mojo/public/cpp/bindings/native_struct_data_view.h"
+#include "mojo/public/cpp/bindings/string_data_view.h"
+#include "{{module.path}}-shared-internal.h"
+{%- for import in imports %}
+#include "{{import.module.path}}-shared.h"
+{%- endfor %}
+
+{{namespace_begin()}}
+
+{#--- Struct Forward Declarations -#}
+{%- for struct in structs %}
+{%- if struct|is_native_only_kind %}
+using {{struct.name}}DataView = mojo::NativeStructDataView;
+{%- else %}
+class {{struct.name}}DataView;
+{%- endif %}
+{% endfor %}
+
+{#--- Union Forward Declarations -#}
+{%- for union in unions %}
+class {{union.name}}DataView;
+{%- endfor %}
+
+{{namespace_end()}}
+
+namespace mojo {
+namespace internal {
+
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{{mojom_type_traits(struct)}}
+{%- endif %}
+{%- endfor %}
+
+{%- for union in unions %}
+{{mojom_type_traits(union)}}
+{%- endfor %}
+
+} // namespace internal
+} // namespace mojo
+
+{{namespace_begin()}}
+
+{#--- Enums #}
+{%- from "enum_macros.tmpl" import enum_decl%}
+{%- for enum in all_enums %}
+{%- if enum|is_native_only_kind %}
+using {{enum|get_name_for_kind(flatten_nested_kind=True)}} = mojo::NativeEnum;
+{%- else %}
+{{enum_decl(enum)}}
+{%- endif %}
+{%- endfor %}
+
+{#--- Interfaces #}
+{%- if interfaces %}
+// Interface base classes. They are used for type safety check.
+{%- endif %}
+{%- for interface in interfaces %}
+class {{interface.name}}InterfaceBase {};
+
+using {{interface.name}}PtrDataView =
+ mojo::InterfacePtrDataView<{{interface.name}}InterfaceBase>;
+using {{interface.name}}RequestDataView =
+ mojo::InterfaceRequestDataView<{{interface.name}}InterfaceBase>;
+using {{interface.name}}AssociatedPtrInfoDataView =
+ mojo::AssociatedInterfacePtrInfoDataView<{{interface.name}}InterfaceBase>;
+using {{interface.name}}AssociatedRequestDataView =
+ mojo::AssociatedInterfaceRequestDataView<{{interface.name}}InterfaceBase>;
+
+{%- endfor %}
+
+{#--- Structs #}
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_data_view_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Interface parameter definitions #}
+{%- for interface in interfaces %}
+{%- for method in interface.methods %}
+{%- set struct = method.param_struct %}
+{% include "struct_data_view_declaration.tmpl" %}
+{%- if method.response_parameters != None %}
+{%- set struct = method.response_param_struct %}
+{% include "struct_data_view_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+{%- endfor %}
+
+{#--- Unions #}
+{%- for union in unions %}
+{% include "union_data_view_declaration.tmpl" %}
+{%- endfor %}
+
+{{namespace_end()}}
+
+namespace std {
+
+{%- from "enum_macros.tmpl" import enum_hash %}
+{%- for enum in all_enums %}
+{%- if not enum|is_native_only_kind %}
+{{enum_hash(enum)}}
+{%- endif %}
+{%- endfor %}
+
+} // namespace std
+
+namespace mojo {
+
+{#--- Enum Serialization Helpers -#}
+{%- for enum in all_enums %}
+{%- if not enum|is_native_only_kind %}
+{% include "enum_serialization_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Struct Serialization Helpers -#}
+{% for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_serialization_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Union Serialization Helpers -#}
+{% if unions %}
+{%- for union in unions %}
+{% include "union_serialization_declaration.tmpl" %}
+{%- endfor %}
+{%- endif %}
+
+} // namespace mojo
+
+{{namespace_begin()}}
+
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_data_view_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{%- for interface in interfaces %}
+{%- for method in interface.methods %}
+{%- set struct = method.param_struct %}
+{% include "struct_data_view_definition.tmpl" %}
+{%- if method.response_parameters != None %}
+{%- set struct = method.response_param_struct %}
+{% include "struct_data_view_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+{%- endfor %}
+
+{%- for union in unions %}
+{% include "union_data_view_definition.tmpl" %}
+{%- endfor %}
+
+{{namespace_end()}}
+
+#endif // {{header_guard}}
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
new file mode 100644
index 0000000000..2c66a85f87
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
@@ -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.
+
+{%- if variant -%}
+{%- set variant_path = "%s-%s"|format(module.path, variant) -%}
+{%- else -%}
+{%- set variant_path = module.path -%}
+{%- endif %}
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-private-field"
+#elif defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4056)
+#pragma warning(disable:4065)
+#pragma warning(disable:4756)
+#endif
+
+#include "{{variant_path}}.h"
+
+#include <math.h>
+#include <stdint.h>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/lib/validate_params.h"
+#include "mojo/public/cpp/bindings/lib/validation_context.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h"
+
+{%- if for_blink %}
+#include "mojo/public/cpp/bindings/lib/wtf_serialization.h"
+{%- endif %}
+
+{%- for header in extra_traits_headers %}
+#include "{{header}}"
+{%- endfor %}
+
+{%- for namespace in namespaces_as_array %}
+namespace {{namespace}} {
+{%- endfor %}
+{%- if variant %}
+namespace {{variant}} {
+{%- endif %}
+
+{#--- Constants #}
+{%- for constant in module.constants %}
+{%- if constant.kind|is_string_kind %}
+const char {{constant.name}}[] = {{constant|constant_value}};
+{%- endif %}
+{%- endfor %}
+
+{#--- Struct Constants #}
+{%- for struct in structs %}
+{%- for constant in struct.constants %}
+{%- if constant.kind|is_string_kind %}
+const char {{struct.name}}::{{constant.name}}[] = {{constant|constant_value}};
+{%- endif %}
+{%- endfor %}
+{%- endfor %}
+
+{#--- Struct builder definitions #}
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{%- include "wrapper_class_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Union builder definitions #}
+{%- for union in unions %}
+{%- include "wrapper_union_class_definition.tmpl" %}
+{%- endfor %}
+
+{#--- Interface definitions #}
+{%- for interface in interfaces %}
+{%- include "interface_definition.tmpl" %}
+{%- endfor %}
+
+{%- if variant %}
+} // namespace {{variant}}
+{%- endif %}
+{%- for namespace in namespaces_as_array|reverse %}
+} // namespace {{namespace}}
+{%- endfor %}
+
+namespace mojo {
+
+{#--- Struct Serialization Helpers -#}
+{% for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_traits_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Union Serialization Helpers #}
+{%- for union in unions %}
+{%- include "union_traits_definition.tmpl" %}
+{%- endfor %}
+
+} // namespace mojo
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(_MSC_VER)
+#pragma warning(pop)
+#endif
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
new file mode 100644
index 0000000000..804a46b6f8
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
@@ -0,0 +1,236 @@
+// Copyright 2013 The Chromium 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 variant -%}
+{%- set variant_path = "%s-%s"|format(module.path, variant) -%}
+{%- else -%}
+{%- set variant_path = module.path -%}
+{%- endif -%}
+
+{%- set header_guard = "%s_H_"|format(
+ variant_path|upper|replace("/","_")|replace(".","_")|
+ replace("-", "_")) %}
+
+{%- macro namespace_begin() %}
+{%- for namespace in namespaces_as_array %}
+namespace {{namespace}} {
+{%- endfor %}
+{%- if variant %}
+namespace {{variant}} {
+{%- endif %}
+{%- endmacro %}
+
+{%- macro namespace_end() %}
+{%- if variant %}
+} // namespace {{variant}}
+{%- endif %}
+{%- for namespace in namespaces_as_array|reverse %}
+} // namespace {{namespace}}
+{%- endfor %}
+{%- endmacro %}
+
+#ifndef {{header_guard}}
+#define {{header_guard}}
+
+#include <stdint.h>
+
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/clone_traits.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/lib/equals_traits.h"
+#include "mojo/public/cpp/bindings/lib/control_message_handler.h"
+#include "mojo/public/cpp/bindings/lib/control_message_proxy.h"
+#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/union_accessor.h"
+#include "mojo/public/cpp/bindings/native_struct.h"
+#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+#include "{{module.path}}-shared.h"
+{%- for import in imports %}
+{%- if variant %}
+#include "{{"%s-%s.h"|format(import.module.path, variant)}}"
+{%- else %}
+#include "{{import.module.path}}.h"
+{%- endif %}
+{%- endfor %}
+{%- if not for_blink %}
+#include <string>
+#include <vector>
+{%- else %}
+{# hash_util.h includes template specializations that should be present for
+ every use of {Inlined}StructPtr. #}
+#include "mojo/public/cpp/bindings/lib/wtf_hash_util.h"
+#include "third_party/WebKit/Source/wtf/HashFunctions.h"
+#include "third_party/WebKit/Source/wtf/Optional.h"
+#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+{%- endif %}
+
+{%- for header in extra_public_headers %}
+#include "{{header}}"
+{%- endfor %}
+
+{%- if export_header %}
+#include "{{export_header}}"
+{%- endif %}
+
+{#--- WTF enum hashing #}
+{%- from "enum_macros.tmpl" import enum_hash_blink%}
+{%- if for_blink %}
+{%- for enum in all_enums %}
+{%- if not enum|is_native_only_kind %}
+{{enum_hash_blink(enum)}}
+{%- endif %}
+{%- endfor %}
+{%- endif %}
+
+{{namespace_begin()}}
+
+{#--- Enums #}
+{%- if variant %}
+{%- for enum in enums %}
+using {{enum.name}} = {{enum.name}}; // Alias for definition in the parent namespace.
+{%- endfor %}
+{%- endif %}
+
+{#--- Constants #}
+{%- for constant in module.constants %}
+{{constant|format_constant_declaration}};
+{%- endfor %}
+
+{#--- Interface Forward Declarations -#}
+{% for interface in interfaces %}
+class {{interface.name}};
+using {{interface.name}}Ptr = mojo::InterfacePtr<{{interface.name}}>;
+using {{interface.name}}PtrInfo = mojo::InterfacePtrInfo<{{interface.name}}>;
+using ThreadSafe{{interface.name}}Ptr =
+ mojo::ThreadSafeInterfacePtr<{{interface.name}}>;
+using {{interface.name}}Request = mojo::InterfaceRequest<{{interface.name}}>;
+using {{interface.name}}AssociatedPtr =
+ mojo::AssociatedInterfacePtr<{{interface.name}}>;
+using ThreadSafe{{interface.name}}AssociatedPtr =
+ mojo::ThreadSafeAssociatedInterfacePtr<{{interface.name}}>;
+using {{interface.name}}AssociatedPtrInfo =
+ mojo::AssociatedInterfacePtrInfo<{{interface.name}}>;
+using {{interface.name}}AssociatedRequest =
+ mojo::AssociatedInterfaceRequest<{{interface.name}}>;
+{% endfor %}
+
+{#--- Struct Forward Declarations -#}
+{% for struct in structs %}
+{%- if struct|is_native_only_kind %}
+using {{struct.name}} = mojo::NativeStruct;
+using {{struct.name}}Ptr = mojo::NativeStructPtr;
+{%- else %}
+class {{struct.name}};
+{%- if struct|should_inline %}
+using {{struct.name}}Ptr = mojo::InlinedStructPtr<{{struct.name}}>;
+{%- else %}
+using {{struct.name}}Ptr = mojo::StructPtr<{{struct.name}}>;
+{%- endif %}
+{%- endif %}
+{% endfor %}
+
+{#--- Union Forward Declarations -#}
+{% for union in unions %}
+class {{union.name}};
+{% if union|should_inline_union %}
+typedef mojo::InlinedStructPtr<{{union.name}}> {{union.name}}Ptr;
+{% else %}
+typedef mojo::StructPtr<{{union.name}}> {{union.name}}Ptr;
+{% endif %}
+{%- endfor %}
+
+{#--- Interfaces -#}
+{% for interface in interfaces %}
+{% include "interface_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- Interface Proxies -#}
+{% for interface in interfaces %}
+{% include "interface_proxy_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- Interface Stubs -#}
+{% for interface in interfaces %}
+{% include "interface_stub_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- Interface Request Validators -#}
+{% for interface in interfaces %}
+{% include "interface_request_validator_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- Interface Response Validators -#}
+{% for interface in interfaces if interface|has_callbacks %}
+{% include "interface_response_validator_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- NOTE: Unions and non-inlined structs may have pointers to inlined structs,
+ so we need to fully define inlined structs ahead of the others. #}
+
+{#--- Inlined structs #}
+{% for struct in structs %}
+{% if struct|should_inline and not struct|is_native_only_kind %}
+{% include "wrapper_class_declaration.tmpl" %}
+{% endif %}
+{%- endfor %}
+
+{#--- Unions must be declared before non-inlined structs because they can be
+ members of structs. #}
+{#--- Unions #}
+{% for union in unions %}
+{% include "wrapper_union_class_declaration.tmpl" %}
+{%- endfor %}
+
+{#--- Non-inlined structs #}
+{% for struct in structs %}
+{% if not struct|should_inline and not struct|is_native_only_kind %}
+{% include "wrapper_class_declaration.tmpl" %}
+{% endif %}
+{%- endfor %}
+
+{%- for union in unions %}
+{% include "wrapper_union_class_template_definition.tmpl" %}
+{%- endfor %}
+
+{%- for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "wrapper_class_template_definition.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{{namespace_end()}}
+
+namespace mojo {
+
+{#--- Struct Serialization Helpers -#}
+{% for struct in structs %}
+{%- if not struct|is_native_only_kind %}
+{% include "struct_traits_declaration.tmpl" %}
+{%- endif %}
+{%- endfor %}
+
+{#--- Union Serialization Helpers -#}
+{% if unions %}
+{%- for union in unions %}
+{% include "union_traits_declaration.tmpl" %}
+{%- endfor %}
+{%- endif %}
+
+} // namespace mojo
+
+#endif // {{header_guard}}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
new file mode 100644
index 0000000000..96e0d614d8
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
@@ -0,0 +1,118 @@
+class {{struct.name}}DataView {
+ public:
+ {{struct.name}}DataView() {}
+
+ {{struct.name}}DataView(
+ internal::{{struct.name}}_Data* data,
+ mojo::internal::SerializationContext* context)
+{%- if struct|requires_context_for_data_view %}
+ : data_(data), context_(context) {}
+{%- else %}
+ : data_(data) {}
+{%- endif %}
+
+ bool is_null() const { return !data_; }
+
+{%- for pf in struct.packed.packed_fields_in_ordinal_order %}
+{%- set kind = pf.field.kind %}
+{%- set name = pf.field.name %}
+{%- if kind|is_union_kind %}
+ inline void Get{{name|under_to_camel}}DataView(
+ {{kind|cpp_data_view_type}}* output);
+
+ template <typename UserType>
+ WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) {
+{%- if pf.min_version != 0 %}
+ auto* pointer = data_->header_.version >= {{pf.min_version}}
+ ? &data_->{{name}} : nullptr;
+{%- else %}
+ auto* pointer = &data_->{{name}};
+{%- endif %}
+ return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ pointer, output, context_);
+ }
+
+{%- elif kind|is_object_kind %}
+ inline void Get{{name|under_to_camel}}DataView(
+ {{kind|cpp_data_view_type}}* output);
+
+ template <typename UserType>
+ WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) {
+{%- if pf.min_version != 0 %}
+ auto* pointer = data_->header_.version >= {{pf.min_version}}
+ ? data_->{{name}}.Get() : nullptr;
+{%- else %}
+ auto* pointer = data_->{{name}}.Get();
+{%- endif %}
+ return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ pointer, output, context_);
+ }
+
+{%- elif kind|is_enum_kind %}
+ template <typename UserType>
+ WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const {
+{%- if pf.min_version != 0 %}
+ auto data_value = data_->header_.version >= {{pf.min_version}}
+ ? data_->{{name}} : 0;
+{%- else %}
+ auto data_value = data_->{{name}};
+{%- endif %}
+ return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ data_value, output);
+ }
+
+ {{kind|cpp_data_view_type}} {{name}}() const {
+{%- if pf.min_version != 0 %}
+ if (data_->header_.version < {{pf.min_version}})
+ return {{kind|get_qualified_name_for_kind}}{};
+{%- endif %}
+ return static_cast<{{kind|cpp_data_view_type}}>(data_->{{name}});
+ }
+
+{%- elif kind|is_any_handle_kind %}
+ {{kind|cpp_data_view_type}} Take{{name|under_to_camel}}() {
+ {{kind|cpp_data_view_type}} result;
+{%- if pf.min_version != 0 %}
+ if (data_->header_.version < {{pf.min_version}})
+ return result;
+{%- endif %}
+ bool ret =
+ mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ &data_->{{name}}, &result, context_);
+ DCHECK(ret);
+ return result;
+ }
+
+{%- elif kind|is_any_interface_kind %}
+ template <typename UserType>
+ UserType Take{{name|under_to_camel}}() {
+ UserType result;
+{%- if pf.min_version != 0 %}
+ if (data_->header_.version < {{pf.min_version}})
+ return result;
+{%- endif %}
+ bool ret =
+ mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ &data_->{{name}}, &result, context_);
+ DCHECK(ret);
+ return result;
+ }
+
+{%- else %}
+ {{kind|cpp_data_view_type}} {{name}}() const {
+{%- if pf.min_version != 0 %}
+ if (data_->header_.version < {{pf.min_version}})
+ return {{kind|cpp_data_view_type}}{};
+{%- endif %}
+ return data_->{{name}};
+ }
+
+{%- endif %}
+{%- endfor %}
+ private:
+ internal::{{struct.name}}_Data* data_ = nullptr;
+{%- if struct|requires_context_for_data_view %}
+ mojo::internal::SerializationContext* context_ = nullptr;
+{%- endif %}
+};
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl
new file mode 100644
index 0000000000..95311dc124
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl
@@ -0,0 +1,30 @@
+{%- for pf in struct.packed.packed_fields_in_ordinal_order %}
+{%- set kind = pf.field.kind %}
+{%- set name = pf.field.name %}
+
+{%- if kind|is_union_kind %}
+inline void {{struct.name}}DataView::Get{{name|under_to_camel}}DataView(
+ {{kind|cpp_data_view_type}}* output) {
+{%- if pf.min_version != 0 %}
+ auto pointer = data_->header_.version >= {{pf.min_version}}
+ ? &data_->{{name}} : nullptr;
+{%- else %}
+ auto pointer = &data_->{{name}};
+{%- endif %}
+ *output = {{kind|cpp_data_view_type}}(pointer, context_);
+}
+
+{%- elif kind|is_object_kind %}
+inline void {{struct.name}}DataView::Get{{name|under_to_camel}}DataView(
+ {{kind|cpp_data_view_type}}* output) {
+{%- if pf.min_version != 0 %}
+ auto pointer = data_->header_.version >= {{pf.min_version}}
+ ? data_->{{name}}.Get() : nullptr;
+{%- else %}
+ auto pointer = data_->{{name}}.Get();
+{%- endif %}
+ *output = {{kind|cpp_data_view_type}}(pointer, context_);
+}
+{%- endif %}
+{%- endfor %}
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
new file mode 100644
index 0000000000..156f7742c4
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
@@ -0,0 +1,46 @@
+{%- set class_name = struct.name ~ "_Data" -%}
+
+class {{class_name}} {
+ public:
+ static {{class_name}}* New(mojo::internal::Buffer* buf) {
+ return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}();
+ }
+
+ static bool Validate(const void* data,
+ mojo::internal::ValidationContext* validation_context);
+
+ mojo::internal::StructHeader header_;
+{%- for packed_field in struct.packed.packed_fields %}
+{%- set name = packed_field.field.name %}
+{%- set kind = packed_field.field.kind %}
+{%- if kind.spec == 'b' %}
+ uint8_t {{name}} : 1;
+{%- else %}
+ {{kind|cpp_field_type}} {{name}};
+{%- endif %}
+{%- if not loop.last %}
+{%- set next_pf = struct.packed.packed_fields[loop.index0 + 1] %}
+{%- set pad = next_pf.offset - (packed_field.offset + packed_field.size) %}
+{%- if pad > 0 %}
+ uint8_t pad{{loop.index0}}_[{{pad}}];
+{%- endif %}
+{%- endif %}
+{%- endfor %}
+
+{%- set num_fields = struct.versions[-1].num_fields %}
+{%- if num_fields > 0 %}
+{%- set last_field = struct.packed.packed_fields[num_fields - 1] %}
+{%- set offset = last_field.offset + last_field.size %}
+{%- set pad = offset|get_pad(8) %}
+{%- if pad > 0 %}
+ uint8_t padfinal_[{{pad}}];
+{%- endif %}
+{%- endif %}
+
+ private:
+ {{class_name}}() : header_({sizeof(*this), {{struct.versions[-1].version}}}) {
+ }
+ ~{{class_name}}() = delete;
+};
+static_assert(sizeof({{class_name}}) == {{struct.versions[-1].num_bytes}},
+ "Bad sizeof({{class_name}})");
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
new file mode 100644
index 0000000000..60dca4010e
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
@@ -0,0 +1,70 @@
+{%- import "validation_macros.tmpl" as validation_macros %}
+{%- set class_name = struct.name ~ "_Data" %}
+
+// static
+bool {{class_name}}::Validate(
+ const void* data,
+ mojo::internal::ValidationContext* validation_context) {
+ if (!data)
+ return true;
+
+ if (!ValidateStructHeaderAndClaimMemory(data, validation_context))
+ return false;
+
+ // NOTE: The memory backing |object| may be smaller than |sizeof(*object)| if
+ // the message comes from an older version.
+ const {{class_name}}* object = static_cast<const {{class_name}}*>(data);
+
+ static constexpr struct {
+ uint32_t version;
+ uint32_t num_bytes;
+ } kVersionSizes[] = {
+{%- for version in struct.versions -%}
+ { {{version.version}}, {{version.num_bytes}} }{% if not loop.last %}, {% endif -%}
+{%- endfor -%}
+ };
+
+ if (object->header_.version <=
+ kVersionSizes[arraysize(kVersionSizes) - 1].version) {
+ // Scan in reverse order to optimize for more recent versions.
+ for (int i = arraysize(kVersionSizes) - 1; i >= 0; --i) {
+ if (object->header_.version >= kVersionSizes[i].version) {
+ if (object->header_.num_bytes == kVersionSizes[i].num_bytes)
+ break;
+
+ ReportValidationError(
+ validation_context,
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER);
+ return false;
+ }
+ }
+ } else if (object->header_.num_bytes <
+ kVersionSizes[arraysize(kVersionSizes) - 1].num_bytes) {
+ ReportValidationError(
+ validation_context,
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER);
+ return false;
+ }
+
+{#- Before validating fields introduced at a certain version, we need to add
+ a version check, which makes sure we skip further validation if |object|
+ is from an earlier version. |last_checked_version| records the last
+ version that we have added such version check. #}
+{%- set last_checked_version = 0 %}
+{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %}
+{%- set kind = packed_field.field.kind %}
+{%- if kind|is_object_kind or kind|is_any_handle_or_interface_kind or
+ kind|is_enum_kind %}
+{%- if packed_field.min_version > last_checked_version %}
+{%- set last_checked_version = packed_field.min_version %}
+ if (object->header_.version < {{packed_field.min_version}})
+ return true;
+{%- endif %}
+{%- set field_expr = "object->" ~ packed_field.field.name %}
+{{validation_macros.validate_field(packed_field.field, field_expr, struct.name, true)}}
+{%- endif %}
+{%- endfor %}
+
+ return true;
+}
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
new file mode 100644
index 0000000000..bb5fb9c496
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
@@ -0,0 +1,161 @@
+{# TODO(yzshen): Make these templates more readable. #}
+
+{# Computes the serialized size for the specified struct.
+ |struct| is the struct definition.
+ |input_field_pattern| should be a pattern that contains one string
+ placeholder, for example, "input->%s", "p_%s". The placeholder will be
+ substituted with struct field names to refer to the input fields.
+ |context| is the name of the serialization context.
+ |input_may_be_temp| indicates whether any input may be temporary obejcts.
+ We need to assign temporary objects to local variables before passing it to
+ Serializer, because it is illegal to pass temporary objects as non-const
+ references.
+ This macro is expanded to compute seriailized size for both:
+ - user-defined structs: the input is an instance of the corresponding struct
+ wrapper class.
+ - method parameters/response parameters: the input is a list of
+ arguments.
+ It declares |size| of type size_t to store the resulting size. #}
+{%- macro get_serialized_size(struct, input_field_pattern, context,
+ input_may_be_temp=False) -%}
+ size_t size = sizeof({{struct|get_qualified_name_for_kind(internal=True)}});
+{%- for pf in struct.packed.packed_fields_in_ordinal_order
+ if pf.field.kind|is_object_kind or pf.field.kind|is_associated_kind %}
+{%- set name = pf.field.name -%}
+{%- set kind = pf.field.kind -%}
+{%- set original_input_field = input_field_pattern|format(name) %}
+{%- set input_field = "in_%s"|format(name) if input_may_be_temp
+ else original_input_field %}
+{%- if input_may_be_temp %}
+ decltype({{original_input_field}}) in_{{name}} = {{original_input_field}};
+{%- endif %}
+
+{%- set serializer_type = kind|unmapped_type_for_serializer %}
+{%- if kind|is_union_kind %}
+ size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
+ {{input_field}}, true, {{context}});
+{%- else %}
+ size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
+ {{input_field}}, {{context}});
+{%- endif %}
+{%- endfor %}
+{%- endmacro -%}
+
+{# Serializes the specified struct.
+ |struct| is the struct definition.
+ |struct_display_name| is the display name for the struct that can be showed
+ in error/log messages, for example, "FooStruct", "FooMethod request".
+ |input_field_pattern| should be a pattern that contains one string
+ placeholder, for example, "input->%s", "p_%s". The placeholder will be
+ substituted with struct field names to refer to the input fields.
+ |output| is the name of the output struct instance.
+ |buffer| is the name of the Buffer instance used.
+ |context| is the name of the serialization context.
+ |input_may_be_temp|: please see the comments of get_serialized_size.
+ This macro is expanded to do serialization for both:
+ - user-defined structs: the input is an instance of the corresponding struct
+ wrapper class.
+ - method parameters/response parameters: the input is a list of
+ arguments. #}
+{%- macro serialize(struct, struct_display_name, input_field_pattern, output,
+ buffer, context, input_may_be_temp=False) -%}
+ auto {{output}} =
+ {{struct|get_qualified_name_for_kind(internal=True)}}::New({{buffer}});
+ ALLOW_UNUSED_LOCAL({{output}});
+{%- for pf in struct.packed.packed_fields_in_ordinal_order %}
+{%- set input_field = input_field_pattern|format(pf.field.name) %}
+{%- set name = pf.field.name %}
+{%- set kind = pf.field.kind %}
+{%- set serializer_type = kind|unmapped_type_for_serializer %}
+
+{%- if kind|is_object_kind or kind|is_any_handle_or_interface_kind %}
+{%- set original_input_field = input_field_pattern|format(name) %}
+{%- set input_field = "in_%s"|format(name) if input_may_be_temp
+ else original_input_field %}
+{%- if input_may_be_temp %}
+ decltype({{original_input_field}}) in_{{name}} = {{original_input_field}};
+{%- endif %}
+{%- endif %}
+
+{%- if kind|is_object_kind %}
+{%- if kind|is_array_kind or kind|is_map_kind %}
+ typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr;
+ const mojo::internal::ContainerValidateParams {{name}}_validate_params(
+ {{kind|get_container_validate_params_ctor_args|indent(10)}});
+ mojo::internal::Serialize<{{serializer_type}}>(
+ {{input_field}}, {{buffer}}, &{{name}}_ptr, &{{name}}_validate_params,
+ {{context}});
+ {{output}}->{{name}}.Set({{name}}_ptr);
+{%- elif kind|is_union_kind %}
+ auto {{name}}_ptr = &{{output}}->{{name}};
+ mojo::internal::Serialize<{{serializer_type}}>(
+ {{input_field}}, {{buffer}}, &{{name}}_ptr, true, {{context}});
+{%- else %}
+ typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr;
+ mojo::internal::Serialize<{{serializer_type}}>(
+ {{input_field}}, {{buffer}}, &{{name}}_ptr, {{context}});
+ {{output}}->{{name}}.Set({{name}}_ptr);
+{%- endif %}
+{%- if not kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ {{output}}->{{name}}.is_null(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ "null {{name}} in {{struct_display_name}}");
+{%- endif %}
+
+{%- elif kind|is_any_handle_or_interface_kind %}
+ mojo::internal::Serialize<{{serializer_type}}>(
+ {{input_field}}, &{{output}}->{{name}}, {{context}});
+{%- if not kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !mojo::internal::IsHandleOrInterfaceValid({{output}}->{{name}}),
+{%- if kind|is_associated_kind %}
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+{%- else %}
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+{%- endif %}
+ "invalid {{name}} in {{struct_display_name}}");
+{%- endif %}
+
+{%- elif kind|is_enum_kind %}
+ mojo::internal::Serialize<{{serializer_type}}>(
+ {{input_field}}, &{{output}}->{{name}});
+
+{%- else %}
+ {{output}}->{{name}} = {{input_field}};
+{%- endif %}
+{%- endfor %}
+{%- endmacro -%}
+
+{# Deserializes the specified struct.
+ |struct| is the struct definition.
+ |input| is the name of the input struct data view. It is expected to be
+ non-null.
+ |output_field_pattern| should be a pattern that contains one string
+ placeholder, for example, "result->%s", "p_%s". The placeholder will be
+ substituted with struct field names to refer to the output fields.
+ |context| is the name of the serialization context.
+ |success| is the name of a bool variable to track success of the operation.
+ This macro is expanded to do deserialization for both:
+ - user-defined structs: the output is an instance of the corresponding
+ struct wrapper class.
+ - method parameters/response parameters: the output is a list of
+ arguments. #}
+{%- macro deserialize(struct, input, output_field_pattern, success) -%}
+{%- for pf in struct.packed.packed_fields_in_ordinal_order %}
+{%- set output_field = output_field_pattern|format(pf.field.name) %}
+{%- set name = pf.field.name %}
+{%- set kind = pf.field.kind %}
+{%- if kind|is_object_kind or kind|is_enum_kind %}
+ if (!{{input}}.Read{{name|under_to_camel}}(&{{output_field}}))
+ {{success}} = false;
+{%- elif kind|is_any_handle_kind %}
+ {{output_field}} = {{input}}.Take{{name|under_to_camel}}();
+{%- elif kind|is_any_interface_kind %}
+ {{output_field}} =
+ {{input}}.Take{{name|under_to_camel}}<decltype({{output_field}})>();
+{%- else %}
+ {{output_field}} = {{input}}.{{name}}();
+{%- endif %}
+{%- endfor %}
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
new file mode 100644
index 0000000000..835178beda
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
@@ -0,0 +1,57 @@
+{%- import "struct_macros.tmpl" as struct_macros %}
+{%- set data_view = struct|get_qualified_name_for_kind ~ "DataView" %}
+{%- set data_type = struct|get_qualified_name_for_kind(internal=True) %}
+
+namespace internal {
+
+template <typename MaybeConstUserType>
+struct Serializer<{{data_view}}, MaybeConstUserType> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = StructTraits<{{data_view}}, UserType>;
+
+ static size_t PrepareToSerialize(MaybeConstUserType& input,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input))
+ return 0;
+
+ void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
+ ALLOW_UNUSED_LOCAL(custom_context);
+
+ {{struct_macros.get_serialized_size(
+ struct, "CallWithContext(Traits::%s, input, custom_context)",
+ "context", True)|indent(2)}}
+ return size;
+ }
+
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buffer,
+ {{data_type}}** output,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input)) {
+ *output = nullptr;
+ return;
+ }
+
+ void* custom_context = CustomContextHelper<Traits>::GetNext(context);
+
+ {{struct_macros.serialize(
+ struct, struct.name ~ " struct",
+ "CallWithContext(Traits::%s, input, custom_context)", "result",
+ "buffer", "context", True)|indent(2)}}
+ *output = result;
+
+ CustomContextHelper<Traits>::TearDown(input, custom_context);
+ }
+
+ static bool Deserialize({{data_type}}* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!input)
+ return CallSetToNullIfExists<Traits>(output);
+
+ {{data_view}} data_view(input, context);
+ return Traits::Read(data_view, output);
+ }
+};
+
+} // namespace internal
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl
new file mode 100644
index 0000000000..1b7cf8954b
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl
@@ -0,0 +1,32 @@
+{%- set mojom_type = struct|get_qualified_name_for_kind %}
+
+template <>
+struct {{export_attribute}} StructTraits<{{mojom_type}}::DataView,
+ {{mojom_type}}Ptr> {
+ static bool IsNull(const {{mojom_type}}Ptr& input) { return !input; }
+ static void SetToNull({{mojom_type}}Ptr* output) { output->reset(); }
+
+{%- for field in struct.fields %}
+{%- set return_ref = field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+{# We want the field accessor to be const whenever possible to allow
+ structs to be used as map keys.
+ TODO(tibell): Make this check more precise to deal with e.g.
+ custom types which don't contain handles but require non-const
+ reference for serialization. #}
+{%- set maybe_const = "" if field.kind|contains_handles_or_interfaces else "const" %}
+{%- if return_ref %}
+ static {{maybe_const}} decltype({{mojom_type}}::{{field.name}})& {{field.name}}(
+ {{maybe_const}} {{mojom_type}}Ptr& input) {
+ return input->{{field.name}};
+ }
+{%- else %}
+ static decltype({{mojom_type}}::{{field.name}}) {{field.name}}(
+ const {{mojom_type}}Ptr& input) {
+ return input->{{field.name}};
+ }
+{%- endif %}
+{%- endfor %}
+
+ static bool Read({{mojom_type}}::DataView input, {{mojom_type}}Ptr* output);
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl
new file mode 100644
index 0000000000..f84337f5bf
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl
@@ -0,0 +1,14 @@
+{%- import "struct_macros.tmpl" as struct_macros %}
+{%- set mojom_type = struct|get_qualified_name_for_kind %}
+
+// static
+bool StructTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read(
+ {{mojom_type}}::DataView input,
+ {{mojom_type}}Ptr* output) {
+ bool success = true;
+ {{mojom_type}}Ptr result({{mojom_type}}::New());
+ {{struct_macros.deserialize(struct, "input", "result->%s",
+ "success")|indent(4)}}
+ *output = std::move(result);
+ return success;
+}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl
new file mode 100644
index 0000000000..5973ba294b
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl
@@ -0,0 +1,92 @@
+class {{union.name}}DataView {
+ public:
+ using Tag = internal::{{union.name}}_Data::{{union.name}}_Tag;
+
+ {{union.name}}DataView() {}
+
+ {{union.name}}DataView(
+ internal::{{union.name}}_Data* data,
+ mojo::internal::SerializationContext* context)
+{%- if union|requires_context_for_data_view %}
+ : data_(data), context_(context) {}
+{%- else %}
+ : data_(data) {}
+{%- endif %}
+
+ bool is_null() const {
+ // For inlined unions, |data_| is always non-null. In that case we need to
+ // check |data_->is_null()|.
+ return !data_ || data_->is_null();
+ }
+
+ Tag tag() const { return data_->tag; }
+
+{%- for field in union.fields %}
+{%- set kind = field.kind %}
+{%- set name = field.name %}
+ bool is_{{name}}() const { return data_->tag == Tag::{{name|upper}}; }
+
+{%- if kind|is_object_kind %}
+ inline void Get{{name|under_to_camel}}DataView(
+ {{kind|cpp_data_view_type}}* output);
+
+ template <typename UserType>
+ WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) {
+ DCHECK(is_{{name}}());
+ return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ data_->data.f_{{name}}.Get(), output, context_);
+ }
+
+{%- elif kind|is_enum_kind %}
+ template <typename UserType>
+ WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const {
+ DCHECK(is_{{name}}());
+ return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ data_->data.f_{{name}}, output);
+ }
+
+ {{kind|cpp_data_view_type}} {{name}}() const {
+ DCHECK(is_{{name}}());
+ return static_cast<{{kind|cpp_data_view_type}}>(
+ data_->data.f_{{name}});
+ }
+
+{%- elif kind|is_any_handle_kind %}
+ {{kind|cpp_data_view_type}} Take{{name|under_to_camel}}() {
+ DCHECK(is_{{name}}());
+ {{kind|cpp_data_view_type}} result;
+ bool ret =
+ mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ &data_->data.f_{{name}}, &result, context_);
+ DCHECK(ret);
+ return result;
+ }
+
+{%- elif kind|is_any_interface_kind %}
+ template <typename UserType>
+ UserType Take{{name|under_to_camel}}() {
+ DCHECK(is_{{name}}());
+ UserType result;
+ bool ret =
+ mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
+ &data_->data.f_{{name}}, &result, context_);
+ DCHECK(ret);
+ return result;
+ }
+
+{%- else %}
+ {{kind|cpp_data_view_type}} {{name}}() const {
+ DCHECK(is_{{name}}());
+ return data_->data.f_{{name}};
+ }
+
+{%- endif %}
+{%- endfor %}
+
+ private:
+ internal::{{union.name}}_Data* data_ = nullptr;
+{%- if union|requires_context_for_data_view %}
+ mojo::internal::SerializationContext* context_ = nullptr;
+{%- endif %}
+};
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl
new file mode 100644
index 0000000000..6da9280a73
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl
@@ -0,0 +1,12 @@
+{%- for field in union.fields %}
+{%- set kind = field.kind %}
+{%- set name = field.name %}
+
+{%- if kind|is_object_kind %}
+inline void {{union.name}}DataView::Get{{name|under_to_camel}}DataView(
+ {{kind|cpp_data_view_type}}* output) {
+ DCHECK(is_{{name}}());
+ *output = {{kind|cpp_data_view_type}}(data_->data.f_{{name}}.Get(), context_);
+}
+{%- endif %}
+{%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
new file mode 100644
index 0000000000..005ba76b61
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
@@ -0,0 +1,56 @@
+{%- set class_name = union.name ~ "_Data" -%}
+{%- set enum_name = union.name ~ "_Tag" -%}
+{%- import "struct_macros.tmpl" as struct_macros %}
+
+class {{class_name}} {
+ public:
+ // Used to identify Mojom Union Data Classes.
+ typedef void MojomUnionDataType;
+
+ {{class_name}}() {}
+ // Do nothing in the destructor since it won't be called when it is a
+ // non-inlined union.
+ ~{{class_name}}() {}
+
+ static {{class_name}}* New(mojo::internal::Buffer* buf) {
+ return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}();
+ }
+
+ static bool Validate(const void* data,
+ mojo::internal::ValidationContext* validation_context,
+ bool inlined);
+
+ bool is_null() const { return size == 0; }
+
+ void set_null() {
+ size = 0U;
+ tag = static_cast<{{enum_name}}>(0);
+ data.unknown = 0U;
+ }
+
+ enum class {{enum_name}} : uint32_t {
+{% for field in union.fields %}
+ {{field.name|upper}},
+{%- endfor %}
+ };
+
+ // A note on layout:
+ // "Each non-static data member is allocated as if it were the sole member of
+ // a struct." - Section 9.5.2 ISO/IEC 14882:2011 (The C++ Spec)
+ union MOJO_ALIGNAS(8) Union_ {
+{%- for field in union.fields %}
+{%- if field.kind.spec == 'b' %}
+ uint8_t f_{{field.name}} : 1;
+{%- else %}
+ {{field.kind|cpp_union_field_type}} f_{{field.name}};
+{%- endif %}
+{%- endfor %}
+ uint64_t unknown;
+ };
+
+ uint32_t size;
+ {{enum_name}} tag;
+ Union_ data;
+};
+static_assert(sizeof({{class_name}}) == mojo::internal::kUnionDataSize,
+ "Bad sizeof({{class_name}})");
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
new file mode 100644
index 0000000000..af5ea9f8a8
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
@@ -0,0 +1,47 @@
+{%- import "validation_macros.tmpl" as validation_macros %}
+{%- set class_name = union.name ~ "_Data" %}
+{%- set enum_name = union.name ~ "_Tag" -%}
+
+// static
+bool {{class_name}}::Validate(
+ const void* data,
+ mojo::internal::ValidationContext* validation_context,
+ bool inlined) {
+ if (!data) {
+ DCHECK(!inlined);
+ return true;
+ }
+
+ // If it is inlined, the alignment is already enforced by its enclosing
+ // object. We don't have to validate that.
+ DCHECK(!inlined || mojo::internal::IsAligned(data));
+
+ if (!inlined &&
+ !mojo::internal::ValidateNonInlinedUnionHeaderAndClaimMemory(
+ data, validation_context)) {
+ return false;
+ }
+
+ const {{class_name}}* object = static_cast<const {{class_name}}*>(data);
+ ALLOW_UNUSED_LOCAL(object);
+
+ if (inlined && object->is_null())
+ return true;
+
+ switch (object->tag) {
+{% for field in union.fields %}
+ case {{enum_name}}::{{field.name|upper}}: {
+{%- set field_expr = "object->data.f_" ~ field.name %}
+{{validation_macros.validate_field(field, field_expr, union.name, false)|indent(4)}}
+ return true;
+ }
+{%- endfor %}
+ default: {
+ ReportValidationError(
+ validation_context,
+ mojo::internal::VALIDATION_ERROR_UNKNOWN_UNION_TAG,
+ "unknown tag in {{union.name}}");
+ return false;
+ }
+ }
+}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
new file mode 100644
index 0000000000..b589ae9147
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
@@ -0,0 +1,141 @@
+{%- set data_view = union|get_qualified_name_for_kind ~ "DataView" %}
+{%- set data_type = union|get_qualified_name_for_kind(internal=True) %}
+
+namespace internal {
+
+template <typename MaybeConstUserType>
+struct Serializer<{{data_view}}, MaybeConstUserType> {
+ using UserType = typename std::remove_const<MaybeConstUserType>::type;
+ using Traits = UnionTraits<{{data_view}}, UserType>;
+
+ static size_t PrepareToSerialize(MaybeConstUserType& input,
+ bool inlined,
+ SerializationContext* context) {
+ size_t size = inlined ? 0 : sizeof({{data_type}});
+
+ if (CallIsNullIfExists<Traits>(input))
+ return size;
+
+ void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
+ ALLOW_UNUSED_LOCAL(custom_context);
+
+ switch (CallWithContext(Traits::GetTag, input, custom_context)) {
+{%- for field in union.fields %}
+{%- set name = field.name %}
+ case {{data_view}}::Tag::{{name|upper}}: {
+{%- if field.kind|is_object_kind or field.kind|is_associated_kind %}
+{%- set kind = field.kind %}
+{%- set serializer_type = kind|unmapped_type_for_serializer %}
+ decltype(CallWithContext(Traits::{{name}}, input, custom_context))
+ in_{{name}} = CallWithContext(Traits::{{name}}, input,
+ custom_context);
+{%- if kind|is_union_kind %}
+ size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
+ in_{{name}}, false, context);
+{%- else %}
+ size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
+ in_{{name}}, context);
+{%- endif %}
+{%- endif %}
+ break;
+ }
+{%- endfor %}
+ }
+ return size;
+ }
+
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buffer,
+ {{data_type}}** output,
+ bool inlined,
+ SerializationContext* context) {
+ if (CallIsNullIfExists<Traits>(input)) {
+ if (inlined)
+ (*output)->set_null();
+ else
+ *output = nullptr;
+ return;
+ }
+
+ void* custom_context = CustomContextHelper<Traits>::GetNext(context);
+
+ if (!inlined)
+ *output = {{data_type}}::New(buffer);
+
+ {{data_type}}* result = *output;
+ ALLOW_UNUSED_LOCAL(result);
+ // TODO(azani): Handle unknown and objects.
+ // Set the not-null flag.
+ result->size = kUnionDataSize;
+ result->tag = CallWithContext(Traits::GetTag, input, custom_context);
+ switch (result->tag) {
+{%- for field in union.fields %}
+{%- set name = field.name %}
+{%- set kind = field.kind %}
+{%- set serializer_type = kind|unmapped_type_for_serializer %}
+ case {{data_view}}::Tag::{{field.name|upper}}: {
+ decltype(CallWithContext(Traits::{{name}}, input, custom_context))
+ in_{{name}} = CallWithContext(Traits::{{name}}, input,
+ custom_context);
+{%- if kind|is_object_kind %}
+ typename decltype(result->data.f_{{name}})::BaseType* ptr;
+{%- if kind|is_union_kind %}
+ mojo::internal::Serialize<{{serializer_type}}>(
+ in_{{name}}, buffer, &ptr, false, context);
+{%- elif kind|is_array_kind or kind|is_map_kind %}
+ const ContainerValidateParams {{name}}_validate_params(
+ {{kind|get_container_validate_params_ctor_args|indent(16)}});
+ mojo::internal::Serialize<{{serializer_type}}>(
+ in_{{name}}, buffer, &ptr, &{{name}}_validate_params, context);
+{%- else %}
+ mojo::internal::Serialize<{{serializer_type}}>(
+ in_{{name}}, buffer, &ptr, context);
+{%- endif %}
+ result->data.f_{{name}}.Set(ptr);
+{%- if not kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !ptr, mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ "null {{name}} in {{union.name}} union");
+{%- endif %}
+
+{%- elif kind|is_any_handle_or_interface_kind %}
+ mojo::internal::Serialize<{{serializer_type}}>(
+ in_{{name}}, &result->data.f_{{name}}, context);
+{%- if not kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !mojo::internal::IsHandleOrInterfaceValid(result->data.f_{{name}}),
+{%- if kind|is_associated_kind %}
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+{%- else %}
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+{%- endif %}
+ "invalid {{name}} in {{union.name}} union");
+{%- endif %}
+
+{%- elif kind|is_enum_kind %}
+ mojo::internal::Serialize<{{serializer_type}}>(
+ in_{{name}}, &result->data.f_{{name}});
+
+{%- else %}
+ result->data.f_{{name}} = in_{{name}};
+{%- endif %}
+ break;
+ }
+{%- endfor %}
+ }
+
+ CustomContextHelper<Traits>::TearDown(input, custom_context);
+ }
+
+ static bool Deserialize({{data_type}}* input,
+ UserType* output,
+ SerializationContext* context) {
+ if (!input || input->is_null())
+ return CallSetToNullIfExists<Traits>(output);
+
+ {{data_view}} data_view(input, context);
+ return Traits::Read(data_view, output);
+ }
+};
+
+} // namespace internal
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl
new file mode 100644
index 0000000000..4933e57871
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl
@@ -0,0 +1,24 @@
+{%- set mojom_type = union|get_qualified_name_for_kind %}
+
+template <>
+struct {{export_attribute}} UnionTraits<{{mojom_type}}::DataView,
+ {{mojom_type}}Ptr> {
+ static bool IsNull(const {{mojom_type}}Ptr& input) { return !input; }
+ static void SetToNull({{mojom_type}}Ptr* output) { output->reset(); }
+
+ static {{mojom_type}}::Tag GetTag(const {{mojom_type}}Ptr& input) {
+ return input->which();
+ }
+
+{%- for field in union.fields %}
+{%- set maybe_const_in = "" if field.kind|contains_handles_or_interfaces else "const" %}
+{%- set maybe_const_out = "" if field.kind|contains_handles_or_interfaces or not field.kind|is_reference_kind else "const" %}
+{# We want the field accessor to be const whenever possible to allow
+ structs to be used as map keys. #}
+ static {{maybe_const_out}} {{field.kind|cpp_union_trait_getter_return_type}} {{field.name}}({{maybe_const_in}} {{mojom_type}}Ptr& input) {
+ return input->get_{{field.name}}();
+ }
+{%- endfor %}
+
+ static bool Read({{mojom_type}}::DataView input, {{mojom_type}}Ptr* output);
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl
new file mode 100644
index 0000000000..cde3f95669
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl
@@ -0,0 +1,47 @@
+{%- set mojom_type = union|get_qualified_name_for_kind %}
+
+// static
+bool UnionTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read(
+ {{mojom_type}}::DataView input,
+ {{mojom_type}}Ptr* output) {
+ *output = {{mojom_type}}::New();
+ {{mojom_type}}Ptr& result = *output;
+
+ internal::UnionAccessor<{{mojom_type}}> result_acc(result.get());
+ switch (input.tag()) {
+{%- for field in union.fields %}
+ case {{mojom_type}}::Tag::{{field.name|upper}}: {
+{%- set name = field.name %}
+{%- set kind = field.kind %}
+{%- set serializer_type = kind|unmapped_type_for_serializer %}
+{%- if kind|is_object_kind %}
+ result_acc.SwitchActive({{mojom_type}}::Tag::{{name|upper}});
+ if (!input.Read{{name|under_to_camel}}(result_acc.data()->{{name}}))
+ return false;
+
+{%- elif kind|is_any_handle_kind %}
+ auto result_{{name}} = input.Take{{name|under_to_camel}}();
+ result->set_{{name}}(std::move(result_{{name}}));
+
+{%- elif kind|is_any_interface_kind %}
+ auto result_{{name}} =
+ input.Take{{name|under_to_camel}}<typename std::remove_reference<decltype(result->get_{{name}}())>::type>();
+ result->set_{{name}}(std::move(result_{{name}}));
+
+{%- elif kind|is_enum_kind %}
+ decltype(result->get_{{name}}()) result_{{name}};
+ if (!input.Read{{name|under_to_camel}}(&result_{{name}}))
+ return false;
+ result->set_{{name}}(result_{{name}});
+
+{%- else %}
+ result->set_{{name}}(input.{{name}}());
+{%- endif %}
+ break;
+ }
+{%- endfor %}
+ default:
+ return false;
+ }
+ return true;
+}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl
new file mode 100644
index 0000000000..a50a585c09
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl
@@ -0,0 +1,82 @@
+{#- Validates the specified field, which is supposed to be an object
+ (struct/array/string/map/union). If it is a union, |union_is_inlined|
+ indicates whether the union is inlined. (Nested unions are not inlined.)
+ This macro is expanded by the Validate() method. #}
+{%- macro validate_object(field, field_expr, object_name, union_is_inlined) %}
+{%- set name = field.name %}
+{%- set kind = field.kind %}
+{%- if not kind|is_nullable_kind %}
+{%- if kind|is_union_kind and union_is_inlined %}
+ if (!mojo::internal::ValidateInlinedUnionNonNullable(
+ {{field_expr}}, "null {{name}} field in {{object_name}}",
+ validation_context)) {
+ return false;
+ }
+{%- else %}
+ if (!mojo::internal::ValidatePointerNonNullable(
+ {{field_expr}}, "null {{name}} field in {{object_name}}",
+ validation_context)) {
+ return false;
+ }
+{%- endif %}
+{%- endif %}
+{%- if kind|is_array_kind or kind|is_string_kind or kind|is_map_kind %}
+ const mojo::internal::ContainerValidateParams {{name}}_validate_params(
+ {{kind|get_container_validate_params_ctor_args|indent(6)}});
+ if (!mojo::internal::ValidateContainer({{field_expr}}, validation_context,
+ &{{name}}_validate_params)) {
+ return false;
+ }
+{%- elif kind|is_struct_kind %}
+ if (!mojo::internal::ValidateStruct({{field_expr}}, validation_context))
+ return false;
+{%- elif kind|is_union_kind %}
+{%- if union_is_inlined %}
+ if (!mojo::internal::ValidateInlinedUnion({{field_expr}}, validation_context))
+ return false;
+{%- else %}
+ if (!mojo::internal::ValidateNonInlinedUnion({{field_expr}},
+ validation_context))
+ return false;
+{%- endif %}
+{%- else %}
+#error Not reached!
+{%- endif %}
+{%- endmacro %}
+
+{#- Validates the specified field, which is supposed to be a handle,
+ an interface, an associated interface or an associated interface request.
+ This macro is expanded by the Validate() method. #}
+{%- macro validate_handle_or_interface(field, field_expr, object_name) %}
+{%- set name = field.name %}
+{%- set kind = field.kind %}
+{%- if not kind|is_nullable_kind %}
+ if (!mojo::internal::ValidateHandleOrInterfaceNonNullable(
+ {{field_expr}},
+ "invalid {{name}} field in {{object_name}}", validation_context)) {
+ return false;
+ }
+{%- endif %}
+ if (!mojo::internal::ValidateHandleOrInterface({{field_expr}},
+ validation_context)) {
+ return false;
+ }
+{%- endmacro %}
+
+{#- Validates the specified field, which is supposed to be an enum.
+ This macro is expanded by the Validate() method. #}
+{%- macro validate_enum(field, field_expr) %}
+ if (!{{field.kind|get_qualified_name_for_kind(internal=True,flatten_nested_kind=True)}}
+ ::Validate({{field_expr}}, validation_context))
+ return false;
+{%- endmacro %}
+
+{%- macro validate_field(field, field_expr, object_name, union_is_inlined) %}
+{%- if field.kind|is_object_kind -%}
+{{validate_object(field, field_expr, object_name, union_is_inlined)}}
+{%- elif field.kind|is_any_handle_or_interface_kind -%}
+{{validate_handle_or_interface(field, field_expr, object_name)}}
+{%- elif field.kind|is_enum_kind %}
+{{validate_enum(field, field_expr)}}
+{%- endif %}
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
new file mode 100644
index 0000000000..7ad9b4e1bc
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
@@ -0,0 +1,94 @@
+class {{export_attribute}} {{struct.name}} {
+ public:
+ using DataView = {{struct.name}}DataView;
+ using Data_ = internal::{{struct.name}}_Data;
+
+{#--- Enums #}
+{%- for enum in struct.enums -%}
+ using {{enum.name}} = {{enum|get_name_for_kind(flatten_nested_kind=True)}};
+{%- endfor %}
+
+{#--- Constants #}
+{%- for constant in struct.constants %}
+ static {{constant|format_constant_declaration(nested=True)}};
+{%- endfor %}
+
+ template <typename... Args>
+ static {{struct.name}}Ptr New(Args&&... args) {
+ return {{struct.name}}Ptr(
+ base::in_place,
+ std::forward<Args>(args)...);
+ }
+
+ template <typename U>
+ static {{struct.name}}Ptr From(const U& u) {
+ return mojo::TypeConverter<{{struct.name}}Ptr, U>::Convert(u);
+ }
+
+ template <typename U>
+ U To() const {
+ return mojo::TypeConverter<U, {{struct.name}}>::Convert(*this);
+ }
+
+{% for constructor in struct|struct_constructors %}
+ {% if constructor.params|length == 1 %}explicit {% endif %}{{struct.name}}(
+{%- for field in constructor.params %}
+{%- set type = field.kind|cpp_wrapper_param_type %}
+{%- set name = field.name %}
+ {{type}} {{name}}
+{%- if not loop.last -%},{%- endif %}
+{%- endfor %});
+{% endfor %}
+ ~{{struct.name}}();
+
+ // Clone() is a template so it is only instantiated if it is used. Thus, the
+ // bindings generator does not need to know whether Clone() or copy
+ // constructor/assignment are available for members.
+ template <typename StructPtrType = {{struct.name}}Ptr>
+ {{struct.name}}Ptr Clone() const;
+
+ // Equals() is a template so it is only instantiated if it is used. Thus, the
+ // bindings generator does not need to know whether Equals() or == operator
+ // are available for members.
+ template <typename T,
+ typename std::enable_if<std::is_same<
+ T, {{struct.name}}>::value>::type* = nullptr>
+ bool Equals(const T& other) const;
+
+{%- if struct|is_hashable %}
+ size_t Hash(size_t seed) const;
+{%- endif %}
+
+{%- set serialization_result_type = "WTF::Vector<uint8_t>"
+ if for_blink else "std::vector<uint8_t>" %}
+
+ template <typename UserType>
+ static {{serialization_result_type}} Serialize(UserType* input) {
+ return mojo::internal::StructSerializeImpl<
+ {{struct.name}}::DataView, {{serialization_result_type}}>(input);
+ }
+
+ template <typename UserType>
+ static bool Deserialize(const {{serialization_result_type}}& input,
+ UserType* output) {
+ return mojo::internal::StructDeserializeImpl<
+ {{struct.name}}::DataView, {{serialization_result_type}}>(
+ input, output, Validate);
+ }
+
+{#--- Struct members #}
+{% for field in struct.fields %}
+{%- set type = field.kind|cpp_wrapper_type %}
+{%- set name = field.name %}
+ {{type}} {{name}};
+{%- endfor %}
+
+ private:
+ static bool Validate(const void* data,
+ mojo::internal::ValidationContext* validation_context);
+
+{%- if struct|contains_move_only_members %}
+ DISALLOW_COPY_AND_ASSIGN({{struct.name}});
+{%- endif %}
+};
+
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
new file mode 100644
index 0000000000..ab8c22d49c
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
@@ -0,0 +1,39 @@
+{% for constructor in struct|struct_constructors %}
+{{struct.name}}::{{struct.name}}(
+{%- for field in constructor.params %}
+{%- set type = field.kind|cpp_wrapper_param_type %}
+{%- set name = field.name %}
+ {{type}} {{name}}_in
+{%- if not loop.last -%},{%- endif %}
+{%- endfor %})
+{%- for field, is_parameter in constructor.fields %}
+{%- set name = field.name %}
+ {% if loop.first %}:{% else %} {% endif %} {{name}}(
+{%- if is_parameter -%}
+std::move({{name}}_in)
+{%- else -%}
+{{ field|default_value }}
+{%- endif -%}
+){% if not loop.last %},{% endif %}
+{%- endfor %} {}
+{% endfor %}
+{{struct.name}}::~{{struct.name}}() = default;
+
+{%- if struct|is_hashable %}
+size_t {{struct.name}}::Hash(size_t seed) const {
+{%- for field in struct.fields %}
+{%- if for_blink %}
+ seed = mojo::internal::WTFHash(seed, this->{{field.name}});
+{%- else %}
+ seed = mojo::internal::Hash(seed, this->{{field.name}});
+{%- endif %}
+{%- endfor %}
+ return seed;
+}
+{%- endif %}
+
+bool {{struct.name}}::Validate(
+ const void* data,
+ mojo::internal::ValidationContext* validation_context) {
+ return Data_::Validate(data, validation_context);
+}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl
new file mode 100644
index 0000000000..feb861569f
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl
@@ -0,0 +1,20 @@
+template <typename StructPtrType>
+{{struct.name}}Ptr {{struct.name}}::Clone() const {
+ return New(
+{%- for field in struct.fields %}
+ mojo::Clone({{field.name}})
+{%- if not loop.last -%},{%- endif %}
+{%- endfor %}
+ );
+}
+
+template <typename T,
+ typename std::enable_if<std::is_same<
+ T, {{struct.name}}>::value>::type*>
+bool {{struct.name}}::Equals(const T& other) const {
+{%- for field in struct.fields %}
+ if (!mojo::internal::Equals(this->{{field.name}}, other.{{field.name}}))
+ return false;
+{%- endfor %}
+ return true;
+}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
new file mode 100644
index 0000000000..8b7cf9e6b1
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
@@ -0,0 +1,80 @@
+class {{export_attribute}} {{union.name}} {
+ public:
+ using DataView = {{union.name}}DataView;
+ using Data_ = internal::{{union.name}}_Data;
+ using Tag = Data_::{{union.name}}_Tag;
+
+ static {{union.name}}Ptr New();
+
+ template <typename U>
+ static {{union.name}}Ptr From(const U& u) {
+ return mojo::TypeConverter<{{union.name}}Ptr, U>::Convert(u);
+ }
+
+ template <typename U>
+ U To() const {
+ return mojo::TypeConverter<U, {{union.name}}>::Convert(*this);
+ }
+
+ {{union.name}}();
+ ~{{union.name}}();
+
+ // Clone() is a template so it is only instantiated if it is used. Thus, the
+ // bindings generator does not need to know whether Clone() or copy
+ // constructor/assignment are available for members.
+ template <typename UnionPtrType = {{union.name}}Ptr>
+ {{union.name}}Ptr Clone() const;
+
+ // Equals() is a template so it is only instantiated if it is used. Thus, the
+ // bindings generator does not need to know whether Equals() or == operator
+ // are available for members.
+ template <typename T,
+ typename std::enable_if<std::is_same<
+ T, {{union.name}}>::value>::type* = nullptr>
+ bool Equals(const T& other) const;
+
+{%- if union|is_hashable %}
+ size_t Hash(size_t seed) const;
+{%- endif %}
+
+ Tag which() const {
+ return tag_;
+ }
+
+{% for field in union.fields %}
+ bool is_{{field.name}}() const { return tag_ == Tag::{{field.name|upper}}; }
+
+ {{field.kind|cpp_union_getter_return_type}} get_{{field.name}}() const {
+ DCHECK(tag_ == Tag::{{field.name|upper}});
+{%- if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ return *(data_.{{field.name}});
+{%- else %}
+ return data_.{{field.name}};
+{%- endif %}
+ }
+
+ void set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}});
+{%- endfor %}
+
+ private:
+ friend class mojo::internal::UnionAccessor<{{union.name}}>;
+ union Union_ {
+ Union_() {}
+ ~Union_() {}
+
+{%- for field in union.fields %}
+{%- if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ {{field.kind|cpp_wrapper_type}}* {{field.name}};
+{%- else %}
+ {{field.kind|cpp_wrapper_type}} {{field.name}};
+{%- endif %}
+{%- endfor %}
+ };
+ void SwitchActive(Tag new_active);
+ void SetActive(Tag new_active);
+ void DestroyActive();
+ Tag tag_;
+ Union_ data_;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
new file mode 100644
index 0000000000..b9e416a9f4
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
@@ -0,0 +1,85 @@
+// static
+{{union.name}}Ptr {{union.name}}::New() {
+ return {{union.name}}Ptr(base::in_place);
+}
+
+{{union.name}}::{{union.name}}() {
+ // TODO(azani): Implement default values here when/if we support them.
+ // TODO(azani): Set to UNKNOWN when unknown is implemented.
+ SetActive(static_cast<Tag>(0));
+}
+
+{{union.name}}::~{{union.name}}() {
+ DestroyActive();
+}
+
+{% for field in union.fields %}
+void {{union.name}}::set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}}) {
+ SwitchActive(Tag::{{field.name|upper}});
+{% if field.kind|is_string_kind %}
+ *(data_.{{field.name}}) = {{field.name}};
+{% elif field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ *(data_.{{field.name}}) = std::move({{field.name}});
+{%- else %}
+ data_.{{field.name}} = {{field.name}};
+{%- endif %}
+}
+{%- endfor %}
+
+void {{union.name}}::SwitchActive(Tag new_active) {
+ if (new_active == tag_) {
+ return;
+ }
+
+ DestroyActive();
+ SetActive(new_active);
+}
+
+void {{union.name}}::SetActive(Tag new_active) {
+ switch (new_active) {
+{% for field in union.fields %}
+ case Tag::{{field.name|upper}}:
+{% if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ data_.{{field.name}} = new {{field.kind|cpp_wrapper_type}}();
+{%- endif %}
+ break;
+{%- endfor %}
+ }
+
+ tag_ = new_active;
+}
+
+void {{union.name}}::DestroyActive() {
+ switch (tag_) {
+{% for field in union.fields %}
+ case Tag::{{field.name|upper}}:
+{% if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ delete data_.{{field.name}};
+{%- endif %}
+ break;
+{%- endfor %}
+ }
+}
+
+{%- if union|is_hashable %}
+size_t {{union.name}}::Hash(size_t seed) const {
+ seed = mojo::internal::HashCombine(seed, static_cast<uint32_t>(tag_));
+ switch (tag_) {
+{% for field in union.fields %}
+ case Tag::{{field.name|upper}}:
+{%- if for_blink %}
+ return mojo::internal::WTFHash(seed, data_.{{field.name}});
+{%- else %}
+ return mojo::internal::Hash(seed, data_.{{field.name}});
+{%- endif %}
+{%- endfor %}
+ default:
+ NOTREACHED();
+ return seed;
+ }
+}
+
+{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl
new file mode 100644
index 0000000000..4c4851fa83
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl
@@ -0,0 +1,41 @@
+template <typename UnionPtrType>
+{{union.name}}Ptr {{union.name}}::Clone() const {
+ // Use UnionPtrType to prevent the compiler from trying to compile this
+ // without being asked.
+ UnionPtrType rv(New());
+ switch (tag_) {
+{%- for field in union.fields %}
+ case Tag::{{field.name|upper}}:
+{%- if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ rv->set_{{field.name}}(mojo::Clone(*data_.{{field.name}}));
+{%- else %}
+ rv->set_{{field.name}}(mojo::Clone(data_.{{field.name}}));
+{%- endif %}
+ break;
+{%- endfor %}
+ };
+ return rv;
+}
+
+template <typename T,
+ typename std::enable_if<std::is_same<
+ T, {{union.name}}>::value>::type*>
+bool {{union.name}}::Equals(const T& other) const {
+ if (tag_ != other.which())
+ return false;
+
+ switch (tag_) {
+{%- for field in union.fields %}
+ case Tag::{{field.name|upper}}:
+{%- if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind %}
+ return mojo::internal::Equals(*(data_.{{field.name}}), *(other.data_.{{field.name}}));
+{%- else %}
+ return mojo::internal::Equals(data_.{{field.name}}, other.data_.{{field.name}});
+{%- endif %}
+{%- endfor %}
+ };
+
+ return false;
+}
diff --git a/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl
new file mode 100644
index 0000000000..db193e29a3
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl
@@ -0,0 +1,3 @@
+{% macro constant_def(constant) %}
+public static final {{constant.kind|java_type}} {{constant|name}} = {{constant|constant_value}};
+{% endmacro %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl
new file mode 100644
index 0000000000..0a4e29956b
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl
@@ -0,0 +1,12 @@
+{% from "constant_definition.tmpl" import constant_def %}
+{% include "header.java.tmpl" %}
+
+public final class {{main_entity}} {
+{% for constant in constants %}
+
+ {{constant_def(constant)|indent(4)}}
+{% endfor %}
+
+ private {{main_entity}}() {}
+
+}
diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
new file mode 100644
index 0000000000..4c0823cce6
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
@@ -0,0 +1,418 @@
+{%- from "constant_definition.tmpl" import constant_def %}
+{%- from "enum_definition.tmpl" import enum_def %}
+
+{%- macro equality(kind, v1, v2, ne=False) -%}
+{%- if kind|is_reference_kind -%}
+{%- if kind|is_array_kind -%}
+{%- if kind.kind|is_reference_kind -%}
+{%- if ne %}!{%- endif %}java.util.Arrays.deepEquals({{v1}}, {{v2}})
+{%- else -%}
+{%- if ne %}!{%- endif %}java.util.Arrays.equals({{v1}}, {{v2}})
+{%- endif -%}
+{%- else -%}
+{%- if ne %}!{%- endif %}org.chromium.mojo.bindings.BindingsHelper.equals({{v1}}, {{v2}})
+{%- endif -%}
+{%- else -%}
+{{v1}} {%- if ne %}!={%- else %}=={%- endif %} {{v2}}
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro hash(kind, v) -%}
+{%- if kind|is_array_kind -%}
+{%- if kind.kind|is_reference_kind -%}
+java.util.Arrays.deepHashCode({{v}})
+{%- else -%}
+java.util.Arrays.hashCode({{v}})
+{%- endif -%}
+{%- else -%}
+org.chromium.mojo.bindings.BindingsHelper.hashCode({{v}})
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro array_element_size(kind) -%}
+{%- if kind|is_union_kind %}
+org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE
+{%- else -%}
+org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro encode(variable, kind, offset, bit, level=0, check_for_null=True) %}
+{%- if kind|is_pointer_array_kind or kind|is_union_array_kind %}
+{%- set sub_kind = kind.kind %}
+{%- if check_for_null %}
+if ({{variable}} == null) {
+ encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}});
+} else {
+{%- else %}
+{
+{%- endif %}
+{%- if kind|is_pointer_array_kind %}
+{%- set encodePointer = 'encodePointerArray' %}
+{%- else %}
+{%- set encodePointer = 'encodeUnionArray' %}
+{%- endif %}
+ org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.{{encodePointer}}({{variable}}.length, {{offset}}, {{kind|array_expected_length}});
+ for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) {
+ {{encode(variable~'[i'~level~']', sub_kind, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + ' ~ array_element_size(sub_kind) ~ ' * i'~level, 0, level+1)|indent(8)}}
+ }
+}
+{%- elif kind|is_map_kind %}
+if ({{variable}} == null) {
+ encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}});
+} else {
+ org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.encoderForMap({{offset}});
+ int size{{level}} = {{variable}}.size();
+ {{kind.key_kind|java_type}}[] keys{{level}} = {{kind.key_kind|array|new_array('size'~level)}};
+ {{kind.value_kind|java_type}}[] values{{level}} = {{kind.value_kind|array|new_array('size'~level)}};
+ int index{{level}} = 0;
+ for (java.util.Map.Entry<{{kind.key_kind|java_type(true)}}, {{kind.value_kind|java_type(true)}}> entry{{level}} : {{variable}}.entrySet()) {
+ keys{{level}}[index{{level}}] = entry{{level}}.getKey();
+ values{{level}}[index{{level}}] = entry{{level}}.getValue();
+ ++index{{level}};
+ }
+ {{encode('keys'~level, kind.key_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0, level+1, False)|indent(4)}}
+ {{encode('values'~level, kind.value_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE', 0, level+1, False)|indent(4)}}
+}
+{%- else %}
+encoder{{level}}.{{kind|encode_method(variable, offset, bit)}};
+{%- endif %}
+{%- endmacro %}
+
+{%- macro decode(variable, kind, offset, bit, level=0) %}
+{%- if kind|is_struct_kind or
+ kind|is_pointer_array_kind or
+ kind|is_union_array_kind or
+ kind|is_map_kind %}
+org.chromium.mojo.bindings.Decoder decoder{{level+1}} = decoder{{level}}.readPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}});
+{%- if kind|is_struct_kind %}
+{{variable}} = {{kind|java_type}}.decode(decoder{{level+1}});
+{%- else %}{# kind|is_pointer_array_kind or is_map_kind #}
+{%- if kind|is_nullable_kind %}
+if (decoder{{level+1}} == null) {
+ {{variable}} = null;
+} else {
+{%- else %}
+{
+{%- endif %}
+{%- if kind|is_map_kind %}
+ decoder{{level+1}}.readDataHeaderForMap();
+ {{kind.key_kind|java_type}}[] keys{{level}};
+ {{kind.value_kind|java_type}}[] values{{level}};
+ {
+ {{decode('keys'~level, kind.key_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0, level+1)|indent(8)}}
+ }
+ {
+ {{decode('values'~level, kind.value_kind|array('keys'~level~'.length'), 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE', 0, level+1)|indent(8)}}
+ }
+ {{variable}} = new java.util.HashMap<{{kind.key_kind|java_type(true)}}, {{kind.value_kind|java_type(true)}}>();
+ for (int index{{level}} = 0; index{{level}} < keys{{level}}.length; ++index{{level}}) {
+ {{variable}}.put(keys{{level}}[index{{level}}], values{{level}}[index{{level}}]);
+ }
+{%- else %}
+ org.chromium.mojo.bindings.DataHeader si{{level+1}} = decoder{{level+1}}.readDataHeaderForPointerArray({{kind|array_expected_length}});
+ {{variable}} = {{kind|new_array('si'~(level+1)~'.elementsOrVersion')}};
+ for (int i{{level+1}} = 0; i{{level+1}} < si{{level+1}}.elementsOrVersion; ++i{{level+1}}) {
+ {{decode(variable~'[i'~(level+1)~']', kind.kind, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + ' ~ array_element_size(kind.kind) ~' * i'~(level+1), 0, level+1)|indent(8)}}
+ }
+{%- endif %}
+}
+{%- endif %}
+{%- elif kind|is_union_kind %}
+{{variable}} = {{kind|java_type}}.decode(decoder{{level}}, {{offset}});
+{%- else %}
+{{variable}} = decoder{{level}}.{{kind|decode_method(offset, bit)}};
+{%- if kind|is_array_kind and kind.kind|is_enum_kind %}
+{%- if kind|is_nullable_kind %}
+if ({{variable}} != null) {
+{%- else %}
+{
+{%- endif %}
+ for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) {
+ {{kind.kind|java_class_for_enum}}.validate({{variable}}[i{{level}}]);
+ }
+}
+{%- elif kind|is_enum_kind %}
+ {{kind|java_class_for_enum}}.validate({{variable}});
+{%- endif %}
+{%- endif %}
+{%- endmacro %}
+
+{%- macro struct_def(struct, inner_class=False) %}
+{{'static' if inner_class else 'public'}} final class {{struct|name}} extends org.chromium.mojo.bindings.Struct {
+
+ private static final int STRUCT_SIZE = {{struct.versions[-1].num_bytes}};
+ private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {
+{%- for version in struct.versions -%}
+ new org.chromium.mojo.bindings.DataHeader({{version.num_bytes}}, {{version.version}}){%- if not loop.last %}, {%- endif -%}
+{%- endfor -%}
+ };
+ private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[{{struct.versions|length - 1}}];
+{%- for constant in struct.constants %}
+
+ {{constant_def(constant)|indent(4)}}
+{%- endfor %}
+{%- for enum in struct.enums %}
+
+ {{enum_def(enum, false)|indent(4)}}
+{%- endfor %}
+{%- if struct.fields %}
+
+{%- for field in struct.fields %}
+ public {{field.kind|java_type}} {{field|name}};
+{%- endfor %}
+{%- endif %}
+
+ private {{struct|name}}(int version) {
+ super(STRUCT_SIZE, version);
+{%- for field in struct.fields %}
+{%- if field.default %}
+ {{field|name}} = {{field|default_value}};
+{%- elif field.kind|is_any_handle_kind %}
+ {{field|name}} = org.chromium.mojo.system.InvalidHandle.INSTANCE;
+{%- endif %}
+{%- endfor %}
+ }
+
+ public {{struct|name}}() {
+ this({{struct.versions[-1].version}});
+ }
+
+ public static {{struct|name}} deserialize(org.chromium.mojo.bindings.Message message) {
+ return decode(new org.chromium.mojo.bindings.Decoder(message));
+ }
+
+ /**
+ * Similar to the method above, but deserializes from a |ByteBuffer| instance.
+ *
+ * @throws org.chromium.mojo.bindings.DeserializationException on deserialization failure.
+ */
+ public static {{struct|name}} deserialize(java.nio.ByteBuffer data) {
+ if (data == null)
+ return null;
+
+ return deserialize(new org.chromium.mojo.bindings.Message(
+ data, new java.util.ArrayList<org.chromium.mojo.system.Handle>()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static {{struct|name}} decode(org.chromium.mojo.bindings.Decoder decoder0) {
+ if (decoder0 == null) {
+ return null;
+ }
+ decoder0.increaseStackDepth();
+ {{struct|name}} result;
+ try {
+ org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+ result = new {{struct|name}}(mainDataHeader.elementsOrVersion);
+{%- for byte in struct.bytes %}
+{%- for packed_field in byte.packed_fields %}
+ if (mainDataHeader.elementsOrVersion >= {{packed_field.min_version}}) {
+ {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}}
+ }
+{%- endfor %}
+{%- endfor %}
+ } finally {
+ decoder0.decreaseStackDepth();
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+{%- if not struct.bytes %}
+ encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+{%- else %}
+ org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+{%- endif %}
+{%- for byte in struct.bytes %}
+{%- for packed_field in byte.packed_fields %}
+ {{encode(packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(8)}}
+{%- endfor %}
+{%- endfor %}
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == this)
+ return true;
+ if (object == null)
+ return false;
+ if (getClass() != object.getClass())
+ return false;
+{%- if struct.fields|length %}
+ {{struct|name}} other = ({{struct|name}}) object;
+{%- for field in struct.fields %}
+ if ({{equality(field.kind, 'this.'~field|name, 'other.'~field|name, True)}})
+ return false;
+{%- endfor %}
+{%- endif %}
+ return true;
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = prime + getClass().hashCode();
+{%- for field in struct.fields %}
+ result = prime * result + {{hash(field.kind, field|name)}};
+{%- endfor %}
+ return result;
+ }
+}
+{%- endmacro %}
+
+
+{%- macro union_def(union) %}
+public final class {{union|name}} extends org.chromium.mojo.bindings.Union {
+
+ public static final class Tag {
+{%- for field in union.fields %}
+ public static final int {{field|ucc}} = {{loop.index0}};
+{%- endfor %}
+ };
+
+ private int mTag_ = -1;
+{%- for field in union.fields %}
+ private {{field.kind|java_type}} m{{field|ucc}};
+{%- endfor %}
+
+ public int which() {
+ return mTag_;
+ }
+
+ public boolean isUnknown() {
+ return mTag_ == -1;
+ }
+{%- for field in union.fields %}
+
+ // TODO(rockot): Fix the findbugs error and remove this suppression.
+ // See http://crbug.com/570386.
+ @SuppressFBWarnings("EI_EXPOSE_REP2")
+ public void set{{field|ucc}}({{field.kind|java_type}} {{field|name}}) {
+ mTag_ = Tag.{{field|ucc}};
+ m{{field|ucc}} = {{field|name}};
+ }
+
+ // TODO(rockot): Fix the findbugs error and remove this suppression.
+ // See http://crbug.com/570386.
+ @SuppressFBWarnings("EI_EXPOSE_REP")
+ public {{field.kind|java_type}} get{{field|ucc}}() {
+ assert mTag_ == Tag.{{field|ucc}};
+ return m{{field|ucc}};
+ }
+{%- endfor %}
+
+
+ @Override
+ protected final void encode(org.chromium.mojo.bindings.Encoder encoder0, int offset) {
+ encoder0.encode(org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE, offset);
+ encoder0.encode(mTag_, offset + 4);
+ switch (mTag_) {
+{%- for field in union.fields %}
+ case Tag.{{field|ucc}}: {
+{%- if field.kind|is_union_kind %}
+ if (m{{field|ucc}} == null) {
+ encoder0.encodeNullPointer(offset + 8, {{field.kind|is_nullable_kind|java_true_false}});
+ } else {
+ m{{field|ucc}}.encode(encoder0.encoderForUnionPointer(offset + 8), 0);
+ }
+{%- else %}
+ {{encode('m' ~ field|ucc, field.kind, 'offset + 8', 0)|indent(16)}}
+{%- endif %}
+ break;
+ }
+{%- endfor %}
+ default: {
+ break;
+ }
+ }
+ }
+
+ public static {{union|name}} deserialize(org.chromium.mojo.bindings.Message message) {
+ return decode(new org.chromium.mojo.bindings.Decoder(message).decoderForSerializedUnion(), 0);
+ }
+
+ public static final {{union|name}} decode(org.chromium.mojo.bindings.Decoder decoder0, int offset) {
+ org.chromium.mojo.bindings.DataHeader dataHeader = decoder0.readDataHeaderForUnion(offset);
+ if (dataHeader.size == 0) {
+ return null;
+ }
+ {{union|name}} result = new {{union|name}}();
+ switch (dataHeader.elementsOrVersion) {
+{%- for field in union.fields %}
+ case Tag.{{field|ucc}}: {
+{%- if field.kind|is_union_kind %}
+ org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE, {{field.kind|is_nullable_kind|java_true_false}});
+ if (decoder1 != null) {
+ result.m{{field|ucc}} = {{field.kind|name}}.decode(decoder1, 0);
+ }
+{%- else %}
+ {{decode('result.m'~field|ucc, field.kind, 'offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0)|indent(16)}}
+{%- endif %}
+ result.mTag_ = Tag.{{field|ucc}};
+ break;
+ }
+{%- endfor %}
+ default: {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == this)
+ return true;
+ if (object == null)
+ return false;
+ if (getClass() != object.getClass())
+ return false;
+ {{union|name}} other = ({{union|name}}) object;
+ if (mTag_ != other.mTag_)
+ return false;
+ switch (mTag_) {
+{%- for field in union.fields %}
+ case Tag.{{field|ucc}}:
+ return {{equality(field.kind, 'm'~field|ucc, 'other.m'~field|ucc)}};
+{%- endfor %}
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = prime + getClass().hashCode();
+ result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(mTag_);
+ switch (mTag_) {
+{%- for field in union.fields %}
+ case Tag.{{field|ucc}}: {
+ result = prime * result + {{hash(field.kind, 'm'~field|ucc)}};
+ break;
+ }
+{%- endfor %}
+ default: {
+ break;
+ }
+ }
+ return result;
+ }
+}
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl
new file mode 100644
index 0000000000..7096a18747
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl
@@ -0,0 +1,4 @@
+{% from "enum_definition.tmpl" import enum_def %}
+{% include "header.java.tmpl" %}
+
+{{enum_def(enum, true)}}
diff --git a/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl
new file mode 100644
index 0000000000..d37288ac5d
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl
@@ -0,0 +1,42 @@
+{%- macro enum_value(enum, field, index) -%}
+{%- if field.value -%}
+(int) ({{field.value|expression_to_text('i32')}})
+{%- elif index == 0 -%}
+0
+{%- else -%}
+{{enum.fields[index - 1]|name}} + 1
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro enum_def(enum, top_level) -%}
+public {{ 'static ' if not top_level }}final class {{enum|name}} {
+
+{% for field in enum.fields %}
+ public static final int {{field|name}} = {{enum_value(enum, field, loop.index0)}};
+{% endfor %}
+
+ private static final boolean IS_EXTENSIBLE = {% if enum.extensible %}true{% else %}false{% endif %};
+
+ public static boolean isKnownValue(int value) {
+{%- if enum.fields %}
+ switch (value) {
+{%- for enum_field in enum.fields|groupby('numeric_value') %}
+ case {{enum_field[0]}}:
+{%- endfor %}
+ return true;
+ }
+{%- endif %}
+ return false;
+ }
+
+ public static void validate(int value) {
+ if (IS_EXTENSIBLE || isKnownValue(value))
+ return;
+
+ throw new DeserializationException("Invalid enum value.");
+ }
+
+ private {{enum|name}}() {}
+
+}
+{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl
new file mode 100644
index 0000000000..1d67890452
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl
@@ -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.
+
+// This file is autogenerated by:
+// mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+// {{module.path}}
+//
+
+package {{package}};
+
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.mojo.bindings.DeserializationException; \ No newline at end of file
diff --git a/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl
new file mode 100644
index 0000000000..a13be3ef60
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl
@@ -0,0 +1,4 @@
+{% from "interface_definition.tmpl" import interface_def %}
+{% include "header.java.tmpl" %}
+
+{{ interface_def(interface) }}
diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
new file mode 100644
index 0000000000..a723f8c393
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
@@ -0,0 +1,297 @@
+{% from "constant_definition.tmpl" import constant_def %}
+{% from "enum_definition.tmpl" import enum_def %}
+{% from "data_types_definition.tmpl" import struct_def %}
+
+{%- macro declare_params(parameters, boxed=false) %}
+{%- for param in parameters -%}
+{{param.kind|java_type(boxed)}} {{param|name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor %}
+{%- endmacro %}
+
+{% macro declare_request_params(method) %}
+{{declare_params(method.parameters)}}
+{%- if method.response_parameters != None -%}
+{%- if method.parameters %}, {% endif %}
+{{method|interface_response_name}} callback
+{%- endif -%}
+{% endmacro %}
+
+{%- macro declare_callback(method) -%}
+
+interface {{method|interface_response_name}} extends org.chromium.mojo.bindings.Callbacks.Callback{{method.response_parameters|length}}{% if method.response_parameters %}<
+{%- for param in method.response_parameters -%}
+{{param.kind|java_type(True)}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor -%}
+>{% endif %} { }
+{%- endmacro -%}
+
+{%- macro run_callback(variable, parameters) -%}
+{%- if parameters -%}
+{%- for param in parameters -%}
+{{variable}}.{{param|name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor -%}
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro flags_for_method(method, is_request) -%}
+{{flags(method.response_parameters != None, is_request)}}
+{%- endmacro -%}
+
+{%- macro flags(use_response_flag, is_request) -%}
+{%- if not use_response_flag -%}
+org.chromium.mojo.bindings.MessageHeader.NO_FLAG
+{%- elif is_request: -%}
+org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG
+{%- else -%}
+org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro manager_class(interface, fully_qualified=False) -%}
+{% if fully_qualified %}org.chromium.mojo.bindings.Interface.{% endif %}Manager<{{interface|name}}, {{interface|name}}.Proxy>
+{%- endmacro -%}
+
+{%- macro manager_def(interface) -%}
+public static final {{manager_class(interface, True)}} MANAGER =
+ new {{manager_class(interface, True)}}() {
+
+ public String getName() {
+ return "{{namespace|replace(".","::")}}::{{interface.name}}";
+ }
+
+ public int getVersion() {
+ return {{interface.version}};
+ }
+
+ public Proxy buildProxy(org.chromium.mojo.system.Core core,
+ org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+ return new Proxy(core, messageReceiver);
+ }
+
+ public Stub buildStub(org.chromium.mojo.system.Core core, {{interface|name}} impl) {
+ return new Stub(core, impl);
+ }
+
+ public {{interface|name}}[] buildArray(int size) {
+ return new {{interface|name}}[size];
+ }
+};
+{%- endmacro -%}
+
+{%- macro accept_body(interface, with_response) -%}
+try {
+ org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+ message.asServiceMessage();
+ org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+ if (!header.validateHeader({{flags(with_response, True)}})) {
+ return false;
+ }
+ switch(header.getType()) {
+{% if with_response %}
+ case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_MESSAGE_ID:
+ return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun(
+ getCore(), {{interface|name}}_Internal.MANAGER, messageWithHeader, receiver);
+{% else %}
+ case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID:
+ return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe(
+ {{interface|name}}_Internal.MANAGER, messageWithHeader);
+{% endif %}
+{% for method in interface.methods %}
+{% if (with_response and method.response_parameters != None) or
+ (not with_response and method.response_parameters == None) %}
+{% set request_struct = method.param_struct %}
+{% if with_response %}
+{% set response_struct = method.response_param_struct %}
+{% endif %}
+ case {{method|method_ordinal_name}}: {
+{% if method.parameters %}
+ {{request_struct|name}} data =
+ {{request_struct|name}}.deserialize(messageWithHeader.getPayload());
+{% else %}
+ {{request_struct|name}}.deserialize(messageWithHeader.getPayload());
+{% endif %}
+ try {
+ getImpl().{{method|name}}({{run_callback('data', method.parameters)}}{% if with_response %}{% if method.parameters %}, {% endif %}new {{response_struct|name}}ProxyToResponder(getCore(), receiver, header.getRequestId()){% endif %});
+ } catch (RuntimeException e) {
+ // TODO(lhchavez): Remove this hack. See b/28814913 for details.
+ android.util.Log.wtf("{{namespace}}.{{interface.name}}", "Uncaught runtime exception", e);
+ }
+ return true;
+ }
+{% endif %}
+{% endfor %}
+ default:
+ return false;
+ }
+} catch (org.chromium.mojo.bindings.DeserializationException e) {
+ System.err.println(e.toString());
+ return false;
+}
+{%- endmacro -%}
+
+{% macro interface_def(interface) %}
+public interface {{interface|name}} extends org.chromium.mojo.bindings.Interface {
+{% for constant in interface.constants %}
+
+ {{constant_def(constant)|indent(4)}}
+{% endfor %}
+{% for enum in interface.enums %}
+
+ {{enum_def(enum, false)|indent(4)}}
+{% endfor %}
+
+ public interface Proxy extends {{interface|name}}, org.chromium.mojo.bindings.Interface.Proxy {
+ }
+
+ {{manager_class(interface)}} MANAGER = {{interface|name}}_Internal.MANAGER;
+{% for method in interface.methods %}
+
+ void {{method|name}}({{declare_request_params(method)}});
+{% if method.response_parameters != None %}
+ {{declare_callback(method)|indent(4)}}
+{% endif %}
+{% endfor %}
+}
+{% endmacro %}
+
+{% macro interface_internal_def(interface) %}
+class {{interface|name}}_Internal {
+
+ {{manager_def(interface)|indent(4)}}
+
+{% for method in interface.methods %}
+ private static final int {{method|method_ordinal_name}} = {{method.ordinal}};
+{% endfor %}
+
+ static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements {{interface|name}}.Proxy {
+
+ Proxy(org.chromium.mojo.system.Core core,
+ org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+ super(core, messageReceiver);
+ }
+{% for method in interface.methods %}
+
+ @Override
+ public void {{method|name}}({{declare_request_params(method)}}) {
+{% set request_struct = method.param_struct %}
+ {{request_struct|name}} _message = new {{request_struct|name}}();
+{% for param in method.parameters %}
+ _message.{{param|name}} = {{param|name}};
+{% endfor %}
+{% if method.response_parameters != None %}
+ getProxyHandler().getMessageReceiver().acceptWithResponder(
+ _message.serializeWithHeader(
+ getProxyHandler().getCore(),
+ new org.chromium.mojo.bindings.MessageHeader(
+ {{method|method_ordinal_name}},
+ {{flags_for_method(method, True)}},
+ 0)),
+ new {{method.response_param_struct|name}}ForwardToCallback(callback));
+{% else %}
+ getProxyHandler().getMessageReceiver().accept(
+ _message.serializeWithHeader(
+ getProxyHandler().getCore(),
+ new org.chromium.mojo.bindings.MessageHeader({{method|method_ordinal_name}})));
+{% endif %}
+ }
+{% endfor %}
+
+ }
+
+ static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<{{interface|name}}> {
+
+ Stub(org.chromium.mojo.system.Core core, {{interface|name}} impl) {
+ super(core, impl);
+ }
+
+ @Override
+ public boolean accept(org.chromium.mojo.bindings.Message message) {
+ {{accept_body(interface, False)|indent(12)}}
+ }
+
+ @Override
+ public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) {
+ {{accept_body(interface, True)|indent(12)}}
+ }
+ }
+{% for method in interface.methods %}
+
+ {{ struct_def(method.param_struct, True)|indent(4) }}
+{% if method.response_parameters != None %}
+{% set response_struct = method.response_param_struct %}
+
+ {{ struct_def(response_struct, True)|indent(4) }}
+
+ static class {{response_struct|name}}ForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable
+ implements org.chromium.mojo.bindings.MessageReceiver {
+ private final {{interface|name}}.{{method|interface_response_name}} mCallback;
+
+ {{response_struct|name}}ForwardToCallback({{interface|name}}.{{method|interface_response_name}} callback) {
+ this.mCallback = callback;
+ }
+
+ @Override
+ public boolean accept(org.chromium.mojo.bindings.Message message) {
+ try {
+ org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+ message.asServiceMessage();
+ org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+ if (!header.validateHeader({{method|method_ordinal_name}},
+ {{flags_for_method(method, False)}})) {
+ return false;
+ }
+{% if method.response_parameters|length %}
+ {{response_struct|name}} response = {{response_struct|name}}.deserialize(messageWithHeader.getPayload());
+{% endif %}
+ try {
+ mCallback.call({{run_callback('response', method.response_parameters)}});
+ } catch (RuntimeException e) {
+ // TODO(lhchavez): Remove this hack. See b/28814913 for details.
+ android.util.Log.wtf("{{namespace}}.{{interface.name}}", "Uncaught runtime exception", e);
+ }
+ return true;
+ } catch (org.chromium.mojo.bindings.DeserializationException e) {
+ return false;
+ }
+ }
+ }
+
+ static class {{response_struct|name}}ProxyToResponder implements {{interface|name}}.{{method|interface_response_name}} {
+
+ private final org.chromium.mojo.system.Core mCore;
+ private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
+ private final long mRequestId;
+
+ {{response_struct|name}}ProxyToResponder(
+ org.chromium.mojo.system.Core core,
+ org.chromium.mojo.bindings.MessageReceiver messageReceiver,
+ long requestId) {
+ mCore = core;
+ mMessageReceiver = messageReceiver;
+ mRequestId = requestId;
+ }
+
+ @Override
+ public void call({{declare_params(method.response_parameters, true)}}) {
+ {{response_struct|name}} _response = new {{response_struct|name}}();
+{% for param in method.response_parameters %}
+ _response.{{param|name}} = {{param|name}};
+{% endfor %}
+ org.chromium.mojo.bindings.ServiceMessage _message =
+ _response.serializeWithHeader(
+ mCore,
+ new org.chromium.mojo.bindings.MessageHeader(
+ {{method|method_ordinal_name}},
+ {{flags_for_method(method, False)}},
+ mRequestId));
+ mMessageReceiver.accept(_message);
+ }
+ }
+{% endif %}
+{% endfor %}
+
+}
+{% endmacro %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl
new file mode 100644
index 0000000000..50c7a7bf94
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl
@@ -0,0 +1,4 @@
+{% from "interface_definition.tmpl" import interface_internal_def %}
+{% include "header.java.tmpl" %}
+
+{{ interface_internal_def(interface) }}
diff --git a/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl
new file mode 100644
index 0000000000..e28ba19c8b
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl
@@ -0,0 +1,4 @@
+{% from "data_types_definition.tmpl" import struct_def %}
+{% include "header.java.tmpl" %}
+
+{{ struct_def(struct) }}
diff --git a/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl
new file mode 100644
index 0000000000..b8cd4aa2e0
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl
@@ -0,0 +1,4 @@
+{% from "data_types_definition.tmpl" import union_def %}
+{% include "header.java.tmpl" %}
+
+{{ union_def(union) }}
diff --git a/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl
new file mode 100644
index 0000000000..019b1b6383
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl
@@ -0,0 +1,33 @@
+{%- macro enum_def(enum_name, enum) -%}
+ {{enum_name}} = {};
+{%- set prev_enum = 0 %}
+{%- for field in enum.fields %}
+{%- if field.value %}
+ {{enum_name}}.{{field.name}} = {{field.value|expression_to_text}};
+{%- elif loop.first %}
+ {{enum_name}}.{{field.name}} = 0;
+{%- else %}
+ {{enum_name}}.{{field.name}} = {{enum_name}}.{{enum.fields[loop.index0 - 1].name}} + 1;
+{%- endif %}
+{%- endfor %}
+
+ {{enum_name}}.isKnownEnumValue = function(value) {
+{%- if enum.fields %}
+ switch (value) {
+{%- for enum_field in enum.fields|groupby('numeric_value') %}
+ case {{enum_field[0]}}:
+{%- endfor %}
+ return true;
+ }
+{%- endif %}
+ return false;
+ };
+
+ {{enum_name}}.validate = function(enumValue) {
+ var isExtensible = {% if enum.extensible %}true{% else %}false{% endif %};
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
new file mode 100644
index 0000000000..11e319c1f7
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
@@ -0,0 +1,198 @@
+{%- for method in interface.methods %}
+ var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}};
+{%- endfor %}
+
+ function {{interface.name}}Ptr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController({{interface.name}},
+ handleOrPtrInfo);
+ }
+
+ function {{interface.name}}Proxy(receiver) {
+ this.receiver_ = receiver;
+ }
+
+{%- for method in interface.methods %}
+ {{interface.name}}Ptr.prototype.{{method.name|stylize_method}} = function() {
+ return {{interface.name}}Proxy.prototype.{{method.name|stylize_method}}
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function(
+{%- for parameter in method.parameters -%}
+{{parameter.name}}{% if not loop.last %}, {% endif %}
+{%- endfor -%}
+) {
+ var params = new {{interface.name}}_{{method.name}}_Params();
+{%- for parameter in method.parameters %}
+ params.{{parameter.name}} = {{parameter.name}};
+{%- endfor %}
+
+{%- if method.response_parameters == None %}
+ var builder = new codec.MessageBuilder(
+ k{{interface.name}}_{{method.name}}_Name,
+ codec.align({{interface.name}}_{{method.name}}_Params.encodedSize));
+ builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+ var message = builder.finish();
+ this.receiver_.accept(message);
+{%- else %}
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageWithRequestIDBuilder(
+ k{{interface.name}}_{{method.name}}_Name,
+ codec.align({{interface.name}}_{{method.name}}_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct({{interface.name}}_{{method.name}}_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+{%- endif %}
+ };
+{%- endfor %}
+
+ function {{interface.name}}Stub(delegate) {
+ this.delegate_ = delegate;
+ }
+
+{%- for method in interface.methods %}
+{%- set js_method_name = method.name|stylize_method %}
+ {{interface.name}}Stub.prototype.{{js_method_name}} = function({{method.parameters|map(attribute='name')|join(', ')}}) {
+ return this.delegate_ && this.delegate_.{{js_method_name}} && this.delegate_.{{js_method_name}}({{method.parameters|map(attribute='name')|join(', ')}});
+ }
+{%- endfor %}
+
+ {{interface.name}}Stub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+{%- for method in interface.methods %}
+{%- if method.response_parameters == None %}
+ case k{{interface.name}}_{{method.name}}_Name:
+ var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
+ this.{{method.name|stylize_method}}(
+{%- for parameter in method.parameters -%}
+ params.{{parameter.name}}{% if not loop.last %}, {% endif %}
+{%- endfor %});
+ return true;
+{%- endif %}
+{%- endfor %}
+ default:
+ return false;
+ }
+ };
+
+ {{interface.name}}Stub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+{%- for method in interface.methods %}
+{%- if method.response_parameters != None %}
+ case k{{interface.name}}_{{method.name}}_Name:
+ var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
+ this.{{method.name|stylize_method}}(
+{%- for parameter in method.parameters -%}
+params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
+{%- endfor %}).then(function(response) {
+ var responseParams =
+ new {{interface.name}}_{{method.name}}_ResponseParams();
+{%- for parameter in method.response_parameters %}
+ responseParams.{{parameter.name}} = response.{{parameter.name}};
+{%- endfor %}
+ var builder = new codec.MessageWithRequestIDBuilder(
+ k{{interface.name}}_{{method.name}}_Name,
+ codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+{%- endif %}
+{%- endfor %}
+ default:
+ return false;
+ }
+ };
+
+{#--- Validation #}
+
+ function validate{{interface.name}}Request(messageValidator) {
+{%- if not(interface.methods) %}
+ return validator.validationError.NONE;
+{%- else %}
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+{%- for method in interface.methods %}
+ case k{{interface.name}}_{{method.name}}_Name:
+{%- if method.response_parameters == None %}
+ if (!message.expectsResponse() && !message.isResponse())
+ paramsClass = {{interface.name}}_{{method.name}}_Params;
+{%- else %}
+ if (message.expectsResponse())
+ paramsClass = {{interface.name}}_{{method.name}}_Params;
+{%- endif %}
+ break;
+{%- endfor %}
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+{%- endif %}
+ }
+
+ function validate{{interface.name}}Response(messageValidator) {
+{%- if not(interface|has_callbacks) %}
+ return validator.validationError.NONE;
+{%- else %}
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+{%- for method in interface.methods %}
+{%- if method.response_parameters != None %}
+ case k{{interface.name}}_{{method.name}}_Name:
+ if (message.isResponse())
+ paramsClass = {{interface.name}}_{{method.name}}_ResponseParams;
+ break;
+{%- endif %}
+{%- endfor %}
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+{%- endif %}
+ }
+
+ var {{interface.name}} = {
+ name: '{{namespace|replace(".","::")}}::{{interface.name}}',
+ kVersion: {{interface.version}},
+ ptrClass: {{interface.name}}Ptr,
+ proxyClass: {{interface.name}}Proxy,
+ stubClass: {{interface.name}}Stub,
+ validateRequest: validate{{interface.name}}Request,
+{%- if interface|has_callbacks %}
+ validateResponse: validate{{interface.name}}Response,
+{%- else %}
+ validateResponse: null,
+{%- endif %}
+ };
+{#--- Interface Constants #}
+{%- for constant in interface.constants %}
+ {{interface.name}}.{{constant.name}} = {{constant.value|expression_to_text}},
+{%- endfor %}
+{#--- Interface Enums #}
+{%- from "enum_definition.tmpl" import enum_def -%}
+{%- for enum in interface.enums %}
+ {{ enum_def("%s.%s"|format(interface.name, enum.name), enum) }}
+{%- endfor %}
+ {{interface.name}}Stub.prototype.validator = validate{{interface.name}}Request;
+{%- if interface|has_callbacks %}
+ {{interface.name}}Proxy.prototype.validator = validate{{interface.name}}Response;
+{%- else %}
+ {{interface.name}}Proxy.prototype.validator = null;
+{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
new file mode 100644
index 0000000000..3637b196ac
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
@@ -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.
+
+{%- if use_new_js_bindings %}
+
+'use strict';
+
+(function() {
+ var mojomId = '{{module.path}}';
+ if (mojo.internal.isMojomLoaded(mojomId)) {
+ console.warn('The following mojom is loaded multiple times: ' + mojomId);
+ return;
+ }
+ mojo.internal.markMojomLoaded(mojomId);
+
+ // TODO(yzshen): Define these aliases to minimize the differences between the
+ // old/new modes. Remove them when the old mode goes away.
+ var bindings = mojo;
+ var codec = mojo.internal;
+ var validator = mojo.internal;
+
+{%- for import in imports %}
+ var {{import.unique_name}} =
+ mojo.internal.exposeNamespace('{{import.module.namespace}}');
+ if (mojo.config.autoLoadMojomDeps) {
+ mojo.internal.loadMojomIfNecessary(
+ '{{import.module.path}}',
+ new URL(
+ '{{import.module|get_relative_path(module)}}.js',
+ document.currentScript.src).href);
+ }
+{%- endfor %}
+
+{% include "module_definition.tmpl" %}
+})();
+
+{%- else %}
+
+define("{{module.path}}", [
+{%- if module.path !=
+ "mojo/public/interfaces/bindings/interface_control_messages.mojom" and
+ module.path !=
+ "mojo/public/interfaces/bindings/pipe_control_messages.mojom" %}
+ "mojo/public/js/bindings",
+{%- endif %}
+ "mojo/public/js/codec",
+ "mojo/public/js/core",
+ "mojo/public/js/validator",
+{%- for import in imports %}
+ "{{import.module.path}}",
+{%- endfor %}
+], function(
+{%- if module.path !=
+ "mojo/public/interfaces/bindings/interface_control_messages.mojom" and
+ module.path !=
+ "mojo/public/interfaces/bindings/pipe_control_messages.mojom" -%}
+bindings, {% endif -%}
+codec, core, validator
+{%- for import in imports -%}
+ , {{import.unique_name}}
+{%- endfor -%}
+) {
+
+{%- include "module_definition.tmpl" %}
+
+ return exports;
+});
+
+{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
new file mode 100644
index 0000000000..a119ee9480
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
@@ -0,0 +1,49 @@
+{#--- Constants #}
+{%- for constant in module.constants %}
+ var {{constant.name}} = {{constant.value|expression_to_text}};
+{%- endfor %}
+
+{#--- Enums #}
+{%- from "enum_definition.tmpl" import enum_def %}
+{%- for enum in enums %}
+ var {{ enum_def(enum.name, enum) }}
+{%- endfor %}
+
+{#--- Struct definitions #}
+{% for struct in structs %}
+{%- include "struct_definition.tmpl" %}
+{%- endfor -%}
+
+{#--- Union definitions #}
+{%- from "union_definition.tmpl" import union_def %}
+{%- for union in unions %}
+{{union_def(union)|indent(2)}}
+{%- endfor %}
+
+{#--- Interface definitions #}
+{%- for interface in interfaces -%}
+{%- include "interface_definition.tmpl" %}
+{%- endfor %}
+
+{%- if use_new_js_bindings %}
+ var exports = mojo.internal.exposeNamespace("{{module.namespace}}");
+{%- else %}
+ var exports = {};
+{%- endif %}
+
+{%- for constant in module.constants %}
+ exports.{{constant.name}} = {{constant.name}};
+{%- endfor %}
+{%- for enum in enums %}
+ exports.{{enum.name}} = {{enum.name}};
+{%- endfor %}
+{%- for struct in structs if struct.exported %}
+ exports.{{struct.name}} = {{struct.name}};
+{%- endfor %}
+{%- for union in unions %}
+ exports.{{union.name}} = {{union.name}};
+{%- endfor %}
+{%- for interface in interfaces %}
+ exports.{{interface.name}} = {{interface.name}};
+ exports.{{interface.name}}Ptr = {{interface.name}}Ptr;
+{%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
new file mode 100644
index 0000000000..e823e46155
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
@@ -0,0 +1,126 @@
+{#--- Begin #}
+ function {{struct.name}}(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+{#--- Enums #}
+{%- from "enum_definition.tmpl" import enum_def %}
+{% for enum in struct.enums %}
+ {{enum_def("%s.%s"|format(struct.name, enum.name), enum)}}
+{%- endfor %}
+
+{#--- Constants #}
+{% for constant in struct.constants %}
+ {{struct.name}}.{{constant.name}} = {{constant.value|expression_to_text}};
+{%- endfor %}
+
+{#--- initDefaults() #}
+ {{struct.name}}.prototype.initDefaults_ = function() {
+{%- for packed_field in struct.packed.packed_fields %}
+ this.{{packed_field.field.name}} = {{packed_field.field|default_value}};
+{%- endfor %}
+ };
+
+{#--- initFields() #}
+ {{struct.name}}.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+{#--- Validation #}
+
+ {{struct.name}}.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+{%- for version in struct.versions %}
+ {version: {{version.version}}, numBytes: {{version.num_bytes}}}{% if not loop.last %},
+ {%- endif -%}
+{% endfor %}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+{#- Before validating fields introduced at a certain version, we need to add
+ a version check, which makes sure we skip further validation if |object|
+ is from an earlier version. |last_checked_version| records the last
+ version that we have added such version check. #}
+{%- from "validation_macros.tmpl" import validate_struct_field %}
+{%- set last_checked_version = 0 %}
+{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %}
+{%- set offset = packed_field|field_offset %}
+{%- set field = packed_field.field %}
+{%- set name = struct.name ~ '.' ~ field.name %}
+{% if field|is_object_field or field|is_any_handle_or_interface_field or
+ field|is_enum_field %}
+{% if packed_field.min_version > last_checked_version %}
+{% set last_checked_version = packed_field.min_version %}
+ // version check {{name}}
+ if (!messageValidator.isFieldInStructVersion(offset, {{packed_field.min_version}}))
+ return validator.validationError.NONE;
+{%- endif -%}
+{{validate_struct_field(field, offset, name)|indent(4)}}
+{%- endif %}
+{%- endfor %}
+
+ return validator.validationError.NONE;
+ };
+
+{#--- Encoding and decoding #}
+
+ {{struct.name}}.encodedSize = codec.kStructHeaderSize + {{struct.packed|payload_size}};
+
+ {{struct.name}}.decode = function(decoder) {
+ var packed;
+ var val = new {{struct.name}}();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+{%- for byte in struct.bytes %}
+{%- if byte.packed_fields|length >= 1 and
+ byte.packed_fields[0].field|is_bool_field %}
+ packed = decoder.readUint8();
+{%- for packed_field in byte.packed_fields %}
+ val.{{packed_field.field.name}} = (packed >> {{packed_field.bit}}) & 1 ? true : false;
+{%- endfor %}
+{%- else %}
+{%- for packed_field in byte.packed_fields %}
+ val.{{packed_field.field.name}} = decoder.{{packed_field.field.kind|decode_snippet}};
+{%- endfor %}
+{%- endif %}
+{%- if byte.is_padding %}
+ decoder.skip(1);
+{%- endif %}
+{%- endfor %}
+ return val;
+ };
+
+ {{struct.name}}.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32({{struct.name}}.encodedSize);
+ encoder.writeUint32({{struct.versions[-1].version}});
+
+{%- for byte in struct.bytes %}
+{%- if byte.packed_fields|length >= 1 and
+ byte.packed_fields[0].field|is_bool_field %}
+ packed = 0;
+{%- for packed_field in byte.packed_fields %}
+ packed |= (val.{{packed_field.field.name}} & 1) << {{packed_field.bit}}
+{%- endfor %}
+ encoder.writeUint8(packed);
+{%- else %}
+{%- for packed_field in byte.packed_fields %}
+ encoder.{{packed_field.field.kind|encode_snippet}}val.{{packed_field.field.name}});
+{%- endfor %}
+{%- endif %}
+{%- if byte.is_padding %}
+ encoder.skip(1);
+{%- endif %}
+{%- endfor %}
+ };
diff --git a/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl
new file mode 100644
index 0000000000..4823febeca
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl
@@ -0,0 +1,154 @@
+{%- macro union_def(union) %}
+function {{union.name}}(value) {
+ this.initDefault_();
+ this.initValue_(value);
+}
+
+{{tags(union)}}
+
+{{union.name}}.prototype.initDefault_ = function() {
+ this.$data = null;
+ this.$tag = undefined;
+}
+
+{{union.name}}.prototype.initValue_ = function(value) {
+ if (value == undefined) {
+ return;
+ }
+
+ var keys = Object.keys(value);
+ if (keys.length == 0) {
+ return;
+ }
+
+ if (keys.length > 1) {
+ throw new TypeError("You may set only one member on a union.");
+ }
+
+ var fields = [
+{%- for field in union.fields %}
+ "{{field.name}}",
+{%- endfor %}
+ ];
+
+ if (fields.indexOf(keys[0]) < 0) {
+ throw new ReferenceError(keys[0] + " is not a {{union.name}} member.");
+
+ }
+
+ this[keys[0]] = value[keys[0]];
+}
+
+{%- for field in union.fields %}
+Object.defineProperty({{union.name}}.prototype, "{{field.name}}", {
+ get: function() {
+ if (this.$tag != {{union.name}}.Tags.{{field.name}}) {
+ throw new ReferenceError(
+ "{{union.name}}.{{field.name}} is not currently set.");
+ }
+ return this.$data;
+ },
+
+ set: function(value) {
+ this.$tag = {{union.name}}.Tags.{{field.name}};
+ this.$data = value;
+ }
+});
+{%- endfor %}
+
+{{encode(union)|indent(2)}}
+
+{{decode(union)|indent(2)}}
+
+{{validate(union)|indent(2)}}
+
+{{union.name}}.encodedSize = 16;
+{%- endmacro %}
+
+{%- macro tags(union) %}
+{{union.name}}.Tags = {
+{%- for field in union.fields %}
+ {{field.name}}: {{field.ordinal}},
+{%- endfor %}
+};
+{%- endmacro %}
+
+{%- macro encode(union) %}
+{{union.name}}.encode = function(encoder, val) {
+ if (val == null) {
+ encoder.writeUint64(0);
+ encoder.writeUint64(0);
+ return;
+ }
+ if (val.$tag == undefined) {
+ throw new TypeError("Cannot encode unions with an unknown member set.");
+ }
+
+ encoder.writeUint32(16);
+ encoder.writeUint32(val.$tag);
+ switch (val.$tag) {
+{%- for field in union.fields %}
+ case {{union.name}}.Tags.{{field.name}}:
+{%- if field|is_bool_field %}
+ encoder.writeUint8(val.{{field.name}} ? 1 : 0);
+{%- else %}
+ encoder.{{field.kind|union_encode_snippet}}val.{{field.name}});
+{%- endif %}
+ break;
+{%- endfor %}
+ }
+ encoder.align();
+};
+{%- endmacro %}
+
+{%- macro decode(union) %}
+{{union.name}}.decode = function(decoder) {
+ var size = decoder.readUint32();
+ if (size == 0) {
+ decoder.readUint32();
+ decoder.readUint64();
+ return null;
+ }
+
+ var result = new {{union.name}}();
+ var tag = decoder.readUint32();
+ switch (tag) {
+{%- for field in union.fields %}
+ case {{union.name}}.Tags.{{field.name}}:
+{%- if field|is_bool_field %}
+ result.{{field.name}} = decoder.readUint8() ? true : false;
+{%- else %}
+ result.{{field.name}} = decoder.{{field.kind|union_decode_snippet}};
+{%- endif %}
+ break;
+{%- endfor %}
+ }
+ decoder.align();
+
+ return result;
+};
+{%- endmacro %}
+
+{%- from "validation_macros.tmpl" import validate_union_field %}
+{%- macro validate(union) %}
+{{union.name}}.validate = function(messageValidator, offset) {
+ var size = messageValidator.decodeUnionSize(offset);
+ if (size != 16) {
+ return validator.validationError.INVALID_UNION_SIZE;
+ }
+
+ var tag = messageValidator.decodeUnionTag(offset);
+ var data_offset = offset + 8;
+ var err;
+ switch (tag) {
+{%- for field in union.fields %}
+{%- set name = union.name ~ '.' ~ field.name %}
+ case {{union.name}}.Tags.{{field.name}}:
+ {{validate_union_field(field, "data_offset", name)}}
+ break;
+{%- endfor %}
+ }
+
+ return validator.validationError.NONE;
+};
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
new file mode 100644
index 0000000000..d4e15a7859
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
@@ -0,0 +1,60 @@
+{% macro _check_err() -%}
+if (err !== validator.validationError.NONE)
+ return err;
+{%- endmacro %}
+
+{%- macro _validate_field(field, offset, name) %}
+{%- if field|is_string_pointer_field %}
+// validate {{name}}
+err = messageValidator.validateStringPointer({{offset}}, {{field|validate_nullable_params}})
+{{_check_err()}}
+{%- elif field|is_array_pointer_field %}
+// validate {{name}}
+err = messageValidator.validateArrayPointer({{offset}}, {{field|validate_array_params}});
+{{_check_err()}}
+{%- elif field|is_struct_pointer_field %}
+// validate {{name}}
+err = messageValidator.validateStructPointer({{offset}}, {{field|validate_struct_params}});
+{{_check_err()}}
+{%- elif field|is_map_pointer_field %}
+// validate {{name}}
+err = messageValidator.validateMapPointer({{offset}}, {{field|validate_map_params}});
+{{_check_err()}}
+{%- elif field|is_interface_field %}
+// validate {{name}}
+err = messageValidator.validateInterface({{offset}}, {{field|validate_nullable_params}});
+{{_check_err()}}
+{%- elif field|is_interface_request_field %}
+// validate {{name}}
+err = messageValidator.validateInterfaceRequest({{offset}}, {{field|validate_nullable_params}})
+{{_check_err()}}
+{%- elif field|is_handle_field %}
+// validate {{name}}
+err = messageValidator.validateHandle({{offset}}, {{field|validate_nullable_params}})
+{{_check_err()}}
+{%- elif field|is_enum_field %}
+// validate {{name}}
+err = messageValidator.validateEnum({{offset}}, {{field|validate_enum_params}});
+{{_check_err()}}
+{%- endif %}
+{%- endmacro %}
+
+{%- macro validate_struct_field(field, offset, name) %}
+{%- if field|is_union_field %}
+// validate {{name}}
+err = messageValidator.validateUnion({{offset}}, {{field|validate_union_params}});
+{{_check_err()}}
+{%- else %}
+{{_validate_field(field, offset, name)}}
+{%- endif %}
+{%- endmacro %}
+
+{%- macro validate_union_field(field, offset, name) %}
+{%- if field|is_union_field %}
+// validate {{name}}
+err = messageValidator.validateNestedUnion({{offset}}, {{field|validate_union_params}});
+{{_check_err()}}
+{%- else %}
+{{_validate_field(field, offset, name)}}
+{%- endif %}
+{%- endmacro %}
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
new file mode 100644
index 0000000000..38d222b136
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -0,0 +1,818 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates C++ source files from a mojom.Module."""
+
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+import mojom.generate.pack as pack
+from mojom.generate.template_expander import UseJinja
+
+
+_kind_to_cpp_type = {
+ mojom.BOOL: "bool",
+ mojom.INT8: "int8_t",
+ mojom.UINT8: "uint8_t",
+ mojom.INT16: "int16_t",
+ mojom.UINT16: "uint16_t",
+ mojom.INT32: "int32_t",
+ mojom.UINT32: "uint32_t",
+ mojom.FLOAT: "float",
+ mojom.INT64: "int64_t",
+ mojom.UINT64: "uint64_t",
+ mojom.DOUBLE: "double",
+}
+
+_kind_to_cpp_literal_suffix = {
+ mojom.UINT8: "U",
+ mojom.UINT16: "U",
+ mojom.UINT32: "U",
+ mojom.FLOAT: "f",
+ mojom.UINT64: "ULL",
+}
+
+# TODO(rockot): Get rid of these globals. This requires some refactoring of the
+# generator library code so that filters can use the generator as context.
+_current_typemap = {}
+_for_blink = False
+# TODO(rockot, yzshen): The variant handling is kind of a hack currently. Make
+# it right.
+_variant = None
+_export_attribute = None
+
+
+class _NameFormatter(object):
+ """A formatter for the names of kinds or values."""
+
+ def __init__(self, token, variant):
+ self._token = token
+ self._variant = variant
+
+ def Format(self, separator, prefixed=False, internal=False,
+ include_variant=False, add_same_module_namespaces=False,
+ flatten_nested_kind=False):
+ """Formats the name according to the given configuration.
+
+ Args:
+ separator: Separator between different parts of the name.
+ prefixed: Whether a leading separator should be added.
+ internal: Returns the name in the "internal" namespace.
+ include_variant: Whether to include variant as namespace. If |internal| is
+ True, then this flag is ignored and variant is not included.
+ add_same_module_namespaces: Includes all namespaces even if the token is
+ from the same module as the current mojom file.
+ flatten_nested_kind: It is allowed to define enums inside structs and
+ interfaces. If this flag is set to True, this method concatenates the
+ parent kind and the nested kind with '_', instead of treating the
+ parent kind as a scope."""
+
+ parts = []
+ if self._ShouldIncludeNamespace(add_same_module_namespaces):
+ if prefixed:
+ parts.append("")
+ parts.extend(self._GetNamespace())
+ if include_variant and self._variant and not internal:
+ parts.append(self._variant)
+ parts.extend(self._GetName(internal, flatten_nested_kind))
+ return separator.join(parts)
+
+ def FormatForCpp(self, add_same_module_namespaces=False, internal=False,
+ flatten_nested_kind=False):
+ return self.Format(
+ "::", prefixed=True,
+ add_same_module_namespaces=add_same_module_namespaces,
+ internal=internal, include_variant=True,
+ flatten_nested_kind=flatten_nested_kind)
+
+ def FormatForMojom(self):
+ return self.Format(".", add_same_module_namespaces=True)
+
+ def _MapKindName(self, token, internal):
+ if not internal:
+ return token.name
+ if (mojom.IsStructKind(token) or mojom.IsUnionKind(token) or
+ mojom.IsEnumKind(token)):
+ return token.name + "_Data"
+ return token.name
+
+ def _GetName(self, internal, flatten_nested_kind):
+ if isinstance(self._token, mojom.EnumValue):
+ name_parts = _NameFormatter(self._token.enum, self._variant)._GetName(
+ internal, flatten_nested_kind)
+ name_parts.append(self._token.name)
+ return name_parts
+
+ name_parts = []
+ if internal:
+ name_parts.append("internal")
+
+ if (flatten_nested_kind and mojom.IsEnumKind(self._token) and
+ self._token.parent_kind):
+ name = "%s_%s" % (self._token.parent_kind.name,
+ self._MapKindName(self._token, internal))
+ name_parts.append(name)
+ return name_parts
+
+ if self._token.parent_kind:
+ name_parts.append(self._MapKindName(self._token.parent_kind, internal))
+ name_parts.append(self._MapKindName(self._token, internal))
+ return name_parts
+
+ def _ShouldIncludeNamespace(self, add_same_module_namespaces):
+ return add_same_module_namespaces or self._token.imported_from
+
+ def _GetNamespace(self):
+ if self._token.imported_from:
+ return NamespaceToArray(self._token.imported_from["namespace"])
+ elif hasattr(self._token, "module"):
+ return NamespaceToArray(self._token.module.namespace)
+ return []
+
+
+def ConstantValue(constant):
+ return ExpressionToText(constant.value, kind=constant.kind)
+
+# TODO(yzshen): Revisit the default value feature. It was designed prior to
+# custom type mapping.
+def DefaultValue(field):
+ if field.default:
+ if mojom.IsStructKind(field.kind):
+ assert field.default == "default"
+ if not IsTypemappedKind(field.kind):
+ return "%s::New()" % GetNameForKind(field.kind)
+ return ExpressionToText(field.default, kind=field.kind)
+ return ""
+
+def NamespaceToArray(namespace):
+ return namespace.split(".") if namespace else []
+
+def GetNameForKind(kind, internal=False, flatten_nested_kind=False,
+ add_same_module_namespaces=False):
+ return _NameFormatter(kind, _variant).FormatForCpp(
+ internal=internal, flatten_nested_kind=flatten_nested_kind,
+ add_same_module_namespaces=add_same_module_namespaces)
+
+def GetQualifiedNameForKind(kind, internal=False, flatten_nested_kind=False,
+ include_variant=True):
+ return _NameFormatter(
+ kind, _variant if include_variant else None).FormatForCpp(
+ internal=internal, add_same_module_namespaces=True,
+ flatten_nested_kind=flatten_nested_kind)
+
+
+def GetWtfHashFnNameForEnum(enum):
+ return _NameFormatter(
+ enum, None).Format("_", internal=True, add_same_module_namespaces=True,
+ flatten_nested_kind=True) + "HashFn"
+
+
+def GetFullMojomNameForKind(kind):
+ return _NameFormatter(kind, _variant).FormatForMojom()
+
+def IsTypemappedKind(kind):
+ return hasattr(kind, "name") and \
+ GetFullMojomNameForKind(kind) in _current_typemap
+
+def IsNativeOnlyKind(kind):
+ return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \
+ kind.native_only
+
+
+def IsHashableKind(kind):
+ """Check if the kind can be hashed.
+
+ Args:
+ kind: {Kind} The kind to check.
+
+ Returns:
+ {bool} True if a value of this kind can be hashed.
+ """
+ checked = set()
+ def Check(kind):
+ if kind.spec in checked:
+ return True
+ checked.add(kind.spec)
+ if mojom.IsNullableKind(kind):
+ return False
+ elif mojom.IsStructKind(kind):
+ if (IsTypemappedKind(kind) and
+ not _current_typemap[GetFullMojomNameForKind(kind)]["hashable"]):
+ return False
+ return all(Check(field.kind) for field in kind.fields)
+ elif mojom.IsEnumKind(kind):
+ return not IsTypemappedKind(kind) or _current_typemap[
+ GetFullMojomNameForKind(kind)]["hashable"]
+ elif mojom.IsUnionKind(kind):
+ return all(Check(field.kind) for field in kind.fields)
+ elif mojom.IsAnyHandleKind(kind):
+ return False
+ elif mojom.IsAnyInterfaceKind(kind):
+ return False
+ # TODO(tibell): Arrays and maps could be made hashable. We just don't have a
+ # use case yet.
+ elif mojom.IsArrayKind(kind):
+ return False
+ elif mojom.IsMapKind(kind):
+ return False
+ else:
+ return True
+ return Check(kind)
+
+
+def AllEnumValues(enum):
+ """Return all enum values associated with an enum.
+
+ Args:
+ enum: {mojom.Enum} The enum type.
+
+ Returns:
+ {Set[int]} The values.
+ """
+ return set(field.numeric_value for field in enum.fields)
+
+
+def GetNativeTypeName(typemapped_kind):
+ return _current_typemap[GetFullMojomNameForKind(typemapped_kind)]["typename"]
+
+def GetCppPodType(kind):
+ return _kind_to_cpp_type[kind]
+
+def FormatConstantDeclaration(constant, nested=False):
+ if mojom.IsStringKind(constant.kind):
+ if nested:
+ return "const char %s[]" % constant.name
+ return "%sextern const char %s[]" % \
+ ((_export_attribute + " ") if _export_attribute else "", constant.name)
+ return "constexpr %s %s = %s" % (GetCppPodType(constant.kind), constant.name,
+ ConstantValue(constant))
+
+def GetCppWrapperType(kind, add_same_module_namespaces=False):
+ def _AddOptional(type_name):
+ pattern = "WTF::Optional<%s>" if _for_blink else "base::Optional<%s>"
+ return pattern % type_name
+
+ if IsTypemappedKind(kind):
+ type_name = GetNativeTypeName(kind)
+ if (mojom.IsNullableKind(kind) and
+ not _current_typemap[GetFullMojomNameForKind(kind)][
+ "nullable_is_same_type"]):
+ type_name = _AddOptional(type_name)
+ return type_name
+ if mojom.IsEnumKind(kind):
+ return GetNameForKind(
+ kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return "%sPtr" % GetNameForKind(
+ kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsArrayKind(kind):
+ pattern = "WTF::Vector<%s>" if _for_blink else "std::vector<%s>"
+ if mojom.IsNullableKind(kind):
+ pattern = _AddOptional(pattern)
+ return pattern % GetCppWrapperType(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsMapKind(kind):
+ pattern = ("WTF::HashMap<%s, %s>" if _for_blink else
+ "std::unordered_map<%s, %s>")
+ if mojom.IsNullableKind(kind):
+ pattern = _AddOptional(pattern)
+ return pattern % (
+ GetCppWrapperType(
+ kind.key_kind,
+ add_same_module_namespaces=add_same_module_namespaces),
+ GetCppWrapperType(
+ kind.value_kind,
+ add_same_module_namespaces=add_same_module_namespaces))
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtr" % GetNameForKind(
+ kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsInterfaceRequestKind(kind):
+ return "%sRequest" % GetNameForKind(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "%sAssociatedPtrInfo" % GetNameForKind(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "%sAssociatedRequest" % GetNameForKind(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsStringKind(kind):
+ if _for_blink:
+ return "WTF::String"
+ type_name = "std::string"
+ return _AddOptional(type_name) if mojom.IsNullableKind(kind) else type_name
+ if mojom.IsGenericHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ if not kind in _kind_to_cpp_type:
+ raise Exception("Unrecognized kind %s" % kind.spec)
+ return _kind_to_cpp_type[kind]
+
+def IsMoveOnlyKind(kind):
+ if IsTypemappedKind(kind):
+ if mojom.IsEnumKind(kind):
+ return False
+ return _current_typemap[GetFullMojomNameForKind(kind)]["move_only"]
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return True
+ if mojom.IsArrayKind(kind):
+ return IsMoveOnlyKind(kind.kind)
+ if mojom.IsMapKind(kind):
+ return IsMoveOnlyKind(kind.value_kind)
+ if mojom.IsAnyHandleOrInterfaceKind(kind):
+ return True
+ return False
+
+def IsCopyablePassByValue(kind):
+ if not IsTypemappedKind(kind):
+ return False
+ return _current_typemap[GetFullMojomNameForKind(kind)][
+ "copyable_pass_by_value"]
+
+def ShouldPassParamByValue(kind):
+ return ((not mojom.IsReferenceKind(kind)) or IsMoveOnlyKind(kind) or
+ IsCopyablePassByValue(kind))
+
+def GetCppWrapperParamType(kind):
+ cpp_wrapper_type = GetCppWrapperType(kind)
+ return (cpp_wrapper_type if ShouldPassParamByValue(kind)
+ else "const %s&" % cpp_wrapper_type)
+
+def GetCppFieldType(kind):
+ if mojom.IsStructKind(kind):
+ return ("mojo::internal::Pointer<%s>" %
+ GetNameForKind(kind, internal=True))
+ if mojom.IsUnionKind(kind):
+ return "%s" % GetNameForKind(kind, internal=True)
+ if mojom.IsArrayKind(kind):
+ return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" %
+ GetCppFieldType(kind.kind))
+ if mojom.IsMapKind(kind):
+ return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" %
+ (GetCppFieldType(kind.key_kind), GetCppFieldType(kind.value_kind)))
+ if mojom.IsInterfaceKind(kind):
+ return "mojo::internal::Interface_Data"
+ if mojom.IsInterfaceRequestKind(kind):
+ return "mojo::internal::Handle_Data"
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "mojo::internal::AssociatedInterface_Data"
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "mojo::internal::AssociatedEndpointHandle_Data"
+ if mojom.IsEnumKind(kind):
+ return "int32_t"
+ if mojom.IsStringKind(kind):
+ return "mojo::internal::Pointer<mojo::internal::String_Data>"
+ if mojom.IsAnyHandleKind(kind):
+ return "mojo::internal::Handle_Data"
+ return _kind_to_cpp_type[kind]
+
+def GetCppUnionFieldType(kind):
+ if mojom.IsUnionKind(kind):
+ return ("mojo::internal::Pointer<%s>" % GetNameForKind(kind, internal=True))
+ return GetCppFieldType(kind)
+
+def GetUnionGetterReturnType(kind):
+ if mojom.IsReferenceKind(kind):
+ return "%s&" % GetCppWrapperType(kind)
+ return GetCppWrapperType(kind)
+
+def GetUnionTraitGetterReturnType(kind):
+ """Get field type used in UnionTraits template specialization.
+
+ The type may be qualified as UnionTraits specializations live outside the
+ namespace where e.g. structs are defined.
+
+ Args:
+ kind: {Kind} The type of the field.
+
+ Returns:
+ {str} The C++ type to use for the field.
+ """
+ if mojom.IsReferenceKind(kind):
+ return "%s&" % GetCppWrapperType(kind, add_same_module_namespaces=True)
+ return GetCppWrapperType(kind, add_same_module_namespaces=True)
+
+def GetCppDataViewType(kind, qualified=False):
+ def _GetName(input_kind):
+ return _NameFormatter(input_kind, None).FormatForCpp(
+ add_same_module_namespaces=qualified, flatten_nested_kind=True)
+
+ if mojom.IsEnumKind(kind):
+ return _GetName(kind)
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return "%sDataView" % _GetName(kind)
+ if mojom.IsArrayKind(kind):
+ return "mojo::ArrayDataView<%s>" % GetCppDataViewType(kind.kind, qualified)
+ if mojom.IsMapKind(kind):
+ return ("mojo::MapDataView<%s, %s>" % (
+ GetCppDataViewType(kind.key_kind, qualified),
+ GetCppDataViewType(kind.value_kind, qualified)))
+ if mojom.IsStringKind(kind):
+ return "mojo::StringDataView"
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtrDataView" % _GetName(kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ return "%sRequestDataView" % _GetName(kind.kind)
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind)
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "%sAssociatedRequestDataView" % _GetName(kind.kind)
+ if mojom.IsGenericHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ return _kind_to_cpp_type[kind]
+
+def GetUnmappedTypeForSerializer(kind):
+ return GetCppDataViewType(kind, qualified=True)
+
+def TranslateConstants(token, kind):
+ if isinstance(token, mojom.NamedValue):
+ return GetNameForKind(token, flatten_nested_kind=True)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == "double.INFINITY":
+ return "std::numeric_limits<double>::infinity()"
+ if token.value == "float.INFINITY":
+ return "std::numeric_limits<float>::infinity()"
+ if token.value == "double.NEGATIVE_INFINITY":
+ return "-std::numeric_limits<double>::infinity()"
+ if token.value == "float.NEGATIVE_INFINITY":
+ return "-std::numeric_limits<float>::infinity()"
+ if token.value == "double.NAN":
+ return "std::numeric_limits<double>::quiet_NaN()"
+ if token.value == "float.NAN":
+ return "std::numeric_limits<float>::quiet_NaN()"
+
+ if (kind is not None and mojom.IsFloatKind(kind)):
+ return token if token.isdigit() else token + "f";
+
+ # Per C++11, 2.14.2, the type of an integer literal is the first of the
+ # corresponding list in Table 6 in which its value can be represented. In this
+ # case, the list for decimal constants with no suffix is:
+ # int, long int, long long int
+ # The standard considers a program ill-formed if it contains an integer
+ # literal that cannot be represented by any of the allowed types.
+ #
+ # As it turns out, MSVC doesn't bother trying to fall back to long long int,
+ # so the integral constant -2147483648 causes it grief: it decides to
+ # represent 2147483648 as an unsigned integer, and then warns that the unary
+ # minus operator doesn't make sense on unsigned types. Doh!
+ if kind == mojom.INT32 and token == "-2147483648":
+ return "(-%d - 1) /* %s */" % (
+ 2**31 - 1, "Workaround for MSVC bug; see https://crbug.com/445618")
+
+ return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, ""))
+
+def ExpressionToText(value, kind=None):
+ return TranslateConstants(value, kind)
+
+def RequiresContextForDataView(kind):
+ for field in kind.fields:
+ if mojom.IsReferenceKind(field.kind):
+ return True
+ return False
+
+def ShouldInlineStruct(struct):
+ # TODO(darin): Base this on the size of the wrapper class.
+ if len(struct.fields) > 4:
+ return False
+ for field in struct.fields:
+ if mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind):
+ return False
+ return True
+
+def ContainsMoveOnlyMembers(struct):
+ for field in struct.fields:
+ if IsMoveOnlyKind(field.kind):
+ return True
+ return False
+
+def ShouldInlineUnion(union):
+ return not any(
+ mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind)
+ for field in union.fields)
+
+
+class StructConstructor(object):
+ """Represents a constructor for a generated struct.
+
+ Fields:
+ fields: {[Field]} All struct fields in order.
+ params: {[Field]} The fields that are passed as params.
+ """
+
+ def __init__(self, fields, params):
+ self._fields = fields
+ self._params = set(params)
+
+ @property
+ def params(self):
+ return [field for field in self._fields if field in self._params]
+
+ @property
+ def fields(self):
+ for field in self._fields:
+ yield (field, field in self._params)
+
+
+def GetStructConstructors(struct):
+ """Returns a list of constructors for a struct.
+
+ Params:
+ struct: {Struct} The struct to return constructors for.
+
+ Returns:
+ {[StructConstructor]} A list of StructConstructors that should be generated
+ for |struct|.
+ """
+ if not mojom.IsStructKind(struct):
+ raise TypeError
+ # Types that are neither copyable nor movable can't be passed to a struct
+ # constructor so only generate a default constructor.
+ if any(IsTypemappedKind(field.kind) and _current_typemap[
+ GetFullMojomNameForKind(field.kind)]["non_copyable_non_movable"]
+ for field in struct.fields):
+ return [StructConstructor(struct.fields, [])]
+
+ param_counts = [0]
+ for version in struct.versions:
+ if param_counts[-1] != version.num_fields:
+ param_counts.append(version.num_fields)
+
+ ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal)
+ return (StructConstructor(struct.fields, ordinal_fields[:param_count])
+ for param_count in param_counts)
+
+
+def GetContainerValidateParamsCtorArgs(kind):
+ if mojom.IsStringKind(kind):
+ expected_num_elements = 0
+ element_is_nullable = False
+ key_validate_params = "nullptr"
+ element_validate_params = "nullptr"
+ enum_validate_func = "nullptr"
+ elif mojom.IsMapKind(kind):
+ expected_num_elements = 0
+ element_is_nullable = False
+ key_validate_params = GetNewContainerValidateParams(mojom.Array(
+ kind=kind.key_kind))
+ element_validate_params = GetNewContainerValidateParams(mojom.Array(
+ kind=kind.value_kind))
+ enum_validate_func = "nullptr"
+ else: # mojom.IsArrayKind(kind)
+ expected_num_elements = generator.ExpectedArraySize(kind) or 0
+ element_is_nullable = mojom.IsNullableKind(kind.kind)
+ key_validate_params = "nullptr"
+ element_validate_params = GetNewContainerValidateParams(kind.kind)
+ if mojom.IsEnumKind(kind.kind):
+ enum_validate_func = ("%s::Validate" %
+ GetQualifiedNameForKind(kind.kind, internal=True,
+ flatten_nested_kind=True))
+ else:
+ enum_validate_func = "nullptr"
+
+ if enum_validate_func == "nullptr":
+ if key_validate_params == "nullptr":
+ return "%d, %s, %s" % (expected_num_elements,
+ "true" if element_is_nullable else "false",
+ element_validate_params)
+ else:
+ return "%s, %s" % (key_validate_params, element_validate_params)
+ else:
+ return "%d, %s" % (expected_num_elements, enum_validate_func)
+
+def GetNewContainerValidateParams(kind):
+ if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) and
+ not mojom.IsStringKind(kind)):
+ return "nullptr"
+
+ return "new mojo::internal::ContainerValidateParams(%s)" % (
+ GetContainerValidateParamsCtorArgs(kind))
+
+class Generator(generator.Generator):
+
+ cpp_filters = {
+ "all_enum_values": AllEnumValues,
+ "constant_value": ConstantValue,
+ "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces,
+ "contains_move_only_members": ContainsMoveOnlyMembers,
+ "cpp_wrapper_param_type": GetCppWrapperParamType,
+ "cpp_data_view_type": GetCppDataViewType,
+ "cpp_field_type": GetCppFieldType,
+ "cpp_union_field_type": GetCppUnionFieldType,
+ "cpp_pod_type": GetCppPodType,
+ "cpp_union_getter_return_type": GetUnionGetterReturnType,
+ "cpp_union_trait_getter_return_type": GetUnionTraitGetterReturnType,
+ "cpp_wrapper_type": GetCppWrapperType,
+ "default_value": DefaultValue,
+ "expression_to_text": ExpressionToText,
+ "format_constant_declaration": FormatConstantDeclaration,
+ "get_container_validate_params_ctor_args":
+ GetContainerValidateParamsCtorArgs,
+ "get_name_for_kind": GetNameForKind,
+ "get_pad": pack.GetPad,
+ "get_qualified_name_for_kind": GetQualifiedNameForKind,
+ "has_callbacks": mojom.HasCallbacks,
+ "has_sync_methods": mojom.HasSyncMethods,
+ "requires_context_for_data_view": RequiresContextForDataView,
+ "should_inline": ShouldInlineStruct,
+ "should_inline_union": ShouldInlineUnion,
+ "is_array_kind": mojom.IsArrayKind,
+ "is_enum_kind": mojom.IsEnumKind,
+ "is_integral_kind": mojom.IsIntegralKind,
+ "is_native_only_kind": IsNativeOnlyKind,
+ "is_any_handle_kind": mojom.IsAnyHandleKind,
+ "is_any_interface_kind": mojom.IsAnyInterfaceKind,
+ "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind,
+ "is_associated_kind": mojom.IsAssociatedKind,
+ "is_hashable": IsHashableKind,
+ "is_map_kind": mojom.IsMapKind,
+ "is_nullable_kind": mojom.IsNullableKind,
+ "is_object_kind": mojom.IsObjectKind,
+ "is_reference_kind": mojom.IsReferenceKind,
+ "is_string_kind": mojom.IsStringKind,
+ "is_struct_kind": mojom.IsStructKind,
+ "is_typemapped_kind": IsTypemappedKind,
+ "is_union_kind": mojom.IsUnionKind,
+ "passes_associated_kinds": mojom.PassesAssociatedKinds,
+ "struct_constructors": GetStructConstructors,
+ "stylize_method": generator.StudlyCapsToCamel,
+ "under_to_camel": generator.UnderToCamel,
+ "unmapped_type_for_serializer": GetUnmappedTypeForSerializer,
+ "wtf_hash_fn_name_for_enum": GetWtfHashFnNameForEnum,
+ }
+
+ def GetExtraTraitsHeaders(self):
+ extra_headers = set()
+ for typemap in self._GetAllUsedTypemaps():
+ extra_headers.update(typemap.get("traits_headers", []))
+ return sorted(extra_headers)
+
+ def _GetAllUsedTypemaps(self):
+ """Returns the typemaps for types needed for serialization in this module.
+
+ A type is needed for serialization if it is contained by a struct or union
+ defined in this module, is a parameter of a message in an interface in
+ this module or is contained within another type needed for serialization.
+ """
+ used_typemaps = []
+ seen_types = set()
+ def AddKind(kind):
+ if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) or
+ mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) or
+ mojom.IsAnyHandleKind(kind) or
+ mojom.IsInterfaceKind(kind) or
+ mojom.IsInterfaceRequestKind(kind) or
+ mojom.IsAssociatedKind(kind)):
+ pass
+ elif mojom.IsArrayKind(kind):
+ AddKind(kind.kind)
+ elif mojom.IsMapKind(kind):
+ AddKind(kind.key_kind)
+ AddKind(kind.value_kind)
+ else:
+ name = GetFullMojomNameForKind(kind)
+ if name in seen_types:
+ return
+ seen_types.add(name)
+
+ typemap = _current_typemap.get(name, None)
+ if typemap:
+ used_typemaps.append(typemap)
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ for field in kind.fields:
+ AddKind(field.kind)
+
+ for kind in self.module.structs + self.module.unions:
+ for field in kind.fields:
+ AddKind(field.kind)
+
+ for interface in self.module.interfaces:
+ for method in interface.methods:
+ for parameter in method.parameters + (method.response_parameters or []):
+ AddKind(parameter.kind)
+
+ return used_typemaps
+
+ def GetExtraPublicHeaders(self):
+ all_enums = list(self.module.enums)
+ for struct in self.module.structs:
+ all_enums.extend(struct.enums)
+ for interface in self.module.interfaces:
+ all_enums.extend(interface.enums)
+
+ types = set(GetFullMojomNameForKind(typename)
+ for typename in
+ self.module.structs + all_enums + self.module.unions)
+ headers = set()
+ for typename, typemap in self.typemap.iteritems():
+ if typename in types:
+ headers.update(typemap.get("public_headers", []))
+ return sorted(headers)
+
+ def _GetDirectlyUsedKinds(self):
+ for struct in self.module.structs + self.module.unions:
+ for field in struct.fields:
+ yield field.kind
+
+ for interface in self.module.interfaces:
+ for method in interface.methods:
+ for param in method.parameters + (method.response_parameters or []):
+ yield param.kind
+
+ def GetJinjaExports(self):
+ structs = self.GetStructs()
+ interfaces = self.GetInterfaces()
+ all_enums = list(self.module.enums)
+ for struct in structs:
+ all_enums.extend(struct.enums)
+ for interface in interfaces:
+ all_enums.extend(interface.enums)
+
+ return {
+ "module": self.module,
+ "namespace": self.module.namespace,
+ "namespaces_as_array": NamespaceToArray(self.module.namespace),
+ "imports": self.module.imports,
+ "kinds": self.module.kinds,
+ "enums": self.module.enums,
+ "all_enums": all_enums,
+ "structs": structs,
+ "unions": self.GetUnions(),
+ "interfaces": interfaces,
+ "variant": self.variant,
+ "extra_traits_headers": self.GetExtraTraitsHeaders(),
+ "extra_public_headers": self.GetExtraPublicHeaders(),
+ "for_blink": self.for_blink,
+ "use_once_callback": self.use_once_callback,
+ "export_attribute": self.export_attribute,
+ "export_header": self.export_header,
+ }
+
+ @staticmethod
+ def GetTemplatePrefix():
+ return "cpp_templates"
+
+ @classmethod
+ def GetFilters(cls):
+ return cls.cpp_filters
+
+ @UseJinja("module.h.tmpl")
+ def GenerateModuleHeader(self):
+ return self.GetJinjaExports()
+
+ @UseJinja("module.cc.tmpl")
+ def GenerateModuleSource(self):
+ return self.GetJinjaExports()
+
+ @UseJinja("module-shared.h.tmpl")
+ def GenerateModuleSharedHeader(self):
+ return self.GetJinjaExports()
+
+ @UseJinja("module-shared-internal.h.tmpl")
+ def GenerateModuleSharedInternalHeader(self):
+ return self.GetJinjaExports()
+
+ @UseJinja("module-shared.cc.tmpl")
+ def GenerateModuleSharedSource(self):
+ return self.GetJinjaExports()
+
+ def GenerateFiles(self, args):
+ if self.generate_non_variant_code:
+ self.Write(self.GenerateModuleSharedHeader(),
+ self.MatchMojomFilePath("%s-shared.h" % self.module.name))
+ self.Write(
+ self.GenerateModuleSharedInternalHeader(),
+ self.MatchMojomFilePath("%s-shared-internal.h" % self.module.name))
+ self.Write(self.GenerateModuleSharedSource(),
+ self.MatchMojomFilePath("%s-shared.cc" % self.module.name))
+ else:
+ global _current_typemap
+ _current_typemap = self.typemap
+ global _for_blink
+ _for_blink = self.for_blink
+ global _use_once_callback
+ _use_once_callback = self.use_once_callback
+ global _variant
+ _variant = self.variant
+ global _export_attribute
+ _export_attribute = self.export_attribute
+ suffix = "-%s" % self.variant if self.variant else ""
+ self.Write(self.GenerateModuleHeader(),
+ self.MatchMojomFilePath("%s%s.h" % (self.module.name, suffix)))
+ self.Write(
+ self.GenerateModuleSource(),
+ self.MatchMojomFilePath("%s%s.cc" % (self.module.name, suffix)))
diff --git a/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py
new file mode 100644
index 0000000000..c7657ff99a
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py
@@ -0,0 +1,550 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates java source files from a mojom.Module."""
+
+import argparse
+import ast
+import contextlib
+import os
+import re
+import shutil
+import sys
+import tempfile
+
+from jinja2 import contextfilter
+
+import mojom.fileutil as fileutil
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+from mojom.generate.template_expander import UseJinja
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir,
+ os.pardir, os.pardir, os.pardir, os.pardir,
+ 'build', 'android', 'gyp'))
+from util import build_utils
+
+
+GENERATOR_PREFIX = 'java'
+
+_spec_to_java_type = {
+ mojom.BOOL.spec: 'boolean',
+ mojom.DCPIPE.spec: 'org.chromium.mojo.system.DataPipe.ConsumerHandle',
+ mojom.DOUBLE.spec: 'double',
+ mojom.DPPIPE.spec: 'org.chromium.mojo.system.DataPipe.ProducerHandle',
+ mojom.FLOAT.spec: 'float',
+ mojom.HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
+ mojom.INT16.spec: 'short',
+ mojom.INT32.spec: 'int',
+ mojom.INT64.spec: 'long',
+ mojom.INT8.spec: 'byte',
+ mojom.MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
+ mojom.NULLABLE_DCPIPE.spec:
+ 'org.chromium.mojo.system.DataPipe.ConsumerHandle',
+ mojom.NULLABLE_DPPIPE.spec:
+ 'org.chromium.mojo.system.DataPipe.ProducerHandle',
+ mojom.NULLABLE_HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
+ mojom.NULLABLE_MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
+ mojom.NULLABLE_SHAREDBUFFER.spec:
+ 'org.chromium.mojo.system.SharedBufferHandle',
+ mojom.NULLABLE_STRING.spec: 'String',
+ mojom.SHAREDBUFFER.spec: 'org.chromium.mojo.system.SharedBufferHandle',
+ mojom.STRING.spec: 'String',
+ mojom.UINT16.spec: 'short',
+ mojom.UINT32.spec: 'int',
+ mojom.UINT64.spec: 'long',
+ mojom.UINT8.spec: 'byte',
+}
+
+_spec_to_decode_method = {
+ mojom.BOOL.spec: 'readBoolean',
+ mojom.DCPIPE.spec: 'readConsumerHandle',
+ mojom.DOUBLE.spec: 'readDouble',
+ mojom.DPPIPE.spec: 'readProducerHandle',
+ mojom.FLOAT.spec: 'readFloat',
+ mojom.HANDLE.spec: 'readUntypedHandle',
+ mojom.INT16.spec: 'readShort',
+ mojom.INT32.spec: 'readInt',
+ mojom.INT64.spec: 'readLong',
+ mojom.INT8.spec: 'readByte',
+ mojom.MSGPIPE.spec: 'readMessagePipeHandle',
+ mojom.NULLABLE_DCPIPE.spec: 'readConsumerHandle',
+ mojom.NULLABLE_DPPIPE.spec: 'readProducerHandle',
+ mojom.NULLABLE_HANDLE.spec: 'readUntypedHandle',
+ mojom.NULLABLE_MSGPIPE.spec: 'readMessagePipeHandle',
+ mojom.NULLABLE_SHAREDBUFFER.spec: 'readSharedBufferHandle',
+ mojom.NULLABLE_STRING.spec: 'readString',
+ mojom.SHAREDBUFFER.spec: 'readSharedBufferHandle',
+ mojom.STRING.spec: 'readString',
+ mojom.UINT16.spec: 'readShort',
+ mojom.UINT32.spec: 'readInt',
+ mojom.UINT64.spec: 'readLong',
+ mojom.UINT8.spec: 'readByte',
+}
+
+_java_primitive_to_boxed_type = {
+ 'boolean': 'Boolean',
+ 'byte': 'Byte',
+ 'double': 'Double',
+ 'float': 'Float',
+ 'int': 'Integer',
+ 'long': 'Long',
+ 'short': 'Short',
+}
+
+
+def NameToComponent(name):
+ # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
+ # HTTP_Entry2_FooBar)
+ name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name)
+ # insert '_' between non upper and start of upper blocks (e.g.,
+ # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar)
+ name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name)
+ return [x.lower() for x in name.split('_')]
+
+def UpperCamelCase(name):
+ return ''.join([x.capitalize() for x in NameToComponent(name)])
+
+def CamelCase(name):
+ uccc = UpperCamelCase(name)
+ return uccc[0].lower() + uccc[1:]
+
+def ConstantStyle(name):
+ components = NameToComponent(name)
+ if components[0] == 'k' and len(components) > 1:
+ components = components[1:]
+ # variable cannot starts with a digit.
+ if components[0][0].isdigit():
+ components[0] = '_' + components[0]
+ return '_'.join([x.upper() for x in components])
+
+def GetNameForElement(element):
+ if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
+ mojom.IsStructKind(element) or mojom.IsUnionKind(element)):
+ return UpperCamelCase(element.name)
+ if mojom.IsInterfaceRequestKind(element) or mojom.IsAssociatedKind(element):
+ return GetNameForElement(element.kind)
+ if isinstance(element, (mojom.Method,
+ mojom.Parameter,
+ mojom.Field)):
+ return CamelCase(element.name)
+ if isinstance(element, mojom.EnumValue):
+ return (GetNameForElement(element.enum) + '.' +
+ ConstantStyle(element.name))
+ if isinstance(element, (mojom.NamedValue,
+ mojom.Constant,
+ mojom.EnumField)):
+ return ConstantStyle(element.name)
+ raise Exception('Unexpected element: %s' % element)
+
+def GetInterfaceResponseName(method):
+ return UpperCamelCase(method.name + 'Response')
+
+def ParseStringAttribute(attribute):
+ assert isinstance(attribute, basestring)
+ return attribute
+
+def GetJavaTrueFalse(value):
+ return 'true' if value else 'false'
+
+def GetArrayNullabilityFlags(kind):
+ """Returns nullability flags for an array type, see Decoder.java.
+
+ As we have dedicated decoding functions for arrays, we have to pass
+ nullability information about both the array itself, as well as the array
+ element type there.
+ """
+ assert mojom.IsArrayKind(kind)
+ ARRAY_NULLABLE = \
+ 'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE'
+ ELEMENT_NULLABLE = \
+ 'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE'
+ NOTHING_NULLABLE = \
+ 'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE'
+
+ flags_to_set = []
+ if mojom.IsNullableKind(kind):
+ flags_to_set.append(ARRAY_NULLABLE)
+ if mojom.IsNullableKind(kind.kind):
+ flags_to_set.append(ELEMENT_NULLABLE)
+
+ if not flags_to_set:
+ flags_to_set = [NOTHING_NULLABLE]
+ return ' | '.join(flags_to_set)
+
+
+def AppendEncodeDecodeParams(initial_params, context, kind, bit):
+ """ Appends standard parameters shared between encode and decode calls. """
+ params = list(initial_params)
+ if (kind == mojom.BOOL):
+ params.append(str(bit))
+ if mojom.IsReferenceKind(kind):
+ if mojom.IsArrayKind(kind):
+ params.append(GetArrayNullabilityFlags(kind))
+ else:
+ params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind)))
+ if mojom.IsArrayKind(kind):
+ params.append(GetArrayExpectedLength(kind))
+ if mojom.IsInterfaceKind(kind):
+ params.append('%s.MANAGER' % GetJavaType(context, kind))
+ if mojom.IsArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
+ params.append('%s.MANAGER' % GetJavaType(context, kind.kind))
+ return params
+
+
+@contextfilter
+def DecodeMethod(context, kind, offset, bit):
+ def _DecodeMethodName(kind):
+ if mojom.IsArrayKind(kind):
+ return _DecodeMethodName(kind.kind) + 's'
+ if mojom.IsEnumKind(kind):
+ return _DecodeMethodName(mojom.INT32)
+ if mojom.IsInterfaceRequestKind(kind):
+ return 'readInterfaceRequest'
+ if mojom.IsInterfaceKind(kind):
+ return 'readServiceInterface'
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return 'readAssociatedInterfaceRequestNotSupported'
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return 'readAssociatedServiceInterfaceNotSupported'
+ return _spec_to_decode_method[kind.spec]
+ methodName = _DecodeMethodName(kind)
+ params = AppendEncodeDecodeParams([ str(offset) ], context, kind, bit)
+ return '%s(%s)' % (methodName, ', '.join(params))
+
+@contextfilter
+def EncodeMethod(context, kind, variable, offset, bit):
+ params = AppendEncodeDecodeParams(
+ [ variable, str(offset) ], context, kind, bit)
+ return 'encode(%s)' % ', '.join(params)
+
+def GetPackage(module):
+ if module.attributes and 'JavaPackage' in module.attributes:
+ return ParseStringAttribute(module.attributes['JavaPackage'])
+ # Default package.
+ if module.namespace:
+ return 'org.chromium.' + module.namespace
+ return 'org.chromium'
+
+def GetNameForKind(context, kind):
+ def _GetNameHierachy(kind):
+ hierachy = []
+ if kind.parent_kind:
+ hierachy = _GetNameHierachy(kind.parent_kind)
+ hierachy.append(GetNameForElement(kind))
+ return hierachy
+
+ module = context.resolve('module')
+ elements = []
+ if GetPackage(module) != GetPackage(kind.module):
+ elements += [GetPackage(kind.module)]
+ elements += _GetNameHierachy(kind)
+ return '.'.join(elements)
+
+@contextfilter
+def GetJavaClassForEnum(context, kind):
+ return GetNameForKind(context, kind)
+
+def GetBoxedJavaType(context, kind, with_generics=True):
+ unboxed_type = GetJavaType(context, kind, False, with_generics)
+ if unboxed_type in _java_primitive_to_boxed_type:
+ return _java_primitive_to_boxed_type[unboxed_type]
+ return unboxed_type
+
+@contextfilter
+def GetJavaType(context, kind, boxed=False, with_generics=True):
+ if boxed:
+ return GetBoxedJavaType(context, kind)
+ if (mojom.IsStructKind(kind) or
+ mojom.IsInterfaceKind(kind) or
+ mojom.IsUnionKind(kind)):
+ return GetNameForKind(context, kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' %
+ GetNameForKind(context, kind.kind))
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return 'org.chromium.mojo.bindings.AssociatedInterfaceNotSupported'
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return 'org.chromium.mojo.bindings.AssociatedInterfaceRequestNotSupported'
+ if mojom.IsMapKind(kind):
+ if with_generics:
+ return 'java.util.Map<%s, %s>' % (
+ GetBoxedJavaType(context, kind.key_kind),
+ GetBoxedJavaType(context, kind.value_kind))
+ else:
+ return 'java.util.Map'
+ if mojom.IsArrayKind(kind):
+ return '%s[]' % GetJavaType(context, kind.kind, boxed, with_generics)
+ if mojom.IsEnumKind(kind):
+ return 'int'
+ return _spec_to_java_type[kind.spec]
+
+@contextfilter
+def DefaultValue(context, field):
+ assert field.default
+ if isinstance(field.kind, mojom.Struct):
+ assert field.default == 'default'
+ return 'new %s()' % GetJavaType(context, field.kind)
+ return '(%s) %s' % (
+ GetJavaType(context, field.kind),
+ ExpressionToText(context, field.default, kind_spec=field.kind.spec))
+
+@contextfilter
+def ConstantValue(context, constant):
+ return '(%s) %s' % (
+ GetJavaType(context, constant.kind),
+ ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
+
+@contextfilter
+def NewArray(context, kind, size):
+ if mojom.IsArrayKind(kind.kind):
+ return NewArray(context, kind.kind, size) + '[]'
+ return 'new %s[%s]' % (
+ GetJavaType(context, kind.kind, boxed=False, with_generics=False), size)
+
+@contextfilter
+def ExpressionToText(context, token, kind_spec=''):
+ def _TranslateNamedValue(named_value):
+ entity_name = GetNameForElement(named_value)
+ if named_value.parent_kind:
+ return GetJavaType(context, named_value.parent_kind) + '.' + entity_name
+ # Handle the case where named_value is a module level constant:
+ if not isinstance(named_value, mojom.EnumValue):
+ entity_name = (GetConstantsMainEntityName(named_value.module) + '.' +
+ entity_name)
+ if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
+ return entity_name
+ return GetPackage(named_value.module) + '.' + entity_name
+
+ if isinstance(token, mojom.NamedValue):
+ return _TranslateNamedValue(token)
+ if kind_spec.startswith('i') or kind_spec.startswith('u'):
+ # Add Long suffix to all integer literals.
+ number = ast.literal_eval(token.lstrip('+ '))
+ if not isinstance(number, (int, long)):
+ raise ValueError('got unexpected type %r for int literal %r' % (
+ type(number), token))
+ # If the literal is too large to fit a signed long, convert it to the
+ # equivalent signed long.
+ if number >= 2 ** 63:
+ number -= 2 ** 64
+ return '%dL' % number
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == 'double.INFINITY':
+ return 'java.lang.Double.POSITIVE_INFINITY'
+ if token.value == 'double.NEGATIVE_INFINITY':
+ return 'java.lang.Double.NEGATIVE_INFINITY'
+ if token.value == 'double.NAN':
+ return 'java.lang.Double.NaN'
+ if token.value == 'float.INFINITY':
+ return 'java.lang.Float.POSITIVE_INFINITY'
+ if token.value == 'float.NEGATIVE_INFINITY':
+ return 'java.lang.Float.NEGATIVE_INFINITY'
+ if token.value == 'float.NAN':
+ return 'java.lang.Float.NaN'
+ return token
+
+def GetArrayKind(kind, size = None):
+ if size is None:
+ return mojom.Array(kind)
+ else:
+ array = mojom.Array(kind, 0)
+ array.java_map_size = size
+ return array
+
+def GetArrayExpectedLength(kind):
+ if mojom.IsArrayKind(kind) and kind.length is not None:
+ return getattr(kind, 'java_map_size', str(kind.length))
+ else:
+ return 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH'
+
+def IsPointerArrayKind(kind):
+ if not mojom.IsArrayKind(kind):
+ return False
+ sub_kind = kind.kind
+ return mojom.IsObjectKind(sub_kind) and not mojom.IsUnionKind(sub_kind)
+
+def IsUnionArrayKind(kind):
+ if not mojom.IsArrayKind(kind):
+ return False
+ sub_kind = kind.kind
+ return mojom.IsUnionKind(sub_kind)
+
+def GetConstantsMainEntityName(module):
+ if module.attributes and 'JavaConstantsClassName' in module.attributes:
+ return ParseStringAttribute(module.attributes['JavaConstantsClassName'])
+ # This constructs the name of the embedding classes for module level constants
+ # by extracting the mojom's filename and prepending it to Constants.
+ return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) +
+ 'Constants')
+
+def GetMethodOrdinalName(method):
+ return ConstantStyle(method.name) + '_ORDINAL'
+
+def HasMethodWithResponse(interface):
+ for method in interface.methods:
+ if method.response_parameters is not None:
+ return True
+ return False
+
+def HasMethodWithoutResponse(interface):
+ for method in interface.methods:
+ if method.response_parameters is None:
+ return True
+ return False
+
+@contextlib.contextmanager
+def TempDir():
+ dirname = tempfile.mkdtemp()
+ try:
+ yield dirname
+ finally:
+ shutil.rmtree(dirname)
+
+class Generator(generator.Generator):
+
+ java_filters = {
+ 'array_expected_length': GetArrayExpectedLength,
+ 'array': GetArrayKind,
+ 'constant_value': ConstantValue,
+ 'decode_method': DecodeMethod,
+ 'default_value': DefaultValue,
+ 'encode_method': EncodeMethod,
+ 'expression_to_text': ExpressionToText,
+ 'has_method_without_response': HasMethodWithoutResponse,
+ 'has_method_with_response': HasMethodWithResponse,
+ 'interface_response_name': GetInterfaceResponseName,
+ 'is_array_kind': mojom.IsArrayKind,
+ 'is_any_handle_kind': mojom.IsAnyHandleKind,
+ "is_enum_kind": mojom.IsEnumKind,
+ 'is_interface_request_kind': mojom.IsInterfaceRequestKind,
+ 'is_map_kind': mojom.IsMapKind,
+ 'is_nullable_kind': mojom.IsNullableKind,
+ 'is_pointer_array_kind': IsPointerArrayKind,
+ 'is_reference_kind': mojom.IsReferenceKind,
+ 'is_struct_kind': mojom.IsStructKind,
+ 'is_union_array_kind': IsUnionArrayKind,
+ 'is_union_kind': mojom.IsUnionKind,
+ 'java_class_for_enum': GetJavaClassForEnum,
+ 'java_true_false': GetJavaTrueFalse,
+ 'java_type': GetJavaType,
+ 'method_ordinal_name': GetMethodOrdinalName,
+ 'name': GetNameForElement,
+ 'new_array': NewArray,
+ 'ucc': lambda x: UpperCamelCase(x.name),
+ }
+
+ def GetJinjaExports(self):
+ return {
+ 'package': GetPackage(self.module),
+ }
+
+ @staticmethod
+ def GetTemplatePrefix():
+ return "java_templates"
+
+ @classmethod
+ def GetFilters(cls):
+ return cls.java_filters
+
+ def GetJinjaExportsForInterface(self, interface):
+ exports = self.GetJinjaExports()
+ exports.update({'interface': interface})
+ return exports
+
+ @UseJinja('enum.java.tmpl')
+ def GenerateEnumSource(self, enum):
+ exports = self.GetJinjaExports()
+ exports.update({'enum': enum})
+ return exports
+
+ @UseJinja('struct.java.tmpl')
+ def GenerateStructSource(self, struct):
+ exports = self.GetJinjaExports()
+ exports.update({'struct': struct})
+ return exports
+
+ @UseJinja('union.java.tmpl')
+ def GenerateUnionSource(self, union):
+ exports = self.GetJinjaExports()
+ exports.update({'union': union})
+ return exports
+
+ @UseJinja('interface.java.tmpl')
+ def GenerateInterfaceSource(self, interface):
+ return self.GetJinjaExportsForInterface(interface)
+
+ @UseJinja('interface_internal.java.tmpl')
+ def GenerateInterfaceInternalSource(self, interface):
+ return self.GetJinjaExportsForInterface(interface)
+
+ @UseJinja('constants.java.tmpl')
+ def GenerateConstantsSource(self, module):
+ exports = self.GetJinjaExports()
+ exports.update({'main_entity': GetConstantsMainEntityName(module),
+ 'constants': module.constants})
+ return exports
+
+ def DoGenerateFiles(self):
+ fileutil.EnsureDirectoryExists(self.output_dir)
+
+ # Keep this above the others as .GetStructs() changes the state of the
+ # module, annotating structs with required information.
+ for struct in self.GetStructs():
+ self.Write(self.GenerateStructSource(struct),
+ '%s.java' % GetNameForElement(struct))
+
+ for union in self.module.unions:
+ self.Write(self.GenerateUnionSource(union),
+ '%s.java' % GetNameForElement(union))
+
+ for enum in self.module.enums:
+ self.Write(self.GenerateEnumSource(enum),
+ '%s.java' % GetNameForElement(enum))
+
+ for interface in self.GetInterfaces():
+ self.Write(self.GenerateInterfaceSource(interface),
+ '%s.java' % GetNameForElement(interface))
+ self.Write(self.GenerateInterfaceInternalSource(interface),
+ '%s_Internal.java' % GetNameForElement(interface))
+
+ if self.module.constants:
+ self.Write(self.GenerateConstantsSource(self.module),
+ '%s.java' % GetConstantsMainEntityName(self.module))
+
+ def GenerateFiles(self, unparsed_args):
+ # TODO(rockot): Support variant output for Java.
+ if self.variant:
+ raise Exception("Variants not supported in Java bindings.")
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--java_output_directory', dest='java_output_directory')
+ args = parser.parse_args(unparsed_args)
+ package_path = GetPackage(self.module).replace('.', '/')
+
+ # Generate the java files in a temporary directory and place a single
+ # srcjar in the output directory.
+ basename = self.MatchMojomFilePath("%s.srcjar" % self.module.name)
+ zip_filename = os.path.join(self.output_dir, basename)
+ with TempDir() as temp_java_root:
+ self.output_dir = os.path.join(temp_java_root, package_path)
+ self.DoGenerateFiles();
+ build_utils.ZipDir(zip_filename, temp_java_root)
+
+ if args.java_output_directory:
+ # If requested, generate the java files directly into indicated directory.
+ self.output_dir = os.path.join(args.java_output_directory, package_path)
+ self.DoGenerateFiles();
+
+ def GetJinjaParameters(self):
+ return {
+ 'lstrip_blocks': True,
+ 'trim_blocks': True,
+ }
+
+ def GetGlobals(self):
+ return {
+ 'namespace': self.module.namespace,
+ 'module': self.module,
+ }
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
new file mode 100644
index 0000000000..ab9635ee30
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -0,0 +1,425 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates JavaScript source files from a mojom.Module."""
+
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+import mojom.generate.pack as pack
+import os
+from mojom.generate.template_expander import UseJinja
+
+_kind_to_javascript_default_value = {
+ mojom.BOOL: "false",
+ mojom.INT8: "0",
+ mojom.UINT8: "0",
+ mojom.INT16: "0",
+ mojom.UINT16: "0",
+ mojom.INT32: "0",
+ mojom.UINT32: "0",
+ mojom.FLOAT: "0",
+ mojom.HANDLE: "null",
+ mojom.DCPIPE: "null",
+ mojom.DPPIPE: "null",
+ mojom.MSGPIPE: "null",
+ mojom.SHAREDBUFFER: "null",
+ mojom.NULLABLE_HANDLE: "null",
+ mojom.NULLABLE_DCPIPE: "null",
+ mojom.NULLABLE_DPPIPE: "null",
+ mojom.NULLABLE_MSGPIPE: "null",
+ mojom.NULLABLE_SHAREDBUFFER: "null",
+ mojom.INT64: "0",
+ mojom.UINT64: "0",
+ mojom.DOUBLE: "0",
+ mojom.STRING: "null",
+ mojom.NULLABLE_STRING: "null"
+}
+
+
+def JavaScriptType(kind):
+ name = []
+ if kind.imported_from:
+ name.append(kind.imported_from["unique_name"])
+ if kind.parent_kind:
+ name.append(kind.parent_kind.name)
+ name.append(kind.name)
+ return ".".join(name)
+
+
+def JavaScriptDefaultValue(field):
+ if field.default:
+ if mojom.IsStructKind(field.kind):
+ assert field.default == "default"
+ return "new %s()" % JavaScriptType(field.kind)
+ return ExpressionToText(field.default)
+ if field.kind in mojom.PRIMITIVES:
+ return _kind_to_javascript_default_value[field.kind]
+ if mojom.IsStructKind(field.kind):
+ return "null"
+ if mojom.IsUnionKind(field.kind):
+ return "null"
+ if mojom.IsArrayKind(field.kind):
+ return "null"
+ if mojom.IsMapKind(field.kind):
+ return "null"
+ if mojom.IsInterfaceKind(field.kind):
+ return "new %sPtr()" % JavaScriptType(field.kind)
+ if mojom.IsInterfaceRequestKind(field.kind):
+ return "new bindings.InterfaceRequest()"
+ if mojom.IsAssociatedKind(field.kind):
+ return "null"
+ if mojom.IsEnumKind(field.kind):
+ return "0"
+ raise Exception("No valid default: %s" % field)
+
+
+def JavaScriptPayloadSize(packed):
+ packed_fields = packed.packed_fields
+ if not packed_fields:
+ return 0
+ last_field = packed_fields[-1]
+ offset = last_field.offset + last_field.size
+ pad = pack.GetPad(offset, 8)
+ return offset + pad
+
+
+_kind_to_codec_type = {
+ mojom.BOOL: "codec.Uint8",
+ mojom.INT8: "codec.Int8",
+ mojom.UINT8: "codec.Uint8",
+ mojom.INT16: "codec.Int16",
+ mojom.UINT16: "codec.Uint16",
+ mojom.INT32: "codec.Int32",
+ mojom.UINT32: "codec.Uint32",
+ mojom.FLOAT: "codec.Float",
+ mojom.HANDLE: "codec.Handle",
+ mojom.DCPIPE: "codec.Handle",
+ mojom.DPPIPE: "codec.Handle",
+ mojom.MSGPIPE: "codec.Handle",
+ mojom.SHAREDBUFFER: "codec.Handle",
+ mojom.NULLABLE_HANDLE: "codec.NullableHandle",
+ mojom.NULLABLE_DCPIPE: "codec.NullableHandle",
+ mojom.NULLABLE_DPPIPE: "codec.NullableHandle",
+ mojom.NULLABLE_MSGPIPE: "codec.NullableHandle",
+ mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle",
+ mojom.INT64: "codec.Int64",
+ mojom.UINT64: "codec.Uint64",
+ mojom.DOUBLE: "codec.Double",
+ mojom.STRING: "codec.String",
+ mojom.NULLABLE_STRING: "codec.NullableString",
+}
+
+
+def CodecType(kind):
+ if kind in mojom.PRIMITIVES:
+ return _kind_to_codec_type[kind]
+ if mojom.IsStructKind(kind):
+ pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
+ else "PointerTo"
+ return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind))
+ if mojom.IsUnionKind(kind):
+ return JavaScriptType(kind)
+ if mojom.IsArrayKind(kind):
+ array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf"
+ array_length = "" if kind.length is None else ", %d" % kind.length
+ element_type = ElementCodecType(kind.kind)
+ return "new codec.%s(%s%s)" % (array_type, element_type, array_length)
+ if mojom.IsInterfaceKind(kind):
+ return "new codec.%s(%sPtr)" % (
+ "NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
+ JavaScriptType(kind))
+ if mojom.IsInterfaceRequestKind(kind):
+ return "codec.%s" % (
+ "NullableInterfaceRequest" if mojom.IsNullableKind(kind)
+ else "InterfaceRequest")
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "codec.AssociatedInterfaceNotSupported"
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "codec.AssociatedInterfaceRequestNotSupported"
+ if mojom.IsEnumKind(kind):
+ return "new codec.Enum(%s)" % JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf"
+ key_type = ElementCodecType(kind.key_kind)
+ value_type = ElementCodecType(kind.value_kind)
+ return "new codec.%s(%s, %s)" % (map_type, key_type, value_type)
+ raise Exception("No codec type for %s" % kind)
+
+
+def ElementCodecType(kind):
+ return "codec.PackedBool" if mojom.IsBoolKind(kind) else CodecType(kind)
+
+
+def JavaScriptDecodeSnippet(kind):
+ if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
+ mojom.IsAnyInterfaceKind(kind)):
+ return "decodeStruct(%s)" % CodecType(kind)
+ if mojom.IsStructKind(kind):
+ return "decodeStructPointer(%s)" % JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ return "decodeMapPointer(%s, %s)" % \
+ (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind))
+ if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
+ return "decodeArrayPointer(codec.PackedBool)"
+ if mojom.IsArrayKind(kind):
+ return "decodeArrayPointer(%s)" % CodecType(kind.kind)
+ if mojom.IsUnionKind(kind):
+ return "decodeUnion(%s)" % CodecType(kind)
+ if mojom.IsEnumKind(kind):
+ return JavaScriptDecodeSnippet(mojom.INT32)
+ raise Exception("No decode snippet for %s" % kind)
+
+
+def JavaScriptEncodeSnippet(kind):
+ if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
+ mojom.IsAnyInterfaceKind(kind)):
+ return "encodeStruct(%s, " % CodecType(kind)
+ if mojom.IsUnionKind(kind):
+ return "encodeStruct(%s, " % JavaScriptType(kind)
+ if mojom.IsStructKind(kind):
+ return "encodeStructPointer(%s, " % JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ return "encodeMapPointer(%s, %s, " % \
+ (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind))
+ if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
+ return "encodeArrayPointer(codec.PackedBool, ";
+ if mojom.IsArrayKind(kind):
+ return "encodeArrayPointer(%s, " % CodecType(kind.kind)
+ if mojom.IsEnumKind(kind):
+ return JavaScriptEncodeSnippet(mojom.INT32)
+ raise Exception("No encode snippet for %s" % kind)
+
+
+def JavaScriptUnionDecodeSnippet(kind):
+ if mojom.IsUnionKind(kind):
+ return "decodeStructPointer(%s)" % JavaScriptType(kind)
+ return JavaScriptDecodeSnippet(kind)
+
+
+def JavaScriptUnionEncodeSnippet(kind):
+ if mojom.IsUnionKind(kind):
+ return "encodeStructPointer(%s, " % JavaScriptType(kind)
+ return JavaScriptEncodeSnippet(kind)
+
+
+def JavaScriptFieldOffset(packed_field):
+ return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
+
+
+def JavaScriptNullableParam(field):
+ return "true" if mojom.IsNullableKind(field.kind) else "false"
+
+
+def GetArrayExpectedDimensionSizes(kind):
+ expected_dimension_sizes = []
+ while mojom.IsArrayKind(kind):
+ expected_dimension_sizes.append(generator.ExpectedArraySize(kind) or 0)
+ kind = kind.kind
+ # Strings are serialized as variable-length arrays.
+ if (mojom.IsStringKind(kind)):
+ expected_dimension_sizes.append(0)
+ return expected_dimension_sizes
+
+
+def JavaScriptValidateArrayParams(field):
+ nullable = JavaScriptNullableParam(field)
+ element_kind = field.kind.kind
+ element_size = pack.PackedField.GetSizeForKind(element_kind)
+ expected_dimension_sizes = GetArrayExpectedDimensionSizes(
+ field.kind)
+ element_type = ElementCodecType(element_kind)
+ return "%s, %s, %s, %s, 0" % \
+ (element_size, element_type, nullable,
+ expected_dimension_sizes)
+
+
+def JavaScriptValidateEnumParams(field):
+ return JavaScriptType(field.kind)
+
+def JavaScriptValidateStructParams(field):
+ nullable = JavaScriptNullableParam(field)
+ struct_type = JavaScriptType(field.kind)
+ return "%s, %s" % (struct_type, nullable)
+
+def JavaScriptValidateUnionParams(field):
+ nullable = JavaScriptNullableParam(field)
+ union_type = JavaScriptType(field.kind)
+ return "%s, %s" % (union_type, nullable)
+
+def JavaScriptValidateMapParams(field):
+ nullable = JavaScriptNullableParam(field)
+ keys_type = ElementCodecType(field.kind.key_kind)
+ values_kind = field.kind.value_kind;
+ values_type = ElementCodecType(values_kind)
+ values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false"
+ return "%s, %s, %s, %s" % \
+ (nullable, keys_type, values_type, values_nullable)
+
+
+def TranslateConstants(token):
+ if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
+ # Both variable and enum constants are constructed like:
+ # NamespaceUid.Struct[.Enum].CONSTANT_NAME
+ name = []
+ if token.imported_from:
+ name.append(token.imported_from["unique_name"])
+ if token.parent_kind:
+ name.append(token.parent_kind.name)
+ if isinstance(token, mojom.EnumValue):
+ name.append(token.enum.name)
+ name.append(token.name)
+ return ".".join(name)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == "double.INFINITY" or token.value == "float.INFINITY":
+ return "Infinity";
+ if token.value == "double.NEGATIVE_INFINITY" or \
+ token.value == "float.NEGATIVE_INFINITY":
+ return "-Infinity";
+ if token.value == "double.NAN" or token.value == "float.NAN":
+ return "NaN";
+
+ return token
+
+
+def ExpressionToText(value):
+ return TranslateConstants(value)
+
+def IsArrayPointerField(field):
+ return mojom.IsArrayKind(field.kind)
+
+def IsEnumField(field):
+ return mojom.IsEnumKind(field.kind)
+
+def IsStringPointerField(field):
+ return mojom.IsStringKind(field.kind)
+
+def IsStructPointerField(field):
+ return mojom.IsStructKind(field.kind)
+
+def IsMapPointerField(field):
+ return mojom.IsMapKind(field.kind)
+
+def IsHandleField(field):
+ return mojom.IsAnyHandleKind(field.kind)
+
+def IsInterfaceField(field):
+ return mojom.IsInterfaceKind(field.kind)
+
+def IsInterfaceRequestField(field):
+ return mojom.IsInterfaceRequestKind(field.kind)
+
+def IsUnionField(field):
+ return mojom.IsUnionKind(field.kind)
+
+def IsBoolField(field):
+ return mojom.IsBoolKind(field.kind)
+
+def IsObjectField(field):
+ return mojom.IsObjectKind(field.kind)
+
+def IsAnyHandleOrInterfaceField(field):
+ return mojom.IsAnyHandleOrInterfaceKind(field.kind)
+
+def IsEnumField(field):
+ return mojom.IsEnumKind(field.kind)
+
+def GetRelativePath(module, base_module):
+ return os.path.relpath(module.path, os.path.dirname(base_module.path))
+
+
+class Generator(generator.Generator):
+
+ js_filters = {
+ "decode_snippet": JavaScriptDecodeSnippet,
+ "default_value": JavaScriptDefaultValue,
+ "encode_snippet": JavaScriptEncodeSnippet,
+ "expression_to_text": ExpressionToText,
+ "field_offset": JavaScriptFieldOffset,
+ "has_callbacks": mojom.HasCallbacks,
+ "is_any_handle_or_interface_field": IsAnyHandleOrInterfaceField,
+ "is_array_pointer_field": IsArrayPointerField,
+ "is_bool_field": IsBoolField,
+ "is_enum_field": IsEnumField,
+ "is_handle_field": IsHandleField,
+ "is_interface_field": IsInterfaceField,
+ "is_interface_request_field": IsInterfaceRequestField,
+ "is_map_pointer_field": IsMapPointerField,
+ "is_object_field": IsObjectField,
+ "is_string_pointer_field": IsStringPointerField,
+ "is_struct_pointer_field": IsStructPointerField,
+ "is_union_field": IsUnionField,
+ "js_type": JavaScriptType,
+ "payload_size": JavaScriptPayloadSize,
+ "get_relative_path": GetRelativePath,
+ "stylize_method": generator.StudlyCapsToCamel,
+ "union_decode_snippet": JavaScriptUnionDecodeSnippet,
+ "union_encode_snippet": JavaScriptUnionEncodeSnippet,
+ "validate_array_params": JavaScriptValidateArrayParams,
+ "validate_enum_params": JavaScriptValidateEnumParams,
+ "validate_map_params": JavaScriptValidateMapParams,
+ "validate_nullable_params": JavaScriptNullableParam,
+ "validate_struct_params": JavaScriptValidateStructParams,
+ "validate_union_params": JavaScriptValidateUnionParams,
+ }
+
+ def GetParameters(self):
+ return {
+ "namespace": self.module.namespace,
+ "imports": self.GetImports(),
+ "kinds": self.module.kinds,
+ "enums": self.module.enums,
+ "module": self.module,
+ "structs": self.GetStructs() + self.GetStructsFromMethods(),
+ "unions": self.GetUnions(),
+ "use_new_js_bindings": self.use_new_js_bindings,
+ "interfaces": self.GetInterfaces(),
+ "imported_interfaces": self.GetImportedInterfaces(),
+ }
+
+ @staticmethod
+ def GetTemplatePrefix():
+ return "js_templates"
+
+ @classmethod
+ def GetFilters(cls):
+ return cls.js_filters
+
+ @UseJinja("module.amd.tmpl")
+ def GenerateAMDModule(self):
+ return self.GetParameters()
+
+ def GenerateFiles(self, args):
+ if self.variant:
+ raise Exception("Variants not supported in JavaScript bindings.")
+
+ self.Write(self.GenerateAMDModule(),
+ self.MatchMojomFilePath("%s.js" % self.module.name))
+
+ def GetImports(self):
+ used_names = set()
+ for each_import in self.module.imports:
+ simple_name = each_import["module_name"].split(".")[0]
+
+ # Since each import is assigned a variable in JS, they need to have unique
+ # names.
+ unique_name = simple_name
+ counter = 0
+ while unique_name in used_names:
+ counter += 1
+ unique_name = simple_name + str(counter)
+
+ used_names.add(unique_name)
+ each_import["unique_name"] = unique_name + "$"
+ counter += 1
+ return self.module.imports
+
+ def GetImportedInterfaces(self):
+ interface_to_import = {};
+ for each_import in self.module.imports:
+ for each_interface in each_import["module"].interfaces:
+ name = each_interface.name
+ interface_to_import[name] = each_import["unique_name"] + "." + name
+ return interface_to_import;
+
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
new file mode 100644
index 0000000000..4a244fb5b1
--- /dev/null
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -0,0 +1,661 @@
+# Copyright 2014 The Chromium 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() {
+ # Indicates whether typemapping should be supported in this build
+ # configuration. This may be disabled when building external projects which
+ # depend on //mojo but which do not need/want all of the Chromium tree
+ # dependencies that come with typemapping.
+ #
+ # Note that (perhaps obviously) a huge amount of Chromium code will not build
+ # with typemapping disabled, so it is never valid to set this to |false| in
+ # any Chromium build configuration.
+ enable_mojom_typemapping = true
+}
+
+mojom_generator_root = "//mojo/public/tools/bindings"
+mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
+mojom_generator_sources = [
+ "$mojom_generator_root/generators/mojom_cpp_generator.py",
+ "$mojom_generator_root/generators/mojom_js_generator.py",
+ "$mojom_generator_root/generators/mojom_java_generator.py",
+ "$mojom_generator_root/pylib/mojom/__init__.py",
+ "$mojom_generator_root/pylib/mojom/error.py",
+ "$mojom_generator_root/pylib/mojom/generate/__init__.py",
+ "$mojom_generator_root/pylib/mojom/generate/constant_resolver.py",
+ "$mojom_generator_root/pylib/mojom/generate/generator.py",
+ "$mojom_generator_root/pylib/mojom/generate/module.py",
+ "$mojom_generator_root/pylib/mojom/generate/pack.py",
+ "$mojom_generator_root/pylib/mojom/generate/template_expander.py",
+ "$mojom_generator_root/pylib/mojom/generate/translate.py",
+ "$mojom_generator_root/pylib/mojom/parse/__init__.py",
+ "$mojom_generator_root/pylib/mojom/parse/ast.py",
+ "$mojom_generator_root/pylib/mojom/parse/lexer.py",
+ "$mojom_generator_root/pylib/mojom/parse/parser.py",
+ "$mojom_generator_script",
+]
+
+if (enable_mojom_typemapping) {
+ if (!is_ios) {
+ _bindings_configuration_files = [
+ "//mojo/public/tools/bindings/chromium_bindings_configuration.gni",
+ "//mojo/public/tools/bindings/blink_bindings_configuration.gni",
+ ]
+ } else {
+ _bindings_configuration_files =
+ [ "//mojo/public/tools/bindings/chromium_bindings_configuration.gni" ]
+ }
+ _bindings_configurations = []
+ foreach(config_file, _bindings_configuration_files) {
+ _bindings_configurations += [ read_file(config_file, "scope") ]
+ }
+ foreach(configuration, _bindings_configurations) {
+ # Check that the mojom field of each typemap refers to a mojom that exists.
+ foreach(typemap, configuration.typemaps) {
+ _typemap_config = {
+ }
+ _typemap_config = typemap.config
+ read_file(_typemap_config.mojom, "")
+ }
+ if (is_mac && defined(configuration.typemaps_mac)) {
+ foreach(typemap, configuration.typemaps_mac) {
+ _typemap_config = {
+ }
+ _typemap_config = typemap.config
+ read_file(_typemap_config.mojom, "")
+ }
+ }
+ }
+} else {
+ _bindings_configuration_files = []
+ _bindings_configurations = [
+ {
+ typemaps = []
+ },
+ {
+ variant = "blink"
+ for_blink = true
+ typemaps = []
+ },
+ ]
+}
+
+# Generates targets for building C++, JavaScript and Java bindings from mojom
+# files. The output files will go under the generated file directory tree with
+# the same path as each input file.
+#
+# Other targets should depend on one of these generated targets (where "foo"
+# is the target name):
+#
+# foo
+# C++ and Javascript bindings. Other mojom targets should also depend on
+# this target.
+#
+# foo_blink
+# C++ bindings using Blink standard types.
+#
+# foo_java
+# Java bindings.
+#
+# Parameters:
+#
+# sources (optional if one of the deps sets listed below is present)
+# List of source .mojom files to compile.
+#
+# deps (optional)
+# Note: this can contain only other mojom targets.
+#
+# DEPRECATED: This is synonymous with public_deps because all mojom
+# dependencies must be public by design. Please use public_deps.
+#
+# public_deps (optional)
+# Note: this can contain only other mojom targets.
+#
+# import_dirs (optional)
+# List of import directories that will get added when processing sources.
+#
+# testonly (optional)
+#
+# visibility (optional)
+#
+# visibility_blink (optional)
+# The value to use for visibility for the blink variant. If unset,
+# |visibility| is used.
+#
+# use_once_callback (optional)
+# If set to true, generated classes will use base::OnceCallback instead of
+# base::RepeatingCallback.
+# Default value is false.
+# TODO(dcheng):
+# - Convert everything to use OnceCallback.
+# - Remove support for the old mode.
+#
+# cpp_only (optional)
+# If set to true, only the C++ bindings targets will be generated.
+#
+# use_new_js_bindings (optional)
+# If set to true, the generated JS code will use the new module loading
+# approach and the core API exposed by Web IDL.
+#
+# TODO(yzshen): Switch all existing users to use_new_js_bindings=true and
+# remove the old mode.
+#
+# The following parameters are used to support the component build. They are
+# needed so that bindings which are linked with a component can use the same
+# export settings for classes. The first three are for the chromium variant, and
+# the last three are for the blink variant.
+# export_class_attribute (optional)
+# The attribute to add to the class declaration. e.g. "CONTENT_EXPORT"
+# export_define (optional)
+# A define to be added to the source_set which is needed by the export
+# header. e.g. "CONTENT_IMPLEMENTATION=1"
+# export_header (optional)
+# A header to be added to the generated bindings to support the component
+# build. e.g. "content/common/content_export.h"
+# export_class_attribute_blink (optional)
+# export_define_blink (optional)
+# export_header_blink (optional)
+# These three parameters are the blink variants of the previous 3.
+#
+# The following parameters are used to correct component build dependencies.
+# They are needed so mojom-mojom dependencies follow the rule that dependencies
+# on a source set in another component are replaced by a dependency on the
+# containing component. The first two are for the chromium variant; the other
+# two are for the blink variant.
+# overridden_deps (optional)
+# The list of mojom deps to be overridden.
+# component_deps (optional)
+# The list of component deps to add to replace overridden_deps.
+# overridden_deps_blink (optional)
+# component_deps_blink (optional)
+# These two parameters are the blink variants of the previous two.
+template("mojom") {
+ assert(
+ defined(invoker.sources) || defined(invoker.deps) ||
+ defined(invoker.public_deps),
+ "\"sources\" or \"deps\" must be defined for the $target_name template.")
+ if (defined(invoker.export_class_attribute) ||
+ defined(invoker.export_define) || defined(invoker.export_header)) {
+ assert(defined(invoker.export_class_attribute))
+ assert(defined(invoker.export_define))
+ assert(defined(invoker.export_header))
+ }
+ if (defined(invoker.export_class_attribute_blink) ||
+ defined(invoker.export_define_blink) ||
+ defined(invoker.export_header_blink)) {
+ assert(defined(invoker.export_class_attribute_blink))
+ assert(defined(invoker.export_define_blink))
+ assert(defined(invoker.export_header_blink))
+ }
+ if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) {
+ assert(defined(invoker.overridden_deps))
+ assert(defined(invoker.component_deps))
+ }
+
+ if (defined(invoker.overridden_deps_blink) ||
+ defined(invoker.component_deps_blink)) {
+ assert(defined(invoker.overridden_deps_blink))
+ assert(defined(invoker.component_deps_blink))
+ }
+
+ all_deps = []
+ if (defined(invoker.deps)) {
+ all_deps += invoker.deps
+ }
+ if (defined(invoker.public_deps)) {
+ all_deps += invoker.public_deps
+ }
+
+ group("${target_name}__is_mojom") {
+ }
+
+ # Explicitly ensure that all dependencies (invoker.deps and
+ # invoker.public_deps) are mojom targets.
+ group("${target_name}__check_deps_are_all_mojom") {
+ deps = []
+ foreach(d, all_deps) {
+ name = get_label_info(d, "label_no_toolchain")
+ toolchain = get_label_info(d, "toolchain")
+ deps += [ "${name}__is_mojom(${toolchain})" ]
+ }
+ }
+
+ # Generate code that is shared by different variants.
+ if (defined(invoker.sources)) {
+ common_generator_args = [
+ "--use_bundled_pylibs",
+ "generate",
+ "{{source}}",
+ "-d",
+ rebase_path("//", root_build_dir),
+ "-I",
+ rebase_path("//", root_build_dir),
+ "-o",
+ rebase_path(root_gen_dir),
+ "--bytecode_path",
+ rebase_path("$root_gen_dir/mojo/public/tools/bindings"),
+ ]
+
+ if (defined(invoker.import_dirs)) {
+ foreach(import_dir, invoker.import_dirs) {
+ common_generator_args += [
+ "-I",
+ rebase_path(import_dir, root_build_dir),
+ ]
+ }
+ }
+
+ generator_shared_cpp_outputs = [
+ "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.cc",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.h",
+ ]
+ generator_shared_target_name = "${target_name}_shared__generator"
+ action_foreach(generator_shared_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources
+ sources = invoker.sources
+ deps = [
+ "//mojo/public/tools/bindings:precompile_templates",
+ ]
+ outputs = generator_shared_cpp_outputs
+ args = common_generator_args
+ args += [
+ "--generate_non_variant_code",
+ "-g",
+ "c++",
+ ]
+ depfile = "{{source_gen_dir}}/${generator_shared_target_name}_{{source_name_part}}.d"
+ args += [
+ "--depfile",
+ depfile,
+ "--depfile_target",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
+ ]
+ }
+ }
+
+ shared_cpp_sources_suffix = "shared_cpp_sources"
+ shared_cpp_sources_target_name = "${target_name}_${shared_cpp_sources_suffix}"
+ source_set(shared_cpp_sources_target_name) {
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ deps = []
+ if (defined(invoker.sources)) {
+ sources =
+ process_file_template(invoker.sources, generator_shared_cpp_outputs)
+ deps += [ ":$generator_shared_target_name" ]
+ }
+ public_deps = []
+ foreach(d, all_deps) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append shared_cpp_sources_suffix
+ # to get the cpp dependency name.
+ full_name = get_label_info("$d", "label_no_toolchain")
+ public_deps += [ "${full_name}_${shared_cpp_sources_suffix}" ]
+ }
+ }
+
+ # Generate code for variants.
+ foreach(bindings_configuration, _bindings_configurations) {
+ cpp_only = false
+ if (defined(invoker.cpp_only)) {
+ cpp_only = invoker.cpp_only
+ }
+ variant_suffix = ""
+ if (defined(bindings_configuration.variant)) {
+ variant = bindings_configuration.variant
+ variant_suffix = "_${variant}"
+ cpp_only = true
+ }
+ type_mappings_target_name = "${target_name}${variant_suffix}__type_mappings"
+ type_mappings_path =
+ "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
+ active_typemaps = []
+ enabled_sources = []
+ if (defined(invoker.sources)) {
+ generator_cpp_outputs = []
+ generator_js_outputs = []
+ generator_java_outputs = []
+ variant_dash_suffix = ""
+ if (defined(variant)) {
+ variant_dash_suffix = "-${variant}"
+ }
+ generator_cpp_outputs += [
+ "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.h",
+ ]
+ enabled_sources = []
+ if (defined(bindings_configuration.blacklist)) {
+ foreach(source, invoker.sources) {
+ blacklisted = false
+ foreach(blacklisted_source, bindings_configuration.blacklist) {
+ if (get_path_info(source, "abspath") == blacklisted_source) {
+ blacklisted = true
+ }
+ }
+ if (!blacklisted) {
+ enabled_sources += [ source ]
+ }
+ }
+ } else {
+ enabled_sources = invoker.sources
+ }
+ foreach(source, enabled_sources) {
+ # TODO(sammc): Use a map instead of a linear scan when GN supports maps.
+ foreach(typemap, bindings_configuration.typemaps) {
+ _typemap_config = {
+ }
+ _typemap_config = typemap.config
+ if (get_path_info(source, "abspath") == _typemap_config.mojom) {
+ active_typemaps += [ typemap ]
+ }
+ }
+ if (is_mac && defined(bindings_configuration.typemaps_mac)) {
+ foreach(typemap, bindings_configuration.typemaps_mac) {
+ _typemap_config = {
+ }
+ _typemap_config = typemap.config
+ if (get_path_info(source, "abspath") == _typemap_config.mojom) {
+ active_typemaps += [ typemap ]
+ }
+ }
+ }
+ }
+
+ if (!cpp_only) {
+ generator_js_outputs =
+ [ "{{source_gen_dir}}/{{source_name_part}}.mojom.js" ]
+ generator_java_outputs =
+ [ "{{source_gen_dir}}/{{source_name_part}}.mojom.srcjar" ]
+ }
+ generator_target_name = "${target_name}${variant_suffix}__generator"
+ action_foreach(generator_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources
+ sources = invoker.sources
+ deps = [
+ ":$type_mappings_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ]
+ outputs = generator_cpp_outputs + generator_java_outputs +
+ generator_js_outputs
+ args = common_generator_args
+
+ if (cpp_only) {
+ args += [
+ "-g",
+ "c++",
+ ]
+ } else {
+ args += [
+ "-g",
+ "c++,javascript,java",
+ ]
+ }
+
+ if (defined(bindings_configuration.variant)) {
+ args += [
+ "--variant",
+ bindings_configuration.variant,
+ ]
+ }
+ depfile =
+ "{{source_gen_dir}}/${generator_target_name}_{{source_name_part}}.d"
+ args += [
+ "--depfile",
+ depfile,
+ "--depfile_target",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc",
+ ]
+
+ args += [
+ "--typemap",
+ rebase_path(type_mappings_path, root_build_dir),
+ ]
+
+ if (defined(bindings_configuration.for_blink) &&
+ bindings_configuration.for_blink) {
+ args += [ "--for_blink" ]
+ if (defined(invoker.export_class_attribute_blink)) {
+ args += [
+ "--export_attribute",
+ invoker.export_class_attribute_blink,
+ "--export_header",
+ invoker.export_header_blink,
+ ]
+ }
+ } else {
+ if (defined(invoker.export_class_attribute)) {
+ args += [
+ "--export_attribute",
+ invoker.export_class_attribute,
+ "--export_header",
+ invoker.export_header,
+ ]
+ }
+ }
+
+ if (defined(invoker.use_once_callback) && invoker.use_once_callback) {
+ args += [ "--use_once_callback" ]
+ }
+
+ if (defined(invoker.use_new_js_bindings) &&
+ invoker.use_new_js_bindings) {
+ args += [ "--use_new_js_bindings" ]
+ }
+ }
+ }
+
+ action(type_mappings_target_name) {
+ inputs = _bindings_configuration_files
+ outputs = [
+ type_mappings_path,
+ ]
+ script = "$mojom_generator_root/generate_type_mappings.py"
+ deps = []
+ args = [
+ "--output",
+ rebase_path(type_mappings_path, root_build_dir),
+ ]
+
+ foreach(d, all_deps) {
+ name = get_label_info(d, "label_no_toolchain")
+ toolchain = get_label_info(d, "toolchain")
+ dependency_output = "${name}${variant_suffix}__type_mappings"
+ dependency_target = "${dependency_output}(${toolchain})"
+ deps += [ dependency_target ]
+ dependency_output_dir =
+ get_label_info(dependency_output, "target_gen_dir")
+ dependency_name = get_label_info(dependency_output, "name")
+ dependency_path =
+ rebase_path("$dependency_output_dir/${dependency_name}",
+ root_build_dir)
+ args += [
+ "--dependency",
+ dependency_path,
+ ]
+ }
+
+ if (enabled_sources != []) {
+ # TODO(sammc): Pass the typemap description in a file to avoid command
+ # line length limitations.
+ typemap_description = []
+ foreach(typemap, active_typemaps) {
+ _typemap_config = {
+ }
+ _typemap_config = typemap.config
+ typemap_description += [ "--start-typemap" ]
+ if (defined(_typemap_config.public_headers)) {
+ foreach(value, _typemap_config.public_headers) {
+ typemap_description += [ "public_headers=$value" ]
+ }
+ }
+ if (defined(_typemap_config.traits_headers)) {
+ foreach(value, _typemap_config.traits_headers) {
+ typemap_description += [ "traits_headers=$value" ]
+ }
+ }
+ foreach(value, _typemap_config.type_mappings) {
+ typemap_description += [ "type_mappings=$value" ]
+ }
+
+ # The typemap configuration files are not actually used as inputs here
+ # but this establishes a necessary build dependency to ensure that
+ # typemap changes force a rebuild of affected targets.
+ inputs += [ typemap.filename ]
+ }
+ args += typemap_description
+ }
+ }
+
+ source_set("${target_name}${variant_suffix}") {
+ if (defined(bindings_configuration.for_blink) &&
+ bindings_configuration.for_blink &&
+ defined(invoker.visibility_blink)) {
+ visibility = invoker.visibility_blink
+ } else if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.sources) && !defined(bindings_configuration.variant)) {
+ data = process_file_template(enabled_sources, generator_js_outputs)
+ }
+ defines = []
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.export_define)) {
+ defines += [ invoker.export_define ]
+ }
+ if (defined(invoker.export_define_blink)) {
+ defines += [ invoker.export_define_blink ]
+ }
+ if (enabled_sources != []) {
+ sources = process_file_template(enabled_sources, generator_cpp_outputs)
+ }
+ deps = [
+ "//mojo/public/cpp/bindings:struct_traits",
+ "//mojo/public/interfaces/bindings:bindings__generator",
+ "//mojo/public/interfaces/bindings:bindings_shared__generator",
+ ]
+ public_deps = [
+ ":$shared_cpp_sources_target_name",
+ "//base",
+ "//mojo/public/cpp/bindings",
+ ]
+ if (enabled_sources != []) {
+ public_deps += [ ":$generator_target_name" ]
+ }
+ foreach(d, all_deps) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append variant_suffix to
+ # get the cpp dependency name.
+ full_name = get_label_info("$d", "label_no_toolchain")
+ public_deps += [ "${full_name}${variant_suffix}" ]
+ }
+ if (defined(bindings_configuration.for_blink) &&
+ bindings_configuration.for_blink) {
+ if (defined(invoker.overridden_deps_blink)) {
+ foreach(d, invoker.overridden_deps_blink) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append variant_suffix
+ # to get the cpp dependency name.
+ full_name = get_label_info("$d", "label_no_toolchain")
+ public_deps -= [ "${full_name}${variant_suffix}" ]
+ }
+ public_deps += invoker.component_deps_blink
+ }
+ } else {
+ if (defined(invoker.overridden_deps)) {
+ foreach(d, invoker.overridden_deps) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append variant_suffix
+ # to get the cpp dependency name.
+ full_name = get_label_info("$d", "label_no_toolchain")
+ public_deps -= [ "${full_name}${variant_suffix}" ]
+ }
+ public_deps += invoker.component_deps
+ }
+ }
+ foreach(typemap, active_typemaps) {
+ _typemap_config = {
+ }
+ _typemap_config = typemap.config
+ if (defined(_typemap_config.public_headers)) {
+ sources += _typemap_config.public_headers
+ }
+ if (defined(_typemap_config.traits_headers)) {
+ sources += _typemap_config.traits_headers
+ }
+ if (defined(_typemap_config.sources)) {
+ sources += _typemap_config.sources
+ }
+ if (defined(_typemap_config.public_deps)) {
+ public_deps += _typemap_config.public_deps
+ }
+ if (defined(_typemap_config.deps)) {
+ deps += _typemap_config.deps
+ }
+ }
+ if (defined(bindings_configuration.for_blink) &&
+ bindings_configuration.for_blink) {
+ public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ]
+ }
+ }
+
+ if (!cpp_only && is_android) {
+ import("//build/config/android/rules.gni")
+
+ java_srcjar_target_name = target_name + "_java_sources"
+ action(java_srcjar_target_name) {
+ script = "//mojo/public/tools/gn/zip.py"
+ inputs = []
+ if (enabled_sources != []) {
+ inputs =
+ process_file_template(enabled_sources, generator_java_outputs)
+ }
+ output = "$target_gen_dir/$target_name.srcjar"
+ outputs = [
+ output,
+ ]
+ rebase_inputs = rebase_path(inputs, root_build_dir)
+ rebase_output = rebase_path(output, root_build_dir)
+ args = [
+ "--zip-inputs=$rebase_inputs",
+ "--output=$rebase_output",
+ ]
+ deps = []
+ if (enabled_sources != []) {
+ deps = [
+ ":$generator_target_name",
+ ]
+ }
+ }
+
+ java_target_name = target_name + "_java"
+ android_library(java_target_name) {
+ deps = [
+ "//base:base_java",
+ "//mojo/public/java:bindings_java",
+ "//mojo/public/java:system_java",
+ ]
+
+ foreach(d, all_deps) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append "_java" to get the java
+ # dependency name.
+ full_name = get_label_info(d, "label_no_toolchain")
+ deps += [ "${full_name}_java" ]
+ }
+
+ srcjar_deps = [ ":$java_srcjar_target_name" ]
+ run_findbugs_override = false
+ }
+ }
+ }
+}
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
new file mode 100755
index 0000000000..a9650d7764
--- /dev/null
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -0,0 +1,336 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""The frontend for the Mojo bindings system."""
+
+
+import argparse
+import imp
+import json
+import os
+import pprint
+import re
+import sys
+
+# Disable lint check for finding modules:
+# pylint: disable=F0401
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+# Manually check for the command-line flag. (This isn't quite right, since it
+# ignores, e.g., "--", but it's close enough.)
+if "--use_bundled_pylibs" in sys.argv[1:]:
+ sys.path.insert(0, os.path.join(_GetDirAbove("mojo"), "third_party"))
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "pylib"))
+
+from mojom.error import Error
+import mojom.fileutil as fileutil
+from mojom.generate import translate
+from mojom.generate import template_expander
+from mojom.parse.parser import Parse
+
+
+_BUILTIN_GENERATORS = {
+ "c++": "mojom_cpp_generator.py",
+ "javascript": "mojom_js_generator.py",
+ "java": "mojom_java_generator.py",
+}
+
+
+def LoadGenerators(generators_string):
+ if not generators_string:
+ return [] # No generators.
+
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ generators = {}
+ for generator_name in [s.strip() for s in generators_string.split(",")]:
+ language = generator_name.lower()
+ if language in _BUILTIN_GENERATORS:
+ generator_name = os.path.join(script_dir, "generators",
+ _BUILTIN_GENERATORS[language])
+ else:
+ print "Unknown generator name %s" % generator_name
+ sys.exit(1)
+ generator_module = imp.load_source(os.path.basename(generator_name)[:-3],
+ generator_name)
+ generators[language] = generator_module
+ return generators
+
+
+def MakeImportStackMessage(imported_filename_stack):
+ """Make a (human-readable) message listing a chain of imports. (Returned
+ string begins with a newline (if nonempty) and does not end with one.)"""
+ return ''.join(
+ reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \
+ zip(imported_filename_stack[1:], imported_filename_stack)]))
+
+
+class RelativePath(object):
+ """Represents a path relative to the source tree."""
+ def __init__(self, path, source_root):
+ self.path = path
+ self.source_root = source_root
+
+ def relative_path(self):
+ return os.path.relpath(os.path.abspath(self.path),
+ os.path.abspath(self.source_root))
+
+
+def FindImportFile(rel_dir, file_name, search_rel_dirs):
+ """Finds |file_name| in either |rel_dir| or |search_rel_dirs|. Returns a
+ RelativePath with first file found, or an arbitrary non-existent file
+ otherwise."""
+ for rel_search_dir in [rel_dir] + search_rel_dirs:
+ path = os.path.join(rel_search_dir.path, file_name)
+ if os.path.isfile(path):
+ return RelativePath(path, rel_search_dir.source_root)
+ return RelativePath(os.path.join(rel_dir.path, file_name),
+ rel_dir.source_root)
+
+
+class MojomProcessor(object):
+ """Parses mojom files and creates ASTs for them.
+
+ Attributes:
+ _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from
+ relative mojom filename paths to the module AST for that mojom file.
+ """
+ def __init__(self, should_generate):
+ self._should_generate = should_generate
+ self._processed_files = {}
+ self._parsed_files = {}
+ self._typemap = {}
+
+ def LoadTypemaps(self, typemaps):
+ # Support some very simple single-line comments in typemap JSON.
+ comment_expr = r"^\s*//.*$"
+ def no_comments(line):
+ return not re.match(comment_expr, line)
+ for filename in typemaps:
+ with open(filename) as f:
+ typemaps = json.loads("".join(filter(no_comments, f.readlines())))
+ for language, typemap in typemaps.iteritems():
+ language_map = self._typemap.get(language, {})
+ language_map.update(typemap)
+ self._typemap[language] = language_map
+
+ def ProcessFile(self, args, remaining_args, generator_modules, filename):
+ self._ParseFileAndImports(RelativePath(filename, args.depth),
+ args.import_directories, [])
+
+ return self._GenerateModule(args, remaining_args, generator_modules,
+ RelativePath(filename, args.depth))
+
+ def _GenerateModule(self, args, remaining_args, generator_modules,
+ rel_filename):
+ # Return the already-generated module.
+ if rel_filename.path in self._processed_files:
+ return self._processed_files[rel_filename.path]
+ tree = self._parsed_files[rel_filename.path]
+
+ dirname, name = os.path.split(rel_filename.path)
+
+ # Process all our imports first and collect the module object for each.
+ # We use these to generate proper type info.
+ imports = {}
+ for parsed_imp in tree.import_list:
+ rel_import_file = FindImportFile(
+ RelativePath(dirname, rel_filename.source_root),
+ parsed_imp.import_filename, args.import_directories)
+ imports[parsed_imp.import_filename] = self._GenerateModule(
+ args, remaining_args, generator_modules, rel_import_file)
+
+ module = translate.OrderedModule(tree, name, imports)
+
+ # Set the path as relative to the source root.
+ module.path = rel_filename.relative_path()
+
+ # Normalize to unix-style path here to keep the generators simpler.
+ module.path = module.path.replace('\\', '/')
+
+ if self._should_generate(rel_filename.path):
+ for language, generator_module in generator_modules.iteritems():
+ generator = generator_module.Generator(
+ module, args.output_dir, typemap=self._typemap.get(language, {}),
+ variant=args.variant, bytecode_path=args.bytecode_path,
+ for_blink=args.for_blink,
+ use_once_callback=args.use_once_callback,
+ use_new_js_bindings=args.use_new_js_bindings,
+ export_attribute=args.export_attribute,
+ export_header=args.export_header,
+ generate_non_variant_code=args.generate_non_variant_code)
+ filtered_args = []
+ if hasattr(generator_module, 'GENERATOR_PREFIX'):
+ prefix = '--' + generator_module.GENERATOR_PREFIX + '_'
+ filtered_args = [arg for arg in remaining_args
+ if arg.startswith(prefix)]
+ generator.GenerateFiles(filtered_args)
+
+ # Save result.
+ self._processed_files[rel_filename.path] = module
+ return module
+
+ def _ParseFileAndImports(self, rel_filename, import_directories,
+ imported_filename_stack):
+ # Ignore already-parsed files.
+ if rel_filename.path in self._parsed_files:
+ return
+
+ if rel_filename.path in imported_filename_stack:
+ print "%s: Error: Circular dependency" % rel_filename.path + \
+ MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
+ sys.exit(1)
+
+ try:
+ with open(rel_filename.path) as f:
+ source = f.read()
+ except IOError as e:
+ print "%s: Error: %s" % (rel_filename.path, e.strerror) + \
+ MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
+ sys.exit(1)
+
+ try:
+ tree = Parse(source, rel_filename.path)
+ except Error as e:
+ full_stack = imported_filename_stack + [rel_filename.path]
+ print str(e) + MakeImportStackMessage(full_stack)
+ sys.exit(1)
+
+ dirname = os.path.split(rel_filename.path)[0]
+ for imp_entry in tree.import_list:
+ import_file_entry = FindImportFile(
+ RelativePath(dirname, rel_filename.source_root),
+ imp_entry.import_filename, import_directories)
+ self._ParseFileAndImports(import_file_entry, import_directories,
+ imported_filename_stack + [rel_filename.path])
+
+ self._parsed_files[rel_filename.path] = tree
+
+
+def _Generate(args, remaining_args):
+ if args.variant == "none":
+ args.variant = None
+
+ for idx, import_dir in enumerate(args.import_directories):
+ tokens = import_dir.split(":")
+ if len(tokens) >= 2:
+ args.import_directories[idx] = RelativePath(tokens[0], tokens[1])
+ else:
+ args.import_directories[idx] = RelativePath(tokens[0], args.depth)
+ generator_modules = LoadGenerators(args.generators_string)
+
+ fileutil.EnsureDirectoryExists(args.output_dir)
+
+ processor = MojomProcessor(lambda filename: filename in args.filename)
+ processor.LoadTypemaps(set(args.typemaps))
+ for filename in args.filename:
+ processor.ProcessFile(args, remaining_args, generator_modules, filename)
+ if args.depfile:
+ assert args.depfile_target
+ with open(args.depfile, 'w') as f:
+ f.write('%s: %s' % (
+ args.depfile_target,
+ ' '.join(processor._parsed_files.keys())))
+
+ return 0
+
+
+def _Precompile(args, _):
+ generator_modules = LoadGenerators(",".join(_BUILTIN_GENERATORS.keys()))
+
+ template_expander.PrecompileTemplates(generator_modules, args.output_dir)
+ return 0
+
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Generate bindings from mojom files.")
+ parser.add_argument("--use_bundled_pylibs", action="store_true",
+ help="use Python modules bundled in the SDK")
+
+ subparsers = parser.add_subparsers()
+ generate_parser = subparsers.add_parser(
+ "generate", description="Generate bindings from mojom files.")
+ generate_parser.add_argument("filename", nargs="+",
+ help="mojom input file")
+ generate_parser.add_argument("-d", "--depth", dest="depth", default=".",
+ help="depth from source root")
+ generate_parser.add_argument("-o", "--output_dir", dest="output_dir",
+ default=".",
+ help="output directory for generated files")
+ generate_parser.add_argument("-g", "--generators",
+ dest="generators_string",
+ metavar="GENERATORS",
+ default="c++,javascript,java",
+ help="comma-separated list of generators")
+ generate_parser.add_argument(
+ "-I", dest="import_directories", action="append", metavar="directory",
+ default=[],
+ help="add a directory to be searched for import files. The depth from "
+ "source root can be specified for each import by appending it after "
+ "a colon")
+ generate_parser.add_argument("--typemap", action="append", metavar="TYPEMAP",
+ default=[], dest="typemaps",
+ help="apply TYPEMAP to generated output")
+ generate_parser.add_argument("--variant", dest="variant", default=None,
+ help="output a named variant of the bindings")
+ generate_parser.add_argument(
+ "--bytecode_path", type=str, required=True, help=(
+ "the path from which to load template bytecode; to generate template "
+ "bytecode, run %s precompile BYTECODE_PATH" % os.path.basename(
+ sys.argv[0])))
+ generate_parser.add_argument("--for_blink", action="store_true",
+ help="Use WTF types as generated types for mojo "
+ "string/array/map.")
+ generate_parser.add_argument(
+ "--use_once_callback", action="store_true",
+ help="Use base::OnceCallback instead of base::RepeatingCallback.")
+ generate_parser.add_argument(
+ "--use_new_js_bindings", action="store_true",
+ help="Use the new module loading approach and the core API exposed by "
+ "Web IDL. This option only affects the JavaScript bindings.")
+ generate_parser.add_argument(
+ "--export_attribute", type=str, default="",
+ help="Optional attribute to specify on class declaration to export it "
+ "for the component build.")
+ generate_parser.add_argument(
+ "--export_header", type=str, default="",
+ help="Optional header to include in the generated headers to support the "
+ "component build.")
+ generate_parser.add_argument(
+ "--generate_non_variant_code", action="store_true",
+ help="Generate code that is shared by different variants.")
+ generate_parser.add_argument(
+ "--depfile", type=str,
+ help="A file into which the list of input files will be written.")
+ generate_parser.add_argument(
+ "--depfile_target", type=str,
+ help="The target name to use in the depfile.")
+ generate_parser.set_defaults(func=_Generate)
+
+ precompile_parser = subparsers.add_parser("precompile",
+ description="Precompile templates for the mojom bindings generator.")
+ precompile_parser.add_argument(
+ "-o", "--output_dir", dest="output_dir", default=".",
+ help="output directory for precompiled templates")
+ precompile_parser.set_defaults(func=_Precompile)
+
+ args, remaining_args = parser.parse_known_args()
+ return args.func(args, remaining_args)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
new file mode 100644
index 0000000000..de388561cb
--- /dev/null
+++ b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
@@ -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.
+
+import unittest
+
+from mojom_bindings_generator import MakeImportStackMessage
+
+
+class MojoBindingsGeneratorTest(unittest.TestCase):
+ """Tests mojo_bindings_generator."""
+
+ def testMakeImportStackMessage(self):
+ """Tests MakeImportStackMessage()."""
+ self.assertEquals(MakeImportStackMessage(["x"]), "")
+ self.assertEquals(MakeImportStackMessage(["x", "y"]),
+ "\n y was imported by x")
+ self.assertEquals(MakeImportStackMessage(["x", "y", "z"]),
+ "\n z was imported by y\n y was imported by x")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom/__init__.py b/mojo/public/tools/bindings/pylib/mojom/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom/error.py b/mojo/public/tools/bindings/pylib/mojom/error.py
new file mode 100644
index 0000000000..99522b9507
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/error.py
@@ -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.
+
+class Error(Exception):
+ """Base class for Mojo IDL bindings parser/generator errors."""
+
+ def __init__(self, filename, message, lineno=None, addenda=None, **kwargs):
+ """|filename| is the (primary) file which caused the error, |message| is the
+ error message, |lineno| is the 1-based line number (or |None| if not
+ applicable/available), and |addenda| is a list of additional lines to append
+ to the final error message."""
+ Exception.__init__(self, **kwargs)
+ self.filename = filename
+ self.message = message
+ self.lineno = lineno
+ self.addenda = addenda
+
+ def __str__(self):
+ if self.lineno:
+ s = "%s:%d: Error: %s" % (self.filename, self.lineno, self.message)
+ else:
+ s = "%s: Error: %s" % (self.filename, self.message)
+ return "\n".join([s] + self.addenda) if self.addenda else s
+
+ def __repr__(self):
+ return str(self)
diff --git a/mojo/public/tools/bindings/pylib/mojom/fileutil.py b/mojo/public/tools/bindings/pylib/mojom/fileutil.py
new file mode 100644
index 0000000000..b321e9f543
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/fileutil.py
@@ -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.
+
+import errno
+import os.path
+
+def EnsureDirectoryExists(path, always_try_to_create=False):
+ """A wrapper for os.makedirs that does not error if the directory already
+ exists. A different process could be racing to create this directory."""
+
+ if not os.path.exists(path) or always_try_to_create:
+ try:
+ os.makedirs(path)
+ except OSError as e:
+ # There may have been a race to create this directory.
+ if e.errno != errno.EEXIST:
+ raise
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py b/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py
new file mode 100644
index 0000000000..c8b21f2629
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py
@@ -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.
+
+"""Resolves the values used for constants and enums."""
+
+from itertools import ifilter
+import mojom.generate.module as mojom
+
+def ResolveConstants(module, expression_to_text):
+ in_progress = set()
+ computed = set()
+
+ def GetResolvedValue(named_value):
+ assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))
+ if isinstance(named_value, mojom.EnumValue):
+ field = next(ifilter(lambda field: field.name == named_value.name,
+ named_value.enum.fields), None)
+ if not field:
+ raise RuntimeError(
+ 'Unable to get computed value for field %s of enum %s' %
+ (named_value.name, named_value.enum.name))
+ if field not in computed:
+ ResolveEnum(named_value.enum)
+ return field.resolved_value
+ else:
+ ResolveConstant(named_value.constant)
+ named_value.resolved_value = named_value.constant.resolved_value
+ return named_value.resolved_value
+
+ def ResolveConstant(constant):
+ if constant in computed:
+ return
+ if constant in in_progress:
+ raise RuntimeError('Circular dependency for constant: %s' % constant.name)
+ in_progress.add(constant)
+ if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
+ resolved_value = GetResolvedValue(constant.value)
+ else:
+ resolved_value = expression_to_text(constant.value)
+ constant.resolved_value = resolved_value
+ in_progress.remove(constant)
+ computed.add(constant)
+
+ def ResolveEnum(enum):
+ def ResolveEnumField(enum, field, default_value):
+ if field in computed:
+ return
+ if field in in_progress:
+ raise RuntimeError('Circular dependency for enum: %s' % enum.name)
+ in_progress.add(field)
+ if field.value:
+ if isinstance(field.value, mojom.EnumValue):
+ resolved_value = GetResolvedValue(field.value)
+ elif isinstance(field.value, str):
+ resolved_value = int(field.value, 0)
+ else:
+ raise RuntimeError('Unexpected value: %s' % field.value)
+ else:
+ resolved_value = default_value
+ field.resolved_value = resolved_value
+ in_progress.remove(field)
+ computed.add(field)
+
+ current_value = 0
+ for field in enum.fields:
+ ResolveEnumField(enum, field, current_value)
+ current_value = field.resolved_value + 1
+
+ for constant in module.constants:
+ ResolveConstant(constant)
+
+ for enum in module.enums:
+ ResolveEnum(enum)
+
+ for struct in module.structs:
+ for constant in struct.constants:
+ ResolveConstant(constant)
+ for enum in struct.enums:
+ ResolveEnum(enum)
+ for field in struct.fields:
+ if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
+ field.default.resolved_value = GetResolvedValue(field.default)
+
+ for interface in module.interfaces:
+ for constant in interface.constants:
+ ResolveConstant(constant)
+ for enum in interface.enums:
+ ResolveEnum(enum)
+
+ return module
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
new file mode 100644
index 0000000000..0e64af78a1
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
@@ -0,0 +1,153 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Code shared by the various language-specific code generators."""
+
+from functools import partial
+import os.path
+import re
+
+import module as mojom
+import mojom.fileutil as fileutil
+import pack
+
+def ExpectedArraySize(kind):
+ if mojom.IsArrayKind(kind):
+ return kind.length
+ return None
+
+def StudlyCapsToCamel(studly):
+ return studly[0].lower() + studly[1:]
+
+def UnderToCamel(under):
+ """Converts underscore_separated strings to CamelCase strings."""
+ return ''.join(word.capitalize() for word in under.split('_'))
+
+def WriteFile(contents, full_path):
+ # Make sure the containing directory exists.
+ full_dir = os.path.dirname(full_path)
+ fileutil.EnsureDirectoryExists(full_dir)
+
+ # Dump the data to disk.
+ with open(full_path, "w+") as f:
+ f.write(contents)
+
+class Generator(object):
+ # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
+ # files to stdout.
+ def __init__(self, module, output_dir=None, typemap=None, variant=None,
+ bytecode_path=None, for_blink=False, use_once_callback=False,
+ use_new_js_bindings=False, export_attribute=None,
+ export_header=None, generate_non_variant_code=False):
+ self.module = module
+ self.output_dir = output_dir
+ self.typemap = typemap or {}
+ self.variant = variant
+ self.bytecode_path = bytecode_path
+ self.for_blink = for_blink
+ self.use_once_callback = use_once_callback
+ self.use_new_js_bindings = use_new_js_bindings
+ self.export_attribute = export_attribute
+ self.export_header = export_header
+ self.generate_non_variant_code = generate_non_variant_code
+
+ def GetStructsFromMethods(self):
+ result = []
+ for interface in self.module.interfaces:
+ for method in interface.methods:
+ result.append(self._GetStructFromMethod(method))
+ if method.response_parameters != None:
+ result.append(self._GetResponseStructFromMethod(method))
+ return result
+
+ def GetStructs(self):
+ return map(partial(self._AddStructComputedData, True), self.module.structs)
+
+ def GetUnions(self):
+ return map(self._AddUnionComputedData, self.module.unions)
+
+ def GetInterfaces(self):
+ return map(self._AddInterfaceComputedData, self.module.interfaces)
+
+ # Prepend the filename with a directory that matches the directory of the
+ # original .mojom file, relative to the import root.
+ def MatchMojomFilePath(self, filename):
+ return os.path.join(os.path.dirname(self.module.path), filename)
+
+ def Write(self, contents, filename):
+ if self.output_dir is None:
+ print contents
+ return
+ full_path = os.path.join(self.output_dir, filename)
+ WriteFile(contents, full_path)
+
+ def GenerateFiles(self, args):
+ raise NotImplementedError("Subclasses must override/implement this method")
+
+ def GetJinjaParameters(self):
+ """Returns default constructor parameters for the jinja environment."""
+ return {}
+
+ def GetGlobals(self):
+ """Returns global mappings for the template generation."""
+ return {}
+
+ def _AddStructComputedData(self, exported, struct):
+ """Adds computed data to the given struct. The data is computed once and
+ used repeatedly in the generation process."""
+ struct.packed = pack.PackedStruct(struct)
+ struct.bytes = pack.GetByteLayout(struct.packed)
+ struct.versions = pack.GetVersionInfo(struct.packed)
+ struct.exported = exported
+ return struct
+
+ def _AddUnionComputedData(self, union):
+ """Adds computed data to the given union. The data is computed once and
+ used repeatedly in the generation process."""
+ ordinal = 0
+ for field in union.fields:
+ if field.ordinal is not None:
+ ordinal = field.ordinal
+ field.ordinal = ordinal
+ ordinal += 1
+ return union
+
+ def _AddInterfaceComputedData(self, interface):
+ """Adds computed data to the given interface. The data is computed once and
+ used repeatedly in the generation process."""
+ interface.version = 0
+ for method in interface.methods:
+ if method.min_version is not None:
+ interface.version = max(interface.version, method.min_version)
+
+ method.param_struct = self._GetStructFromMethod(method)
+ interface.version = max(interface.version,
+ method.param_struct.versions[-1].version)
+
+ if method.response_parameters is not None:
+ method.response_param_struct = self._GetResponseStructFromMethod(method)
+ interface.version = max(
+ interface.version,
+ method.response_param_struct.versions[-1].version)
+ else:
+ method.response_param_struct = None
+ return interface
+
+ def _GetStructFromMethod(self, method):
+ """Converts a method's parameters into the fields of a struct."""
+ params_class = "%s_%s_Params" % (method.interface.name, method.name)
+ struct = mojom.Struct(params_class, module=method.interface.module)
+ for param in method.parameters:
+ struct.AddField(param.name, param.kind, param.ordinal,
+ attributes=param.attributes)
+ return self._AddStructComputedData(False, struct)
+
+ def _GetResponseStructFromMethod(self, method):
+ """Converts a method's response_parameters into the fields of a struct."""
+ params_class = "%s_%s_ResponseParams" % (method.interface.name, method.name)
+ struct = mojom.Struct(params_class, module=method.interface.module)
+ for param in method.response_parameters:
+ struct.AddField(param.name, param.kind, param.ordinal,
+ attributes=param.attributes)
+ return self._AddStructComputedData(False, struct)
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py
new file mode 100644
index 0000000000..9966b0b7f8
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py
@@ -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.
+
+import unittest
+
+import module as mojom
+import generator
+
+class TestGenerator(unittest.TestCase):
+
+ def testGetUnionsAddsOrdinals(self):
+ module = mojom.Module()
+ union = module.AddUnion('a')
+ union.AddField('a', mojom.BOOL)
+ union.AddField('b', mojom.BOOL)
+ union.AddField('c', mojom.BOOL, ordinal=10)
+ union.AddField('d', mojom.BOOL)
+
+ gen = generator.Generator(module)
+ union = gen.GetUnions()[0]
+ ordinals = [field.ordinal for field in union.fields]
+
+ self.assertEquals([0, 1, 10, 11], ordinals)
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
new file mode 100644
index 0000000000..3a5f188e75
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
@@ -0,0 +1,891 @@
+# Copyright 2013 The Chromium 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's classes provide an interface to mojo modules. Modules are
+# collections of interfaces and structs to be used by mojo ipc clients and
+# servers.
+#
+# A simple interface would be created this way:
+# module = mojom.generate.module.Module('Foo')
+# interface = module.AddInterface('Bar')
+# method = interface.AddMethod('Tat', 0)
+# method.AddParameter('baz', 0, mojom.INT32)
+
+
+# We use our own version of __repr__ when displaying the AST, as the
+# AST currently doesn't capture which nodes are reference (e.g. to
+# types) and which nodes are definitions. This allows us to e.g. print
+# the definition of a struct when it's defined inside a module, but
+# only print its name when it's referenced in e.g. a method parameter.
+def Repr(obj, as_ref=True):
+ """A version of __repr__ that can distinguish references.
+
+ Sometimes we like to print an object's full representation
+ (e.g. with its fields) and sometimes we just want to reference an
+ object that was printed in full elsewhere. This function allows us
+ to make that distinction.
+
+ Args:
+ obj: The object whose string representation we compute.
+ as_ref: If True, use the short reference representation.
+
+ Returns:
+ A str representation of |obj|.
+ """
+ if hasattr(obj, 'Repr'):
+ return obj.Repr(as_ref=as_ref)
+ # Since we cannot implement Repr for existing container types, we
+ # handle them here.
+ elif isinstance(obj, list):
+ if not obj:
+ return '[]'
+ else:
+ return ('[\n%s\n]' % (',\n'.join(' %s' % Repr(elem, as_ref).replace(
+ '\n', '\n ') for elem in obj)))
+ elif isinstance(obj, dict):
+ if not obj:
+ return '{}'
+ else:
+ return ('{\n%s\n}' % (',\n'.join(' %s: %s' % (
+ Repr(key, as_ref).replace('\n', '\n '),
+ Repr(val, as_ref).replace('\n', '\n '))
+ for key, val in obj.iteritems())))
+ else:
+ return repr(obj)
+
+
+def GenericRepr(obj, names):
+ """Compute generic Repr for |obj| based on the attributes in |names|.
+
+ Args:
+ obj: The object to compute a Repr for.
+ names: A dict from attribute names to include, to booleans
+ specifying whether those attributes should be shown as
+ references or not.
+
+ Returns:
+ A str representation of |obj|.
+ """
+ def ReprIndent(name, as_ref):
+ return ' %s=%s' % (name, Repr(getattr(obj, name), as_ref).replace(
+ '\n', '\n '))
+
+ return '%s(\n%s\n)' % (
+ obj.__class__.__name__,
+ ',\n'.join(ReprIndent(name, as_ref)
+ for (name, as_ref) in names.iteritems()))
+
+
+class Kind(object):
+ """Kind represents a type (e.g. int8, string).
+
+ Attributes:
+ spec: A string uniquely identifying the type. May be None.
+ parent_kind: The enclosing type. For example, a struct defined
+ inside an interface has that interface as its parent. May be None.
+ """
+ def __init__(self, spec=None):
+ self.spec = spec
+ self.parent_kind = None
+
+ def Repr(self, as_ref=True):
+ return '<%s spec=%r>' % (self.__class__.__name__, self.spec)
+
+ def __repr__(self):
+ # Gives us a decent __repr__ for all kinds.
+ return self.Repr()
+
+
+class ReferenceKind(Kind):
+ """ReferenceKind represents pointer and handle types.
+
+ A type is nullable if null (for pointer types) or invalid handle (for handle
+ types) is a legal value for the type.
+
+ Attributes:
+ is_nullable: True if the type is nullable.
+ """
+
+ def __init__(self, spec=None, is_nullable=False):
+ assert spec is None or is_nullable == spec.startswith('?')
+ Kind.__init__(self, spec)
+ self.is_nullable = is_nullable
+ self.shared_definition = {}
+
+ def Repr(self, as_ref=True):
+ return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
+ self.is_nullable)
+
+ def MakeNullableKind(self):
+ assert not self.is_nullable
+
+ if self == STRING:
+ return NULLABLE_STRING
+ if self == HANDLE:
+ return NULLABLE_HANDLE
+ if self == DCPIPE:
+ return NULLABLE_DCPIPE
+ if self == DPPIPE:
+ return NULLABLE_DPPIPE
+ if self == MSGPIPE:
+ return NULLABLE_MSGPIPE
+ if self == SHAREDBUFFER:
+ return NULLABLE_SHAREDBUFFER
+
+ nullable_kind = type(self)()
+ nullable_kind.shared_definition = self.shared_definition
+ if self.spec is not None:
+ nullable_kind.spec = '?' + self.spec
+ nullable_kind.is_nullable = True
+
+ return nullable_kind
+
+ @classmethod
+ def AddSharedProperty(cls, name):
+ """Adds a property |name| to |cls|, which accesses the corresponding item in
+ |shared_definition|.
+
+ The reason of adding such indirection is to enable sharing definition
+ between a reference kind and its nullable variation. For example:
+ a = Struct('test_struct_1')
+ b = a.MakeNullableKind()
+ a.name = 'test_struct_2'
+ print b.name # Outputs 'test_struct_2'.
+ """
+ def Get(self):
+ return self.shared_definition[name]
+
+ def Set(self, value):
+ self.shared_definition[name] = value
+
+ setattr(cls, name, property(Get, Set))
+
+
+# Initialize the set of primitive types. These can be accessed by clients.
+BOOL = Kind('b')
+INT8 = Kind('i8')
+INT16 = Kind('i16')
+INT32 = Kind('i32')
+INT64 = Kind('i64')
+UINT8 = Kind('u8')
+UINT16 = Kind('u16')
+UINT32 = Kind('u32')
+UINT64 = Kind('u64')
+FLOAT = Kind('f')
+DOUBLE = Kind('d')
+STRING = ReferenceKind('s')
+HANDLE = ReferenceKind('h')
+DCPIPE = ReferenceKind('h:d:c')
+DPPIPE = ReferenceKind('h:d:p')
+MSGPIPE = ReferenceKind('h:m')
+SHAREDBUFFER = ReferenceKind('h:s')
+NULLABLE_STRING = ReferenceKind('?s', True)
+NULLABLE_HANDLE = ReferenceKind('?h', True)
+NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True)
+NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True)
+NULLABLE_MSGPIPE = ReferenceKind('?h:m', True)
+NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True)
+
+
+# Collection of all Primitive types
+PRIMITIVES = (
+ BOOL,
+ INT8,
+ INT16,
+ INT32,
+ INT64,
+ UINT8,
+ UINT16,
+ UINT32,
+ UINT64,
+ FLOAT,
+ DOUBLE,
+ STRING,
+ HANDLE,
+ DCPIPE,
+ DPPIPE,
+ MSGPIPE,
+ SHAREDBUFFER,
+ NULLABLE_STRING,
+ NULLABLE_HANDLE,
+ NULLABLE_DCPIPE,
+ NULLABLE_DPPIPE,
+ NULLABLE_MSGPIPE,
+ NULLABLE_SHAREDBUFFER
+)
+
+
+ATTRIBUTE_MIN_VERSION = 'MinVersion'
+ATTRIBUTE_EXTENSIBLE = 'Extensible'
+ATTRIBUTE_SYNC = 'Sync'
+
+
+class NamedValue(object):
+ def __init__(self, module, parent_kind, name):
+ self.module = module
+ self.namespace = module.namespace
+ self.parent_kind = parent_kind
+ self.name = name
+ self.imported_from = None
+
+ def GetSpec(self):
+ return (self.namespace + '.' +
+ (self.parent_kind and (self.parent_kind.name + '.') or "") +
+ self.name)
+
+
+class BuiltinValue(object):
+ def __init__(self, value):
+ self.value = value
+
+
+class ConstantValue(NamedValue):
+ def __init__(self, module, parent_kind, constant):
+ NamedValue.__init__(self, module, parent_kind, constant.name)
+ self.constant = constant
+
+
+class EnumValue(NamedValue):
+ def __init__(self, module, enum, field):
+ NamedValue.__init__(self, module, enum.parent_kind, field.name)
+ self.enum = enum
+
+ def GetSpec(self):
+ return (self.namespace + '.' +
+ (self.parent_kind and (self.parent_kind.name + '.') or "") +
+ self.enum.name + '.' + self.name)
+
+
+class Constant(object):
+ def __init__(self, name=None, kind=None, value=None, parent_kind=None):
+ self.name = name
+ self.kind = kind
+ self.value = value
+ self.parent_kind = parent_kind
+
+
+class Field(object):
+ def __init__(self, name=None, kind=None, ordinal=None, default=None,
+ attributes=None):
+ if self.__class__.__name__ == 'Field':
+ raise Exception()
+ self.name = name
+ self.kind = kind
+ self.ordinal = ordinal
+ self.default = default
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ # Fields are only referenced by objects which define them and thus
+ # they are always displayed as non-references.
+ return GenericRepr(self, {'name': False, 'kind': True})
+
+ @property
+ def min_version(self):
+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
+ if self.attributes else None
+
+
+class StructField(Field): pass
+
+
+class UnionField(Field): pass
+
+
+class Struct(ReferenceKind):
+ """A struct with typed fields.
+
+ Attributes:
+ name: {str} The name of the struct type.
+ native_only: {bool} Does the struct have a body (i.e. any fields) or is it
+ purely a native struct.
+ module: {Module} The defining module.
+ imported_from: {dict} Information about where this union was
+ imported from.
+ fields: {List[StructField]} The members of the struct.
+ attributes: {dict} Additional information about the struct, such as
+ if it's a native struct.
+ """
+
+ ReferenceKind.AddSharedProperty('name')
+ ReferenceKind.AddSharedProperty('native_only')
+ ReferenceKind.AddSharedProperty('module')
+ ReferenceKind.AddSharedProperty('imported_from')
+ ReferenceKind.AddSharedProperty('fields')
+ ReferenceKind.AddSharedProperty('attributes')
+
+ def __init__(self, name=None, module=None, attributes=None):
+ if name is not None:
+ spec = 'x:' + name
+ else:
+ spec = None
+ ReferenceKind.__init__(self, spec)
+ self.name = name
+ self.native_only = False
+ self.module = module
+ self.imported_from = None
+ self.fields = []
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s name=%r imported_from=%s>' % (
+ self.__class__.__name__, self.name,
+ Repr(self.imported_from, as_ref=True))
+ else:
+ return GenericRepr(self, {'name': False, 'fields': False,
+ 'imported_from': True})
+
+ def AddField(self, name, kind, ordinal=None, default=None, attributes=None):
+ field = StructField(name, kind, ordinal, default, attributes)
+ self.fields.append(field)
+ return field
+
+
+class Union(ReferenceKind):
+ """A union of several kinds.
+
+ Attributes:
+ name: {str} The name of the union type.
+ module: {Module} The defining module.
+ imported_from: {dict} Information about where this union was
+ imported from.
+ fields: {List[UnionField]} The members of the union.
+ attributes: {dict} Additional information about the union, such as
+ which Java class name to use to represent it in the generated
+ bindings.
+ """
+ ReferenceKind.AddSharedProperty('name')
+ ReferenceKind.AddSharedProperty('module')
+ ReferenceKind.AddSharedProperty('imported_from')
+ ReferenceKind.AddSharedProperty('fields')
+ ReferenceKind.AddSharedProperty('attributes')
+
+ def __init__(self, name=None, module=None, attributes=None):
+ if name is not None:
+ spec = 'x:' + name
+ else:
+ spec = None
+ ReferenceKind.__init__(self, spec)
+ self.name = name
+ self.module = module
+ self.imported_from = None
+ self.fields = []
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s spec=%r is_nullable=%r fields=%s>' % (
+ self.__class__.__name__, self.spec, self.is_nullable,
+ Repr(self.fields))
+ else:
+ return GenericRepr(self, {'fields': True, 'is_nullable': False})
+
+ def AddField(self, name, kind, ordinal=None, attributes=None):
+ field = UnionField(name, kind, ordinal, None, attributes)
+ self.fields.append(field)
+ return field
+
+
+class Array(ReferenceKind):
+ """An array.
+
+ Attributes:
+ kind: {Kind} The type of the elements. May be None.
+ length: The number of elements. None if unknown.
+ """
+
+ ReferenceKind.AddSharedProperty('kind')
+ ReferenceKind.AddSharedProperty('length')
+
+ def __init__(self, kind=None, length=None):
+ if kind is not None:
+ if length is not None:
+ spec = 'a%d:%s' % (length, kind.spec)
+ else:
+ spec = 'a:%s' % kind.spec
+
+ ReferenceKind.__init__(self, spec)
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind
+ self.length = length
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (
+ self.__class__.__name__, self.spec, self.is_nullable, Repr(self.kind),
+ self.length)
+ else:
+ return GenericRepr(self, {'kind': True, 'length': False,
+ 'is_nullable': False})
+
+
+class Map(ReferenceKind):
+ """A map.
+
+ Attributes:
+ key_kind: {Kind} The type of the keys. May be None.
+ value_kind: {Kind} The type of the elements. May be None.
+ """
+ ReferenceKind.AddSharedProperty('key_kind')
+ ReferenceKind.AddSharedProperty('value_kind')
+
+ def __init__(self, key_kind=None, value_kind=None):
+ if (key_kind is not None and value_kind is not None):
+ ReferenceKind.__init__(self,
+ 'm[' + key_kind.spec + '][' + value_kind.spec +
+ ']')
+ if IsNullableKind(key_kind):
+ raise Exception("Nullable kinds cannot be keys in maps.")
+ if IsAnyHandleKind(key_kind):
+ raise Exception("Handles cannot be keys in maps.")
+ if IsAnyInterfaceKind(key_kind):
+ raise Exception("Interfaces cannot be keys in maps.")
+ if IsArrayKind(key_kind):
+ raise Exception("Arrays cannot be keys in maps.")
+ else:
+ ReferenceKind.__init__(self)
+
+ self.key_kind = key_kind
+ self.value_kind = value_kind
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (
+ self.__class__.__name__, self.spec, self.is_nullable,
+ Repr(self.key_kind), Repr(self.value_kind))
+ else:
+ return GenericRepr(self, {'key_kind': True, 'value_kind': True})
+
+
+class InterfaceRequest(ReferenceKind):
+ ReferenceKind.AddSharedProperty('kind')
+
+ def __init__(self, kind=None):
+ if kind is not None:
+ if not isinstance(kind, Interface):
+ raise Exception(
+ "Interface request requires %r to be an interface." % kind.spec)
+ ReferenceKind.__init__(self, 'r:' + kind.spec)
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind
+
+
+class AssociatedInterfaceRequest(ReferenceKind):
+ ReferenceKind.AddSharedProperty('kind')
+
+ def __init__(self, kind=None):
+ if kind is not None:
+ if not isinstance(kind, InterfaceRequest):
+ raise Exception(
+ "Associated interface request requires %r to be an interface "
+ "request." % kind.spec)
+ assert not kind.is_nullable
+ ReferenceKind.__init__(self, 'asso:' + kind.spec)
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind.kind if kind is not None else None
+
+
+class Parameter(object):
+ def __init__(self, name=None, kind=None, ordinal=None, default=None,
+ attributes=None):
+ self.name = name
+ self.ordinal = ordinal
+ self.kind = kind
+ self.default = default
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ return '<%s name=%r kind=%s>' % (self.__class__.__name__, self.name,
+ self.kind.Repr(as_ref=True))
+
+ @property
+ def min_version(self):
+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
+ if self.attributes else None
+
+
+class Method(object):
+ def __init__(self, interface, name, ordinal=None, attributes=None):
+ self.interface = interface
+ self.name = name
+ self.ordinal = ordinal
+ self.parameters = []
+ self.response_parameters = None
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s name=%r>' % (self.__class__.__name__, self.name)
+ else:
+ return GenericRepr(self, {'name': False, 'parameters': True,
+ 'response_parameters': True})
+
+ def AddParameter(self, name, kind, ordinal=None, default=None,
+ attributes=None):
+ parameter = Parameter(name, kind, ordinal, default, attributes)
+ self.parameters.append(parameter)
+ return parameter
+
+ def AddResponseParameter(self, name, kind, ordinal=None, default=None,
+ attributes=None):
+ if self.response_parameters == None:
+ self.response_parameters = []
+ parameter = Parameter(name, kind, ordinal, default, attributes)
+ self.response_parameters.append(parameter)
+ return parameter
+
+ @property
+ def min_version(self):
+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
+ if self.attributes else None
+
+ @property
+ def sync(self):
+ return self.attributes.get(ATTRIBUTE_SYNC) \
+ if self.attributes else None
+
+
+class Interface(ReferenceKind):
+ ReferenceKind.AddSharedProperty('module')
+ ReferenceKind.AddSharedProperty('name')
+ ReferenceKind.AddSharedProperty('imported_from')
+ ReferenceKind.AddSharedProperty('methods')
+ ReferenceKind.AddSharedProperty('attributes')
+
+ def __init__(self, name=None, module=None, attributes=None):
+ if name is not None:
+ spec = 'x:' + name
+ else:
+ spec = None
+ ReferenceKind.__init__(self, spec)
+ self.module = module
+ self.name = name
+ self.imported_from = None
+ self.methods = []
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s name=%r>' % (self.__class__.__name__, self.name)
+ else:
+ return GenericRepr(self, {'name': False, 'attributes': False,
+ 'methods': False})
+
+ def AddMethod(self, name, ordinal=None, attributes=None):
+ method = Method(self, name, ordinal, attributes)
+ self.methods.append(method)
+ return method
+
+ # TODO(451323): Remove when the language backends no longer rely on this.
+ @property
+ def client(self):
+ return None
+
+
+class AssociatedInterface(ReferenceKind):
+ ReferenceKind.AddSharedProperty('kind')
+
+ def __init__(self, kind=None):
+ if kind is not None:
+ if not isinstance(kind, Interface):
+ raise Exception(
+ "Associated interface requires %r to be an interface." % kind.spec)
+ assert not kind.is_nullable
+ ReferenceKind.__init__(self, 'asso:' + kind.spec)
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind
+
+
+class EnumField(object):
+ def __init__(self, name=None, value=None, attributes=None,
+ numeric_value=None):
+ self.name = name
+ self.value = value
+ self.attributes = attributes
+ self.numeric_value = numeric_value
+
+ @property
+ def min_version(self):
+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
+ if self.attributes else None
+
+
+class Enum(Kind):
+ def __init__(self, name=None, module=None, attributes=None):
+ self.module = module
+ self.name = name
+ self.native_only = False
+ self.imported_from = None
+ if name is not None:
+ spec = 'x:' + name
+ else:
+ spec = None
+ Kind.__init__(self, spec)
+ self.fields = []
+ self.attributes = attributes
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s name=%r>' % (self.__class__.__name__, self.name)
+ else:
+ return GenericRepr(self, {'name': False, 'fields': False})
+
+ @property
+ def extensible(self):
+ return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \
+ if self.attributes else False
+
+
+class Module(object):
+ def __init__(self, name=None, namespace=None, attributes=None):
+ self.name = name
+ self.path = name
+ self.namespace = namespace
+ self.structs = []
+ self.unions = []
+ self.interfaces = []
+ self.kinds = {}
+ self.attributes = attributes
+
+ def __repr__(self):
+ # Gives us a decent __repr__ for modules.
+ return self.Repr()
+
+ def Repr(self, as_ref=True):
+ if as_ref:
+ return '<%s name=%r namespace=%r>' % (
+ self.__class__.__name__, self.name, self.namespace)
+ else:
+ return GenericRepr(self, {'name': False, 'namespace': False,
+ 'attributes': False, 'structs': False,
+ 'interfaces': False, 'unions': False})
+
+ def AddInterface(self, name, attributes=None):
+ interface = Interface(name, self, attributes)
+ self.interfaces.append(interface)
+ return interface
+
+ def AddStruct(self, name, attributes=None):
+ struct = Struct(name, self, attributes)
+ self.structs.append(struct)
+ return struct
+
+ def AddUnion(self, name, attributes=None):
+ union = Union(name, self, attributes)
+ self.unions.append(union)
+ return union
+
+
+def IsBoolKind(kind):
+ return kind.spec == BOOL.spec
+
+
+def IsFloatKind(kind):
+ return kind.spec == FLOAT.spec
+
+
+def IsDoubleKind(kind):
+ return kind.spec == DOUBLE.spec
+
+
+def IsIntegralKind(kind):
+ return (kind.spec == BOOL.spec or
+ kind.spec == INT8.spec or
+ kind.spec == INT16.spec or
+ kind.spec == INT32.spec or
+ kind.spec == INT64.spec or
+ kind.spec == UINT8.spec or
+ kind.spec == UINT16.spec or
+ kind.spec == UINT32.spec or
+ kind.spec == UINT64.spec)
+
+
+def IsStringKind(kind):
+ return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec
+
+
+def IsGenericHandleKind(kind):
+ return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec
+
+
+def IsDataPipeConsumerKind(kind):
+ return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec
+
+
+def IsDataPipeProducerKind(kind):
+ return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec
+
+
+def IsMessagePipeKind(kind):
+ return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec
+
+
+def IsSharedBufferKind(kind):
+ return (kind.spec == SHAREDBUFFER.spec or
+ kind.spec == NULLABLE_SHAREDBUFFER.spec)
+
+
+def IsStructKind(kind):
+ return isinstance(kind, Struct)
+
+
+def IsUnionKind(kind):
+ return isinstance(kind, Union)
+
+
+def IsArrayKind(kind):
+ return isinstance(kind, Array)
+
+
+def IsInterfaceKind(kind):
+ return isinstance(kind, Interface)
+
+
+def IsAssociatedInterfaceKind(kind):
+ return isinstance(kind, AssociatedInterface)
+
+
+def IsInterfaceRequestKind(kind):
+ return isinstance(kind, InterfaceRequest)
+
+
+def IsAssociatedInterfaceRequestKind(kind):
+ return isinstance(kind, AssociatedInterfaceRequest)
+
+
+def IsEnumKind(kind):
+ return isinstance(kind, Enum)
+
+
+def IsReferenceKind(kind):
+ return isinstance(kind, ReferenceKind)
+
+
+def IsNullableKind(kind):
+ return IsReferenceKind(kind) and kind.is_nullable
+
+
+def IsMapKind(kind):
+ return isinstance(kind, Map)
+
+
+def IsObjectKind(kind):
+ return IsPointerKind(kind) or IsUnionKind(kind)
+
+
+def IsPointerKind(kind):
+ return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind) or
+ IsMapKind(kind))
+
+
+# Please note that it doesn't include any interface kind.
+def IsAnyHandleKind(kind):
+ return (IsGenericHandleKind(kind) or
+ IsDataPipeConsumerKind(kind) or
+ IsDataPipeProducerKind(kind) or
+ IsMessagePipeKind(kind) or
+ IsSharedBufferKind(kind))
+
+
+def IsAnyInterfaceKind(kind):
+ return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind) or
+ IsAssociatedKind(kind))
+
+
+def IsAnyHandleOrInterfaceKind(kind):
+ return IsAnyHandleKind(kind) or IsAnyInterfaceKind(kind)
+
+
+def IsAssociatedKind(kind):
+ return (IsAssociatedInterfaceKind(kind) or
+ IsAssociatedInterfaceRequestKind(kind))
+
+
+def HasCallbacks(interface):
+ for method in interface.methods:
+ if method.response_parameters != None:
+ return True
+ return False
+
+
+# Finds out whether an interface passes associated interfaces and associated
+# interface requests.
+def PassesAssociatedKinds(interface):
+ def _ContainsAssociatedKinds(kind, visited_kinds):
+ if kind in visited_kinds:
+ # No need to examine the kind again.
+ return False
+ visited_kinds.add(kind)
+ if IsAssociatedKind(kind):
+ return True
+ if IsArrayKind(kind):
+ return _ContainsAssociatedKinds(kind.kind, visited_kinds)
+ if IsStructKind(kind) or IsUnionKind(kind):
+ for field in kind.fields:
+ if _ContainsAssociatedKinds(field.kind, visited_kinds):
+ return True
+ if IsMapKind(kind):
+ # No need to examine the key kind, only primitive kinds and non-nullable
+ # string are allowed to be key kinds.
+ return _ContainsAssociatedKinds(kind.value_kind, visited_kinds)
+ return False
+
+ visited_kinds = set()
+ for method in interface.methods:
+ for param in method.parameters:
+ if _ContainsAssociatedKinds(param.kind, visited_kinds):
+ return True
+ if method.response_parameters != None:
+ for param in method.response_parameters:
+ if _ContainsAssociatedKinds(param.kind, visited_kinds):
+ return True
+ return False
+
+
+def HasSyncMethods(interface):
+ for method in interface.methods:
+ if method.sync:
+ return True
+ return False
+
+
+def ContainsHandlesOrInterfaces(kind):
+ """Check if the kind contains any handles.
+
+ This check is recursive so it checks all struct fields, containers elements,
+ etc.
+
+ Args:
+ struct: {Kind} The kind to check.
+
+ Returns:
+ {bool}: True if the kind contains handles.
+ """
+ # We remember the types we already checked to avoid infinite recursion when
+ # checking recursive (or mutually recursive) types:
+ checked = set()
+ def Check(kind):
+ if kind.spec in checked:
+ return False
+ checked.add(kind.spec)
+ if IsStructKind(kind):
+ return any(Check(field.kind) for field in kind.fields)
+ elif IsUnionKind(kind):
+ return any(Check(field.kind) for field in kind.fields)
+ elif IsAnyHandleKind(kind):
+ return True
+ elif IsAnyInterfaceKind(kind):
+ return True
+ elif IsArrayKind(kind):
+ return Check(kind.kind)
+ elif IsMapKind(kind):
+ return Check(kind.key_kind) or Check(kind.value_kind)
+ else:
+ return False
+ return Check(kind)
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py
new file mode 100644
index 0000000000..a887686e1b
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py
@@ -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.
+
+import sys
+
+import test_support
+
+EXPECT_EQ = test_support.EXPECT_EQ
+EXPECT_TRUE = test_support.EXPECT_TRUE
+RunTest = test_support.RunTest
+ModulesAreEqual = test_support.ModulesAreEqual
+BuildTestModule = test_support.BuildTestModule
+TestTestModule = test_support.TestTestModule
+
+
+def BuildAndTestModule():
+ return TestTestModule(BuildTestModule())
+
+
+def TestModulesEqual():
+ return EXPECT_TRUE(ModulesAreEqual(BuildTestModule(), BuildTestModule()))
+
+
+def Main(args):
+ errors = 0
+ errors += RunTest(BuildAndTestModule)
+ errors += RunTest(TestModulesEqual)
+
+ return errors
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
new file mode 100644
index 0000000000..37dc8f396b
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
@@ -0,0 +1,250 @@
+# Copyright 2013 The Chromium 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 module as mojom
+
+# This module provides a mechanism for determining the packed order and offsets
+# of a mojom.Struct.
+#
+# ps = pack.PackedStruct(struct)
+# ps.packed_fields will access a list of PackedField objects, each of which
+# will have an offset, a size and a bit (for mojom.BOOLs).
+
+# Size of struct header in bytes: num_bytes [4B] + version [4B].
+HEADER_SIZE = 8
+
+class PackedField(object):
+ kind_to_size = {
+ mojom.BOOL: 1,
+ mojom.INT8: 1,
+ mojom.UINT8: 1,
+ mojom.INT16: 2,
+ mojom.UINT16: 2,
+ mojom.INT32: 4,
+ mojom.UINT32: 4,
+ mojom.FLOAT: 4,
+ mojom.HANDLE: 4,
+ mojom.MSGPIPE: 4,
+ mojom.SHAREDBUFFER: 4,
+ mojom.DCPIPE: 4,
+ mojom.DPPIPE: 4,
+ mojom.NULLABLE_HANDLE: 4,
+ mojom.NULLABLE_MSGPIPE: 4,
+ mojom.NULLABLE_SHAREDBUFFER: 4,
+ mojom.NULLABLE_DCPIPE: 4,
+ mojom.NULLABLE_DPPIPE: 4,
+ mojom.INT64: 8,
+ mojom.UINT64: 8,
+ mojom.DOUBLE: 8,
+ mojom.STRING: 8,
+ mojom.NULLABLE_STRING: 8
+ }
+
+ @classmethod
+ def GetSizeForKind(cls, kind):
+ if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct,
+ mojom.Interface, mojom.AssociatedInterface)):
+ return 8
+ if isinstance(kind, mojom.Union):
+ return 16
+ if isinstance(kind, mojom.InterfaceRequest):
+ kind = mojom.MSGPIPE
+ if isinstance(kind, mojom.AssociatedInterfaceRequest):
+ return 4
+ if isinstance(kind, mojom.Enum):
+ # TODO(mpcomplete): what about big enums?
+ return cls.kind_to_size[mojom.INT32]
+ if not kind in cls.kind_to_size:
+ raise Exception("Invalid kind: %s" % kind.spec)
+ return cls.kind_to_size[kind]
+
+ @classmethod
+ def GetAlignmentForKind(cls, kind):
+ if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface)):
+ return 4
+ if isinstance(kind, mojom.Union):
+ return 8
+ return cls.GetSizeForKind(kind)
+
+ def __init__(self, field, index, ordinal):
+ """
+ Args:
+ field: the original field.
+ index: the position of the original field in the struct.
+ ordinal: the ordinal of the field for serialization.
+ """
+ self.field = field
+ self.index = index
+ self.ordinal = ordinal
+ self.size = self.GetSizeForKind(field.kind)
+ self.alignment = self.GetAlignmentForKind(field.kind)
+ self.offset = None
+ self.bit = None
+ self.min_version = None
+
+
+def GetPad(offset, alignment):
+ """Returns the pad necessary to reserve space so that |offset + pad| equals to
+ some multiple of |alignment|."""
+ return (alignment - (offset % alignment)) % alignment
+
+
+def GetFieldOffset(field, last_field):
+ """Returns a 2-tuple of the field offset and bit (for BOOLs)."""
+ if (field.field.kind == mojom.BOOL and
+ last_field.field.kind == mojom.BOOL and
+ last_field.bit < 7):
+ return (last_field.offset, last_field.bit + 1)
+
+ offset = last_field.offset + last_field.size
+ pad = GetPad(offset, field.alignment)
+ return (offset + pad, 0)
+
+
+def GetPayloadSizeUpToField(field):
+ """Returns the payload size (not including struct header) if |field| is the
+ last field.
+ """
+ if not field:
+ return 0
+ offset = field.offset + field.size
+ pad = GetPad(offset, 8)
+ return offset + pad
+
+
+class PackedStruct(object):
+ def __init__(self, struct):
+ self.struct = struct
+ # |packed_fields| contains all the fields, in increasing offset order.
+ self.packed_fields = []
+ # |packed_fields_in_ordinal_order| refers to the same fields as
+ # |packed_fields|, but in ordinal order.
+ self.packed_fields_in_ordinal_order = []
+
+ # No fields.
+ if (len(struct.fields) == 0):
+ return
+
+ # Start by sorting by ordinal.
+ src_fields = self.packed_fields_in_ordinal_order
+ ordinal = 0
+ for index, field in enumerate(struct.fields):
+ if field.ordinal is not None:
+ ordinal = field.ordinal
+ src_fields.append(PackedField(field, index, ordinal))
+ ordinal += 1
+ src_fields.sort(key=lambda field: field.ordinal)
+
+ # Set |min_version| for each field.
+ next_min_version = 0
+ for packed_field in src_fields:
+ if packed_field.field.min_version is None:
+ assert next_min_version == 0
+ else:
+ assert packed_field.field.min_version >= next_min_version
+ next_min_version = packed_field.field.min_version
+ packed_field.min_version = next_min_version
+
+ if (packed_field.min_version != 0 and
+ mojom.IsReferenceKind(packed_field.field.kind) and
+ not packed_field.field.kind.is_nullable):
+ raise Exception("Non-nullable fields are only allowed in version 0 of "
+ "a struct. %s.%s is defined with [MinVersion=%d]."
+ % (self.struct.name, packed_field.field.name,
+ packed_field.min_version))
+
+ src_field = src_fields[0]
+ src_field.offset = 0
+ src_field.bit = 0
+ dst_fields = self.packed_fields
+ dst_fields.append(src_field)
+
+ # Then find first slot that each field will fit.
+ for src_field in src_fields[1:]:
+ last_field = dst_fields[0]
+ for i in xrange(1, len(dst_fields)):
+ next_field = dst_fields[i]
+ offset, bit = GetFieldOffset(src_field, last_field)
+ if offset + src_field.size <= next_field.offset:
+ # Found hole.
+ src_field.offset = offset
+ src_field.bit = bit
+ dst_fields.insert(i, src_field)
+ break
+ last_field = next_field
+ if src_field.offset is None:
+ # Add to end
+ src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field)
+ dst_fields.append(src_field)
+
+
+class ByteInfo(object):
+ def __init__(self):
+ self.is_padding = False
+ self.packed_fields = []
+
+
+def GetByteLayout(packed_struct):
+ total_payload_size = GetPayloadSizeUpToField(
+ packed_struct.packed_fields[-1] if packed_struct.packed_fields else None)
+ bytes = [ByteInfo() for i in xrange(total_payload_size)]
+
+ limit_of_previous_field = 0
+ for packed_field in packed_struct.packed_fields:
+ for i in xrange(limit_of_previous_field, packed_field.offset):
+ bytes[i].is_padding = True
+ bytes[packed_field.offset].packed_fields.append(packed_field)
+ limit_of_previous_field = packed_field.offset + packed_field.size
+
+ for i in xrange(limit_of_previous_field, len(bytes)):
+ bytes[i].is_padding = True
+
+ for byte in bytes:
+ # A given byte cannot both be padding and have a fields packed into it.
+ assert not (byte.is_padding and byte.packed_fields)
+
+ return bytes
+
+
+class VersionInfo(object):
+ def __init__(self, version, num_fields, num_bytes):
+ self.version = version
+ self.num_fields = num_fields
+ self.num_bytes = num_bytes
+
+
+def GetVersionInfo(packed_struct):
+ """Get version information for a struct.
+
+ Args:
+ packed_struct: A PackedStruct instance.
+
+ Returns:
+ A non-empty list of VersionInfo instances, sorted by version in increasing
+ order.
+ Note: The version numbers may not be consecutive.
+ """
+ versions = []
+ last_version = 0
+ last_num_fields = 0
+ last_payload_size = 0
+
+ for packed_field in packed_struct.packed_fields_in_ordinal_order:
+ if packed_field.min_version != last_version:
+ versions.append(
+ VersionInfo(last_version, last_num_fields,
+ last_payload_size + HEADER_SIZE))
+ last_version = packed_field.min_version
+
+ last_num_fields += 1
+ # The fields are iterated in ordinal order here. However, the size of a
+ # version is determined by the last field of that version in pack order,
+ # instead of ordinal order. Therefore, we need to calculate the max value.
+ last_payload_size = max(GetPayloadSizeUpToField(packed_field),
+ last_payload_size)
+
+ assert len(versions) == 0 or last_num_fields != versions[-1].num_fields
+ versions.append(VersionInfo(last_version, last_num_fields,
+ last_payload_size + HEADER_SIZE))
+ return versions
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py
new file mode 100644
index 0000000000..14f699da34
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py
@@ -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.
+
+import sys
+
+import module as mojom
+import pack
+import test_support
+
+
+EXPECT_EQ = test_support.EXPECT_EQ
+EXPECT_TRUE = test_support.EXPECT_TRUE
+RunTest = test_support.RunTest
+
+
+def TestOrdinalOrder():
+ errors = 0
+ struct = mojom.Struct('test')
+ struct.AddField('testfield1', mojom.INT32, 2)
+ struct.AddField('testfield2', mojom.INT32, 1)
+ ps = pack.PackedStruct(struct)
+
+ errors += EXPECT_EQ(2, len(ps.packed_fields))
+ errors += EXPECT_EQ('testfield2', ps.packed_fields[0].field.name)
+ errors += EXPECT_EQ('testfield1', ps.packed_fields[1].field.name)
+
+ return errors
+
+def TestZeroFields():
+ errors = 0
+ struct = mojom.Struct('test')
+ ps = pack.PackedStruct(struct)
+ errors += EXPECT_EQ(0, len(ps.packed_fields))
+ return errors
+
+
+def TestOneField():
+ errors = 0
+ struct = mojom.Struct('test')
+ struct.AddField('testfield1', mojom.INT8)
+ ps = pack.PackedStruct(struct)
+ errors += EXPECT_EQ(1, len(ps.packed_fields))
+ return errors
+
+# Pass three tuples.
+# |kinds| is a sequence of mojom.Kinds that specify the fields that are to
+# be created.
+# |fields| is the expected order of the resulting fields, with the integer
+# "1" first.
+# |offsets| is the expected order of offsets, with the integer "0" first.
+def TestSequence(kinds, fields, offsets):
+ errors = 0
+ struct = mojom.Struct('test')
+ index = 1
+ for kind in kinds:
+ struct.AddField("%d" % index, kind)
+ index += 1
+ ps = pack.PackedStruct(struct)
+ num_fields = len(ps.packed_fields)
+ errors += EXPECT_EQ(len(kinds), num_fields)
+ for i in xrange(num_fields):
+ EXPECT_EQ("%d" % fields[i], ps.packed_fields[i].field.name)
+ EXPECT_EQ(offsets[i], ps.packed_fields[i].offset)
+
+ return errors
+
+
+def TestPaddingPackedInOrder():
+ return TestSequence(
+ (mojom.INT8, mojom.UINT8, mojom.INT32),
+ (1, 2, 3),
+ (0, 1, 4))
+
+
+def TestPaddingPackedOutOfOrder():
+ return TestSequence(
+ (mojom.INT8, mojom.INT32, mojom.UINT8),
+ (1, 3, 2),
+ (0, 1, 4))
+
+
+def TestPaddingPackedOverflow():
+ kinds = (mojom.INT8, mojom.INT32, mojom.INT16, mojom.INT8, mojom.INT8)
+ # 2 bytes should be packed together first, followed by short, then by int.
+ fields = (1, 4, 3, 2, 5)
+ offsets = (0, 1, 2, 4, 8)
+ return TestSequence(kinds, fields, offsets)
+
+
+def TestNullableTypes():
+ kinds = (mojom.STRING.MakeNullableKind(),
+ mojom.HANDLE.MakeNullableKind(),
+ mojom.Struct('test_struct').MakeNullableKind(),
+ mojom.DCPIPE.MakeNullableKind(),
+ mojom.Array().MakeNullableKind(),
+ mojom.DPPIPE.MakeNullableKind(),
+ mojom.Array(length=5).MakeNullableKind(),
+ mojom.MSGPIPE.MakeNullableKind(),
+ mojom.Interface('test_inteface').MakeNullableKind(),
+ mojom.SHAREDBUFFER.MakeNullableKind(),
+ mojom.InterfaceRequest().MakeNullableKind())
+ fields = (1, 2, 4, 3, 5, 6, 8, 7, 9, 10, 11)
+ offsets = (0, 8, 12, 16, 24, 32, 36, 40, 48, 52, 56)
+ return TestSequence(kinds, fields, offsets)
+
+
+def TestAllTypes():
+ return TestSequence(
+ (mojom.BOOL, mojom.INT8, mojom.STRING, mojom.UINT8,
+ mojom.INT16, mojom.DOUBLE, mojom.UINT16,
+ mojom.INT32, mojom.UINT32, mojom.INT64,
+ mojom.FLOAT, mojom.STRING, mojom.HANDLE,
+ mojom.UINT64, mojom.Struct('test'), mojom.Array(),
+ mojom.STRING.MakeNullableKind()),
+ (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17, 18),
+ (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80, 88))
+
+
+def TestPaddingPackedOutOfOrderByOrdinal():
+ errors = 0
+ struct = mojom.Struct('test')
+ struct.AddField('testfield1', mojom.INT8)
+ struct.AddField('testfield3', mojom.UINT8, 3)
+ struct.AddField('testfield2', mojom.INT32, 2)
+ ps = pack.PackedStruct(struct)
+ errors += EXPECT_EQ(3, len(ps.packed_fields))
+
+ # Second byte should be packed in behind first, altering order.
+ errors += EXPECT_EQ('testfield1', ps.packed_fields[0].field.name)
+ errors += EXPECT_EQ('testfield3', ps.packed_fields[1].field.name)
+ errors += EXPECT_EQ('testfield2', ps.packed_fields[2].field.name)
+
+ # Second byte should be packed with first.
+ errors += EXPECT_EQ(0, ps.packed_fields[0].offset)
+ errors += EXPECT_EQ(1, ps.packed_fields[1].offset)
+ errors += EXPECT_EQ(4, ps.packed_fields[2].offset)
+
+ return errors
+
+
+def TestBools():
+ errors = 0
+ struct = mojom.Struct('test')
+ struct.AddField('bit0', mojom.BOOL)
+ struct.AddField('bit1', mojom.BOOL)
+ struct.AddField('int', mojom.INT32)
+ struct.AddField('bit2', mojom.BOOL)
+ struct.AddField('bit3', mojom.BOOL)
+ struct.AddField('bit4', mojom.BOOL)
+ struct.AddField('bit5', mojom.BOOL)
+ struct.AddField('bit6', mojom.BOOL)
+ struct.AddField('bit7', mojom.BOOL)
+ struct.AddField('bit8', mojom.BOOL)
+ ps = pack.PackedStruct(struct)
+ errors += EXPECT_EQ(10, len(ps.packed_fields))
+
+ # First 8 bits packed together.
+ for i in xrange(8):
+ pf = ps.packed_fields[i]
+ errors += EXPECT_EQ(0, pf.offset)
+ errors += EXPECT_EQ("bit%d" % i, pf.field.name)
+ errors += EXPECT_EQ(i, pf.bit)
+
+ # Ninth bit goes into second byte.
+ errors += EXPECT_EQ("bit8", ps.packed_fields[8].field.name)
+ errors += EXPECT_EQ(1, ps.packed_fields[8].offset)
+ errors += EXPECT_EQ(0, ps.packed_fields[8].bit)
+
+ # int comes last.
+ errors += EXPECT_EQ("int", ps.packed_fields[9].field.name)
+ errors += EXPECT_EQ(4, ps.packed_fields[9].offset)
+
+ return errors
+
+
+def Main(args):
+ errors = 0
+ errors += RunTest(TestZeroFields)
+ errors += RunTest(TestOneField)
+ errors += RunTest(TestPaddingPackedInOrder)
+ errors += RunTest(TestPaddingPackedOutOfOrder)
+ errors += RunTest(TestPaddingPackedOverflow)
+ errors += RunTest(TestNullableTypes)
+ errors += RunTest(TestAllTypes)
+ errors += RunTest(TestPaddingPackedOutOfOrderByOrdinal)
+ errors += RunTest(TestBools)
+
+ return errors
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py
new file mode 100755
index 0000000000..41f11a2b71
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+""" Test runner for Mojom """
+
+import subprocess
+import sys
+
+def TestMojom(testname, args):
+ print '\nRunning unit tests for %s.' % testname
+ try:
+ args = [sys.executable, testname] + args
+ subprocess.check_call(args, stdout=sys.stdout)
+ print 'Succeeded'
+ return 0
+ except subprocess.CalledProcessError as err:
+ print 'Failed with %s.' % str(err)
+ return 1
+
+
+def main(args):
+ errors = 0
+ errors += TestMojom('data_tests.py', ['--test'])
+ errors += TestMojom('module_tests.py', ['--test'])
+ errors += TestMojom('pack_tests.py', ['--test'])
+
+ if errors:
+ print '\nFailed tests.'
+ return min(errors, 127) # Make sure the return value doesn't "wrap".
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
new file mode 100644
index 0000000000..66f8954012
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
@@ -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.
+
+# Based on third_party/WebKit/Source/build/scripts/template_expander.py.
+
+import imp
+import os.path
+import sys
+
+# Disable lint check for finding modules:
+# pylint: disable=F0401
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("jinja2")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+import jinja2
+
+
+def ApplyTemplate(mojo_generator, path_to_template, params, **kwargs):
+ loader = jinja2.ModuleLoader(os.path.join(
+ mojo_generator.bytecode_path, "%s.zip" % mojo_generator.GetTemplatePrefix(
+ )))
+ final_kwargs = dict(mojo_generator.GetJinjaParameters())
+ final_kwargs.update(kwargs)
+ jinja_env = jinja2.Environment(loader=loader,
+ keep_trailing_newline=True,
+ **final_kwargs)
+ jinja_env.globals.update(mojo_generator.GetGlobals())
+ jinja_env.filters.update(mojo_generator.GetFilters())
+ template = jinja_env.get_template(path_to_template)
+ return template.render(params)
+
+
+def UseJinja(path_to_template, **kwargs):
+ def RealDecorator(generator):
+ def GeneratorInternal(*args, **kwargs2):
+ parameters = generator(*args, **kwargs2)
+ return ApplyTemplate(args[0], path_to_template, parameters, **kwargs)
+ GeneratorInternal.func_name = generator.func_name
+ return GeneratorInternal
+ return RealDecorator
+
+
+def PrecompileTemplates(generator_modules, output_dir):
+ for module in generator_modules.values():
+ generator = module.Generator(None)
+ jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader([os.path.join(
+ os.path.dirname(module.__file__), generator.GetTemplatePrefix())]))
+ jinja_env.filters.update(generator.GetFilters())
+ jinja_env.compile_templates(
+ os.path.join(output_dir, "%s.zip" % generator.GetTemplatePrefix()),
+ extensions=["tmpl"],
+ zip="stored",
+ py_compile=True,
+ ignore_errors=False)
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py
new file mode 100644
index 0000000000..eb394619d2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py
@@ -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.
+
+import sys
+import traceback
+
+import module as mojom
+
+# Support for writing mojom test cases.
+# RunTest(fn) will execute fn, catching any exceptions. fn should return
+# the number of errors that are encountered.
+#
+# EXPECT_EQ(a, b) and EXPECT_TRUE(b) will print error information if the
+# expectations are not true and return a non zero value. This allows test cases
+# to be written like this
+#
+# def Foo():
+# errors = 0
+# errors += EXPECT_EQ('test', test())
+# ...
+# return errors
+#
+# RunTest(foo)
+
+def FieldsAreEqual(field1, field2):
+ if field1 == field2:
+ return True
+ return field1.name == field2.name and \
+ KindsAreEqual(field1.kind, field2.kind) and \
+ field1.ordinal == field2.ordinal and \
+ field1.default == field2.default
+
+
+def KindsAreEqual(kind1, kind2):
+ if kind1 == kind2:
+ return True
+ if kind1.__class__ != kind2.__class__ or kind1.spec != kind2.spec:
+ return False
+ if kind1.__class__ == mojom.Kind:
+ return kind1.spec == kind2.spec
+ if kind1.__class__ == mojom.Struct:
+ if kind1.name != kind2.name or \
+ kind1.spec != kind2.spec or \
+ len(kind1.fields) != len(kind2.fields):
+ return False
+ for i in range(len(kind1.fields)):
+ if not FieldsAreEqual(kind1.fields[i], kind2.fields[i]):
+ return False
+ return True
+ if kind1.__class__ == mojom.Array:
+ return KindsAreEqual(kind1.kind, kind2.kind)
+ print 'Unknown Kind class: ', kind1.__class__.__name__
+ return False
+
+
+def ParametersAreEqual(parameter1, parameter2):
+ if parameter1 == parameter2:
+ return True
+ return parameter1.name == parameter2.name and \
+ parameter1.ordinal == parameter2.ordinal and \
+ parameter1.default == parameter2.default and \
+ KindsAreEqual(parameter1.kind, parameter2.kind)
+
+
+def MethodsAreEqual(method1, method2):
+ if method1 == method2:
+ return True
+ if method1.name != method2.name or \
+ method1.ordinal != method2.ordinal or \
+ len(method1.parameters) != len(method2.parameters):
+ return False
+ for i in range(len(method1.parameters)):
+ if not ParametersAreEqual(method1.parameters[i], method2.parameters[i]):
+ return False
+ return True
+
+
+def InterfacesAreEqual(interface1, interface2):
+ if interface1 == interface2:
+ return True
+ if interface1.name != interface2.name or \
+ len(interface1.methods) != len(interface2.methods):
+ return False
+ for i in range(len(interface1.methods)):
+ if not MethodsAreEqual(interface1.methods[i], interface2.methods[i]):
+ return False
+ return True
+
+
+def ModulesAreEqual(module1, module2):
+ if module1 == module2:
+ return True
+ if module1.name != module2.name or \
+ module1.namespace != module2.namespace or \
+ len(module1.structs) != len(module2.structs) or \
+ len(module1.interfaces) != len(module2.interfaces):
+ return False
+ for i in range(len(module1.structs)):
+ if not KindsAreEqual(module1.structs[i], module2.structs[i]):
+ return False
+ for i in range(len(module1.interfaces)):
+ if not InterfacesAreEqual(module1.interfaces[i], module2.interfaces[i]):
+ return False
+ return True
+
+
+# Builds and returns a Module suitable for testing/
+def BuildTestModule():
+ module = mojom.Module('test', 'testspace')
+ struct = module.AddStruct('teststruct')
+ struct.AddField('testfield1', mojom.INT32)
+ struct.AddField('testfield2', mojom.Array(mojom.INT32), 42)
+
+ interface = module.AddInterface('Server')
+ method = interface.AddMethod('Foo', 42)
+ method.AddParameter('foo', mojom.INT32)
+ method.AddParameter('bar', mojom.Array(struct))
+
+ return module
+
+
+# Tests if |module| is as built by BuildTestModule(). Returns the number of
+# errors
+def TestTestModule(module):
+ errors = 0
+
+ errors += EXPECT_EQ('test', module.name)
+ errors += EXPECT_EQ('testspace', module.namespace)
+ errors += EXPECT_EQ(1, len(module.structs))
+ errors += EXPECT_EQ('teststruct', module.structs[0].name)
+ errors += EXPECT_EQ(2, len(module.structs[0].fields))
+ errors += EXPECT_EQ('testfield1', module.structs[0].fields[0].name)
+ errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[0].kind)
+ errors += EXPECT_EQ('testfield2', module.structs[0].fields[1].name)
+ errors += EXPECT_EQ(mojom.Array, module.structs[0].fields[1].kind.__class__)
+ errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[1].kind.kind)
+
+ errors += EXPECT_EQ(1, len(module.interfaces))
+ errors += EXPECT_EQ('Server', module.interfaces[0].name)
+ errors += EXPECT_EQ(1, len(module.interfaces[0].methods))
+ errors += EXPECT_EQ('Foo', module.interfaces[0].methods[0].name)
+ errors += EXPECT_EQ(2, len(module.interfaces[0].methods[0].parameters))
+ errors += EXPECT_EQ('foo', module.interfaces[0].methods[0].parameters[0].name)
+ errors += EXPECT_EQ(mojom.INT32,
+ module.interfaces[0].methods[0].parameters[0].kind)
+ errors += EXPECT_EQ('bar', module.interfaces[0].methods[0].parameters[1].name)
+ errors += EXPECT_EQ(
+ mojom.Array,
+ module.interfaces[0].methods[0].parameters[1].kind.__class__)
+ errors += EXPECT_EQ(
+ module.structs[0],
+ module.interfaces[0].methods[0].parameters[1].kind.kind)
+ return errors
+
+
+def PrintFailure(string):
+ stack = traceback.extract_stack()
+ frame = stack[len(stack)-3]
+ sys.stderr.write("ERROR at %s:%d, %s\n" % (frame[0], frame[1], string))
+ print "Traceback:"
+ for line in traceback.format_list(stack[:len(stack)-2]):
+ sys.stderr.write(line)
+
+
+def EXPECT_EQ(a, b):
+ if a != b:
+ PrintFailure("%s != %s" % (a, b))
+ return 1
+ return 0
+
+
+def EXPECT_TRUE(a):
+ if not a:
+ PrintFailure('Expecting True')
+ return 1
+ return 0
+
+
+def RunTest(fn):
+ sys.stdout.write('Running %s...' % fn.__name__)
+ try:
+ errors = fn()
+ except:
+ traceback.print_exc(sys.stderr)
+ errors = 1
+ if errors == 0:
+ sys.stdout.write('OK\n')
+ elif errors == 1:
+ sys.stdout.write('1 ERROR\n')
+ else:
+ sys.stdout.write('%d ERRORS\n' % errors)
+ return errors
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
new file mode 100644
index 0000000000..ffad7447a9
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
@@ -0,0 +1,639 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Convert parse tree to AST.
+
+This module converts the parse tree to the AST we use for code generation. The
+main entry point is OrderedModule, which gets passed the parser
+representation of a mojom file. When called it's assumed that all imports have
+already been parsed and converted to ASTs before.
+"""
+
+import copy
+import re
+
+import module as mojom
+from mojom.parse import ast
+
+def _DuplicateName(values):
+ """Returns the 'name' of the first entry in |values| whose 'name' has already
+ been encountered. If there are no duplicates, returns None."""
+ names = set()
+ for value in values:
+ if value.name in names:
+ return value.name
+ names.add(value.name)
+ return None
+
+def _ElemsOfType(elems, elem_type, scope):
+ """Find all elements of the given type.
+
+ Args:
+ elems: {Sequence[Any]} Sequence of elems.
+ elem_type: {Type[C]} Extract all elems of this type.
+ scope: {str} The name of the surrounding scope (e.g. struct
+ definition). Used in error messages.
+
+ Returns:
+ {List[C]} All elems of matching type.
+ """
+ assert isinstance(elem_type, type)
+ result = [elem for elem in elems if isinstance(elem, elem_type)]
+ duplicate_name = _DuplicateName(result)
+ if duplicate_name:
+ raise Exception('Names in mojom must be unique within a scope. The name '
+ '"%s" is used more than once within the scope "%s".' %
+ (duplicate_name, scope))
+ return result
+
+def _MapKind(kind):
+ map_to_kind = {'bool': 'b',
+ 'int8': 'i8',
+ 'int16': 'i16',
+ 'int32': 'i32',
+ 'int64': 'i64',
+ 'uint8': 'u8',
+ 'uint16': 'u16',
+ 'uint32': 'u32',
+ 'uint64': 'u64',
+ 'float': 'f',
+ 'double': 'd',
+ 'string': 's',
+ 'handle': 'h',
+ 'handle<data_pipe_consumer>': 'h:d:c',
+ 'handle<data_pipe_producer>': 'h:d:p',
+ 'handle<message_pipe>': 'h:m',
+ 'handle<shared_buffer>': 'h:s'}
+ if kind.endswith('?'):
+ base_kind = _MapKind(kind[0:-1])
+ # NOTE: This doesn't rule out enum types. Those will be detected later, when
+ # cross-reference is established.
+ reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso')
+ if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
+ raise Exception(
+ 'A type (spec "%s") cannot be made nullable' % base_kind)
+ return '?' + base_kind
+ if kind.endswith('}'):
+ lbracket = kind.rfind('{')
+ value = kind[0:lbracket]
+ return 'm[' + _MapKind(kind[lbracket+1:-1]) + '][' + _MapKind(value) + ']'
+ if kind.endswith(']'):
+ lbracket = kind.rfind('[')
+ typename = kind[0:lbracket]
+ return 'a' + kind[lbracket+1:-1] + ':' + _MapKind(typename)
+ if kind.endswith('&'):
+ return 'r:' + _MapKind(kind[0:-1])
+ if kind.startswith('asso<'):
+ assert kind.endswith('>')
+ return 'asso:' + _MapKind(kind[5:-1])
+ if kind in map_to_kind:
+ return map_to_kind[kind]
+ return 'x:' + kind
+
+def _AttributeListToDict(attribute_list):
+ if attribute_list is None:
+ return None
+ assert isinstance(attribute_list, ast.AttributeList)
+ # TODO(vtl): Check for duplicate keys here.
+ return dict([(attribute.key, attribute.value)
+ for attribute in attribute_list])
+
+builtin_values = frozenset([
+ "double.INFINITY",
+ "double.NEGATIVE_INFINITY",
+ "double.NAN",
+ "float.INFINITY",
+ "float.NEGATIVE_INFINITY",
+ "float.NAN"])
+
+def _IsBuiltinValue(value):
+ return value in builtin_values
+
+def _LookupKind(kinds, spec, scope):
+ """Tries to find which Kind a spec refers to, given the scope in which its
+ referenced. Starts checking from the narrowest scope to most general. For
+ example, given a struct field like
+ Foo.Bar x;
+ Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner
+ type 'Bar' in the struct 'Foo' in the current namespace.
+
+ |scope| is a tuple that looks like (namespace, struct/interface), referring
+ to the location where the type is referenced."""
+ if spec.startswith('x:'):
+ name = spec[2:]
+ for i in xrange(len(scope), -1, -1):
+ test_spec = 'x:'
+ if i > 0:
+ test_spec += '.'.join(scope[:i]) + '.'
+ test_spec += name
+ kind = kinds.get(test_spec)
+ if kind:
+ return kind
+
+ return kinds.get(spec)
+
+def _LookupValue(values, name, scope, kind):
+ """Like LookupKind, but for constant values."""
+ # If the type is an enum, the value can be specified as a qualified name, in
+ # which case the form EnumName.ENUM_VALUE must be used. We use the presence
+ # of a '.' in the requested name to identify this. Otherwise, we prepend the
+ # enum name.
+ if isinstance(kind, mojom.Enum) and '.' not in name:
+ name = '%s.%s' % (kind.spec.split(':', 1)[1], name)
+ for i in reversed(xrange(len(scope) + 1)):
+ test_spec = '.'.join(scope[:i])
+ if test_spec:
+ test_spec += '.'
+ test_spec += name
+ value = values.get(test_spec)
+ if value:
+ return value
+
+ return values.get(name)
+
+def _FixupExpression(module, value, scope, kind):
+ """Translates an IDENTIFIER into a built-in value or structured NamedValue
+ object."""
+ if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
+ # Allow user defined values to shadow builtins.
+ result = _LookupValue(module.values, value[1], scope, kind)
+ if result:
+ if isinstance(result, tuple):
+ raise Exception('Unable to resolve expression: %r' % value[1])
+ return result
+ if _IsBuiltinValue(value[1]):
+ return mojom.BuiltinValue(value[1])
+ return value
+
+def _Kind(kinds, spec, scope):
+ """Convert a type name into a mojom.Kind object.
+
+ As a side-effect this function adds the result to 'kinds'.
+
+ Args:
+ kinds: {Dict[str, mojom.Kind]} All known kinds up to this point, indexed by
+ their names.
+ spec: {str} A name uniquely identifying a type.
+ scope: {Tuple[str, str]} A tuple that looks like (namespace,
+ struct/interface), referring to the location where the type is
+ referenced.
+
+ Returns:
+ {mojom.Kind} The type corresponding to 'spec'.
+ """
+ kind = _LookupKind(kinds, spec, scope)
+ if kind:
+ return kind
+
+ if spec.startswith('?'):
+ kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
+ elif spec.startswith('a:'):
+ kind = mojom.Array(_Kind(kinds, spec[2:], scope))
+ elif spec.startswith('asso:'):
+ inner_kind = _Kind(kinds, spec[5:], scope)
+ if isinstance(inner_kind, mojom.InterfaceRequest):
+ kind = mojom.AssociatedInterfaceRequest(inner_kind)
+ else:
+ kind = mojom.AssociatedInterface(inner_kind)
+ elif spec.startswith('a'):
+ colon = spec.find(':')
+ length = int(spec[1:colon])
+ kind = mojom.Array(_Kind(kinds, spec[colon+1:], scope), length)
+ elif spec.startswith('r:'):
+ kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope))
+ elif spec.startswith('m['):
+ # Isolate the two types from their brackets.
+
+ # It is not allowed to use map as key, so there shouldn't be nested ']'s
+ # inside the key type spec.
+ key_end = spec.find(']')
+ assert key_end != -1 and key_end < len(spec) - 1
+ assert spec[key_end+1] == '[' and spec[-1] == ']'
+
+ first_kind = spec[2:key_end]
+ second_kind = spec[key_end+2:-1]
+
+ kind = mojom.Map(_Kind(kinds, first_kind, scope),
+ _Kind(kinds, second_kind, scope))
+ else:
+ kind = mojom.Kind(spec)
+
+ kinds[spec] = kind
+ return kind
+
+def _KindFromImport(original_kind, imported_from):
+ """Used with 'import module' - clones the kind imported from the given
+ module's namespace. Only used with Structs, Unions, Interfaces and Enums."""
+ kind = copy.copy(original_kind)
+ # |shared_definition| is used to store various properties (see
+ # |AddSharedProperty()| in module.py), including |imported_from|. We don't
+ # want the copy to share these with the original, so copy it if necessary.
+ if hasattr(original_kind, 'shared_definition'):
+ kind.shared_definition = copy.copy(original_kind.shared_definition)
+ kind.imported_from = imported_from
+ return kind
+
+def _Import(module, import_module):
+ import_item = {}
+ import_item['module_name'] = import_module.name
+ import_item['namespace'] = import_module.namespace
+ import_item['module'] = import_module
+
+ # Copy the struct kinds from our imports into the current module.
+ importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
+ for kind in import_module.kinds.itervalues():
+ if (isinstance(kind, importable_kinds) and
+ kind.imported_from is None):
+ kind = _KindFromImport(kind, import_item)
+ module.kinds[kind.spec] = kind
+ # Ditto for values.
+ for value in import_module.values.itervalues():
+ if value.imported_from is None:
+ # Values don't have shared definitions (since they're not nullable), so no
+ # need to do anything special.
+ value = copy.copy(value)
+ value.imported_from = import_item
+ module.values[value.GetSpec()] = value
+
+ return import_item
+
+def _Struct(module, parsed_struct):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_struct: {ast.Struct} Parsed struct.
+
+ Returns:
+ {mojom.Struct} AST struct.
+ """
+ struct = mojom.Struct(module=module)
+ struct.name = parsed_struct.name
+ struct.native_only = parsed_struct.body is None
+ struct.spec = 'x:' + module.namespace + '.' + struct.name
+ module.kinds[struct.spec] = struct
+ if struct.native_only:
+ struct.enums = []
+ struct.constants = []
+ struct.fields_data = []
+ else:
+ struct.enums = map(
+ lambda enum: _Enum(module, enum, struct),
+ _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.name))
+ struct.constants = map(
+ lambda constant: _Constant(module, constant, struct),
+ _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.name))
+ # Stash fields parsed_struct here temporarily.
+ struct.fields_data = _ElemsOfType(
+ parsed_struct.body, ast.StructField, parsed_struct.name)
+ struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
+
+ # Enforce that a [Native] attribute is set to make native-only struct
+ # declarations more explicit.
+ if struct.native_only:
+ if not struct.attributes or not struct.attributes.get('Native', False):
+ raise Exception("Native-only struct declarations must include a " +
+ "Native attribute.")
+
+ return struct
+
+def _Union(module, parsed_union):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_union: {ast.Union} Parsed union.
+
+ Returns:
+ {mojom.Union} AST union.
+ """
+ union = mojom.Union(module=module)
+ union.name = parsed_union.name
+ union.spec = 'x:' + module.namespace + '.' + union.name
+ module.kinds[union.spec] = union
+ # Stash fields parsed_union here temporarily.
+ union.fields_data = _ElemsOfType(
+ parsed_union.body, ast.UnionField, parsed_union.name)
+ union.attributes = _AttributeListToDict(parsed_union.attribute_list)
+ return union
+
+def _StructField(module, parsed_field, struct):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_field: {ast.StructField} Parsed struct field.
+ struct: {mojom.Struct} Struct this field belongs to.
+
+ Returns:
+ {mojom.StructField} AST struct field.
+ """
+ field = mojom.StructField()
+ field.name = parsed_field.name
+ field.kind = _Kind(
+ module.kinds, _MapKind(parsed_field.typename),
+ (module.namespace, struct.name))
+ field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
+ field.default = _FixupExpression(
+ module, parsed_field.default_value, (module.namespace, struct.name),
+ field.kind)
+ field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+ return field
+
+def _UnionField(module, parsed_field, union):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_field: {ast.UnionField} Parsed union field.
+ union: {mojom.Union} Union this fields belong to.
+
+ Returns:
+ {mojom.UnionField} AST union.
+ """
+ field = mojom.UnionField()
+ field.name = parsed_field.name
+ field.kind = _Kind(
+ module.kinds, _MapKind(parsed_field.typename),
+ (module.namespace, union.name))
+ field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
+ field.default = _FixupExpression(
+ module, None, (module.namespace, union.name), field.kind)
+ field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+ return field
+
+def _Parameter(module, parsed_param, interface):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_param: {ast.Parameter} Parsed parameter.
+ union: {mojom.Interface} Interface this parameter belongs to.
+
+ Returns:
+ {mojom.Parameter} AST parameter.
+ """
+ parameter = mojom.Parameter()
+ parameter.name = parsed_param.name
+ parameter.kind = _Kind(
+ module.kinds, _MapKind(parsed_param.typename),
+ (module.namespace, interface.name))
+ parameter.ordinal = (
+ parsed_param.ordinal.value if parsed_param.ordinal else None)
+ parameter.default = None # TODO(tibell): We never have these. Remove field?
+ parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
+ return parameter
+
+def _Method(module, parsed_method, interface):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_method: {ast.Method} Parsed method.
+ interface: {mojom.Interface} Interface this method belongs to.
+
+ Returns:
+ {mojom.Method} AST method.
+ """
+ method = mojom.Method(
+ interface, parsed_method.name,
+ ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None)
+ method.parameters = map(
+ lambda parameter: _Parameter(module, parameter, interface),
+ parsed_method.parameter_list)
+ if parsed_method.response_parameter_list is not None:
+ method.response_parameters = map(
+ lambda parameter: _Parameter(module, parameter, interface),
+ parsed_method.response_parameter_list)
+ method.attributes = _AttributeListToDict(parsed_method.attribute_list)
+
+ # Enforce that only methods with response can have a [Sync] attribute.
+ if method.sync and method.response_parameters is None:
+ raise Exception("Only methods with response can include a [Sync] "
+ "attribute. If no response parameters are needed, you "
+ "could use an empty response parameter list, i.e., "
+ "\"=> ()\".")
+
+ return method
+
+def _Interface(module, parsed_iface):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_iface: {ast.Interface} Parsed interface.
+
+ Returns:
+ {mojom.Interface} AST interface.
+ """
+ interface = mojom.Interface(module=module)
+ interface.name = parsed_iface.name
+ interface.spec = 'x:' + module.namespace + '.' + interface.name
+ module.kinds[interface.spec] = interface
+ interface.enums = map(
+ lambda enum: _Enum(module, enum, interface),
+ _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.name))
+ interface.constants = map(
+ lambda constant: _Constant(module, constant, interface),
+ _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.name))
+ # Stash methods parsed_iface here temporarily.
+ interface.methods_data = _ElemsOfType(
+ parsed_iface.body, ast.Method, parsed_iface.name)
+ interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
+ return interface
+
+def _EnumField(module, enum, parsed_field, parent_kind):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ enum: {mojom.Enum} Enum this field belongs to.
+ parsed_field: {ast.EnumValue} Parsed enum value.
+ parent_kind: {mojom.Kind} The enclosing type.
+
+ Returns:
+ {mojom.EnumField} AST enum field.
+ """
+ field = mojom.EnumField()
+ field.name = parsed_field.name
+ # TODO(mpcomplete): FixupExpression should be done in the second pass,
+ # so constants and enums can refer to each other.
+ # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
+ # vice versa?
+ if parent_kind:
+ field.value = _FixupExpression(
+ module, parsed_field.value, (module.namespace, parent_kind.name), enum)
+ else:
+ field.value = _FixupExpression(
+ module, parsed_field.value, (module.namespace, ), enum)
+ field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+ value = mojom.EnumValue(module, enum, field)
+ module.values[value.GetSpec()] = value
+ return field
+
+def _ResolveNumericEnumValues(enum_fields):
+ """
+ Given a reference to a list of mojom.EnumField, resolves and assigns their
+ values to EnumField.numeric_value.
+ """
+
+ # map of <name> -> integral value
+ resolved_enum_values = {}
+ prev_value = -1
+ for field in enum_fields:
+ # This enum value is +1 the previous enum value (e.g: BEGIN).
+ if field.value is None:
+ prev_value += 1
+
+ # Integral value (e.g: BEGIN = -0x1).
+ elif type(field.value) is str:
+ prev_value = int(field.value, 0)
+
+ # Reference to a previous enum value (e.g: INIT = BEGIN).
+ elif type(field.value) is mojom.EnumValue:
+ prev_value = resolved_enum_values[field.value.name]
+ else:
+ raise Exception("Unresolved enum value.")
+
+ resolved_enum_values[field.name] = prev_value
+ field.numeric_value = prev_value
+
+def _Enum(module, parsed_enum, parent_kind):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_enum: {ast.Enum} Parsed enum.
+
+ Returns:
+ {mojom.Enum} AST enum.
+ """
+ enum = mojom.Enum(module=module)
+ enum.name = parsed_enum.name
+ enum.native_only = parsed_enum.enum_value_list is None
+ name = enum.name
+ if parent_kind:
+ name = parent_kind.name + '.' + name
+ enum.spec = 'x:%s.%s' % (module.namespace, name)
+ enum.parent_kind = parent_kind
+ enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
+ if enum.native_only:
+ enum.fields = []
+ else:
+ enum.fields = map(
+ lambda field: _EnumField(module, enum, field, parent_kind),
+ parsed_enum.enum_value_list)
+ _ResolveNumericEnumValues(enum.fields)
+
+ module.kinds[enum.spec] = enum
+
+ # Enforce that a [Native] attribute is set to make native-only enum
+ # declarations more explicit.
+ if enum.native_only:
+ if not enum.attributes or not enum.attributes.get('Native', False):
+ raise Exception("Native-only enum declarations must include a " +
+ "Native attribute.")
+
+ return enum
+
+def _Constant(module, parsed_const, parent_kind):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_const: {ast.Const} Parsed constant.
+
+ Returns:
+ {mojom.Constant} AST constant.
+ """
+ constant = mojom.Constant()
+ constant.name = parsed_const.name
+ if parent_kind:
+ scope = (module.namespace, parent_kind.name)
+ else:
+ scope = (module.namespace, )
+ # TODO(mpcomplete): maybe we should only support POD kinds.
+ constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope)
+ constant.parent_kind = parent_kind
+ constant.value = _FixupExpression(module, parsed_const.value, scope, None)
+
+ value = mojom.ConstantValue(module, parent_kind, constant)
+ module.values[value.GetSpec()] = value
+ return constant
+
+def _Module(tree, name, imports):
+ """
+ Args:
+ tree: {ast.Mojom} The parse tree.
+ name: {str} The mojom filename, excluding the path.
+ imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
+ the import list, to already processed modules. Used to process imports.
+
+ Returns:
+ {mojom.Module} An AST for the mojom.
+ """
+ module = mojom.Module()
+ module.kinds = {}
+ for kind in mojom.PRIMITIVES:
+ module.kinds[kind.spec] = kind
+
+ module.values = {}
+
+ module.name = name
+ module.namespace = tree.module.name[1] if tree.module else ''
+ # Imports must come first, because they add to module.kinds which is used
+ # by by the others.
+ module.imports = [
+ _Import(module, imports[imp.import_filename])
+ for imp in tree.import_list]
+ if tree.module and tree.module.attribute_list:
+ assert isinstance(tree.module.attribute_list, ast.AttributeList)
+ # TODO(vtl): Check for duplicate keys here.
+ module.attributes = dict((attribute.key, attribute.value)
+ for attribute in tree.module.attribute_list)
+
+ # First pass collects kinds.
+ module.enums = map(
+ lambda enum: _Enum(module, enum, None),
+ _ElemsOfType(tree.definition_list, ast.Enum, name))
+ module.structs = map(
+ lambda struct: _Struct(module, struct),
+ _ElemsOfType(tree.definition_list, ast.Struct, name))
+ module.unions = map(
+ lambda union: _Union(module, union),
+ _ElemsOfType(tree.definition_list, ast.Union, name))
+ module.interfaces = map(
+ lambda interface: _Interface(module, interface),
+ _ElemsOfType(tree.definition_list, ast.Interface, name))
+ module.constants = map(
+ lambda constant: _Constant(module, constant, None),
+ _ElemsOfType(tree.definition_list, ast.Const, name))
+
+ # Second pass expands fields and methods. This allows fields and parameters
+ # to refer to kinds defined anywhere in the mojom.
+ for struct in module.structs:
+ struct.fields = map(lambda field:
+ _StructField(module, field, struct), struct.fields_data)
+ del struct.fields_data
+ for union in module.unions:
+ union.fields = map(lambda field:
+ _UnionField(module, field, union), union.fields_data)
+ del union.fields_data
+ for interface in module.interfaces:
+ interface.methods = map(lambda method:
+ _Method(module, method, interface), interface.methods_data)
+ del interface.methods_data
+
+ return module
+
+def OrderedModule(tree, name, imports):
+ """Convert parse tree to AST module.
+
+ Args:
+ tree: {ast.Mojom} The parse tree.
+ name: {str} The mojom filename, excluding the path.
+ imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
+ the import list, to already processed modules. Used to process imports.
+
+ Returns:
+ {mojom.Module} An AST for the mojom.
+ """
+ module = _Module(tree, name, imports)
+ for interface in module.interfaces:
+ next_ordinal = 0
+ for method in interface.methods:
+ if method.ordinal is None:
+ method.ordinal = next_ordinal
+ next_ordinal = method.ordinal + 1
+ return module
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/ast.py b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
new file mode 100644
index 0000000000..2c6b5fcdf8
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
@@ -0,0 +1,410 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Node classes for the AST for a Mojo IDL file."""
+
+# Note: For convenience of testing, you probably want to define __eq__() methods
+# for all node types; it's okay to be slightly lax (e.g., not compare filename
+# and lineno). You may also define __repr__() to help with analyzing test
+# failures, especially for more complex types.
+
+
+class NodeBase(object):
+ """Base class for nodes in the AST."""
+
+ def __init__(self, filename=None, lineno=None):
+ self.filename = filename
+ self.lineno = lineno
+
+ def __eq__(self, other):
+ return type(self) == type(other)
+
+ # Make != the inverse of ==. (Subclasses shouldn't have to override this.)
+ def __ne__(self, other):
+ return not self == other
+
+
+# TODO(vtl): Some of this is complicated enough that it should be tested.
+class NodeListBase(NodeBase):
+ """Represents a list of other nodes, all having the same type. (This is meant
+ to be subclassed, with subclasses defining _list_item_type to be the class (or
+ classes, in a tuple) of the members of the list.)"""
+
+ def __init__(self, item_or_items=None, **kwargs):
+ super(NodeListBase, self).__init__(**kwargs)
+ self.items = []
+ if item_or_items is None:
+ pass
+ elif isinstance(item_or_items, list):
+ for item in item_or_items:
+ assert isinstance(item, self._list_item_type)
+ self.Append(item)
+ else:
+ assert isinstance(item_or_items, self._list_item_type)
+ self.Append(item_or_items)
+
+ # Support iteration. For everything else, users should just access |items|
+ # directly. (We intentionally do NOT supply |__len__()| or |__nonzero__()|, so
+ # |bool(NodeListBase())| is true.)
+ def __iter__(self):
+ return self.items.__iter__()
+
+ def __eq__(self, other):
+ return super(NodeListBase, self).__eq__(other) and \
+ self.items == other.items
+
+ # Implement this so that on failure, we get slightly more sensible output.
+ def __repr__(self):
+ return self.__class__.__name__ + "([" + \
+ ", ".join([repr(elem) for elem in self.items]) + "])"
+
+ def Insert(self, item):
+ """Inserts item at the front of the list."""
+
+ assert isinstance(item, self._list_item_type)
+ self.items.insert(0, item)
+ self._UpdateFilenameAndLineno()
+
+ def Append(self, item):
+ """Appends item to the end of the list."""
+
+ assert isinstance(item, self._list_item_type)
+ self.items.append(item)
+ self._UpdateFilenameAndLineno()
+
+ def _UpdateFilenameAndLineno(self):
+ if self.items:
+ self.filename = self.items[0].filename
+ self.lineno = self.items[0].lineno
+
+
+class Definition(NodeBase):
+ """Represents a definition of anything that has a global name (e.g., enums,
+ enum values, consts, structs, struct fields, interfaces). (This does not
+ include parameter definitions.) This class is meant to be subclassed."""
+
+ def __init__(self, name, **kwargs):
+ assert isinstance(name, str)
+ NodeBase.__init__(self, **kwargs)
+ self.name = name
+
+
+################################################################################
+
+
+class Attribute(NodeBase):
+ """Represents an attribute."""
+
+ def __init__(self, key, value, **kwargs):
+ assert isinstance(key, str)
+ super(Attribute, self).__init__(**kwargs)
+ self.key = key
+ self.value = value
+
+ def __eq__(self, other):
+ return super(Attribute, self).__eq__(other) and \
+ self.key == other.key and \
+ self.value == other.value
+
+
+class AttributeList(NodeListBase):
+ """Represents a list attributes."""
+
+ _list_item_type = Attribute
+
+
+class Const(Definition):
+ """Represents a const definition."""
+
+ def __init__(self, name, typename, value, **kwargs):
+ # The typename is currently passed through as a string.
+ assert isinstance(typename, str)
+ # The value is either a literal (currently passed through as a string) or a
+ # "wrapped identifier".
+ assert isinstance(value, str) or isinstance(value, tuple)
+ super(Const, self).__init__(name, **kwargs)
+ self.typename = typename
+ self.value = value
+
+ def __eq__(self, other):
+ return super(Const, self).__eq__(other) and \
+ self.typename == other.typename and \
+ self.value == other.value
+
+
+class Enum(Definition):
+ """Represents an enum definition."""
+
+ def __init__(self, name, attribute_list, enum_value_list, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
+ super(Enum, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.enum_value_list = enum_value_list
+
+ def __eq__(self, other):
+ return super(Enum, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.enum_value_list == other.enum_value_list
+
+
+class EnumValue(Definition):
+ """Represents a definition of an enum value."""
+
+ def __init__(self, name, attribute_list, value, **kwargs):
+ # The optional value is either an int (which is current a string) or a
+ # "wrapped identifier".
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert value is None or isinstance(value, (str, tuple))
+ super(EnumValue, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.value = value
+
+ def __eq__(self, other):
+ return super(EnumValue, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.value == other.value
+
+
+class EnumValueList(NodeListBase):
+ """Represents a list of enum value definitions (i.e., the "body" of an enum
+ definition)."""
+
+ _list_item_type = EnumValue
+
+
+class Import(NodeBase):
+ """Represents an import statement."""
+
+ def __init__(self, import_filename, **kwargs):
+ assert isinstance(import_filename, str)
+ super(Import, self).__init__(**kwargs)
+ self.import_filename = import_filename
+
+ def __eq__(self, other):
+ return super(Import, self).__eq__(other) and \
+ self.import_filename == other.import_filename
+
+
+class ImportList(NodeListBase):
+ """Represents a list (i.e., sequence) of import statements."""
+
+ _list_item_type = Import
+
+
+class Interface(Definition):
+ """Represents an interface definition."""
+
+ def __init__(self, name, attribute_list, body, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert isinstance(body, InterfaceBody)
+ super(Interface, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.body = body
+
+ def __eq__(self, other):
+ return super(Interface, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.body == other.body
+
+
+class Method(Definition):
+ """Represents a method definition."""
+
+ def __init__(self, name, attribute_list, ordinal, parameter_list,
+ response_parameter_list, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(parameter_list, ParameterList)
+ assert response_parameter_list is None or \
+ isinstance(response_parameter_list, ParameterList)
+ super(Method, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.ordinal = ordinal
+ self.parameter_list = parameter_list
+ self.response_parameter_list = response_parameter_list
+
+ def __eq__(self, other):
+ return super(Method, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.ordinal == other.ordinal and \
+ self.parameter_list == other.parameter_list and \
+ self.response_parameter_list == other.response_parameter_list
+
+
+# This needs to be declared after |Method|.
+class InterfaceBody(NodeListBase):
+ """Represents the body of (i.e., list of definitions inside) an interface."""
+
+ _list_item_type = (Const, Enum, Method)
+
+
+class Module(NodeBase):
+ """Represents a module statement."""
+
+ def __init__(self, name, attribute_list, **kwargs):
+ # |name| is either none or a "wrapped identifier".
+ assert name is None or isinstance(name, tuple)
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ super(Module, self).__init__(**kwargs)
+ self.name = name
+ self.attribute_list = attribute_list
+
+ def __eq__(self, other):
+ return super(Module, self).__eq__(other) and \
+ self.name == other.name and \
+ self.attribute_list == other.attribute_list
+
+
+class Mojom(NodeBase):
+ """Represents an entire .mojom file. (This is the root node.)"""
+
+ def __init__(self, module, import_list, definition_list, **kwargs):
+ assert module is None or isinstance(module, Module)
+ assert isinstance(import_list, ImportList)
+ assert isinstance(definition_list, list)
+ super(Mojom, self).__init__(**kwargs)
+ self.module = module
+ self.import_list = import_list
+ self.definition_list = definition_list
+
+ def __eq__(self, other):
+ return super(Mojom, self).__eq__(other) and \
+ self.module == other.module and \
+ self.import_list == other.import_list and \
+ self.definition_list == other.definition_list
+
+ def __repr__(self):
+ return "%s(%r, %r, %r)" % (self.__class__.__name__, self.module,
+ self.import_list, self.definition_list)
+
+
+class Ordinal(NodeBase):
+ """Represents an ordinal value labeling, e.g., a struct field."""
+
+ def __init__(self, value, **kwargs):
+ assert isinstance(value, int)
+ super(Ordinal, self).__init__(**kwargs)
+ self.value = value
+
+ def __eq__(self, other):
+ return super(Ordinal, self).__eq__(other) and \
+ self.value == other.value
+
+
+class Parameter(NodeBase):
+ """Represents a method request or response parameter."""
+
+ def __init__(self, name, attribute_list, ordinal, typename, **kwargs):
+ assert isinstance(name, str)
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(typename, str)
+ super(Parameter, self).__init__(**kwargs)
+ self.name = name
+ self.attribute_list = attribute_list
+ self.ordinal = ordinal
+ self.typename = typename
+
+ def __eq__(self, other):
+ return super(Parameter, self).__eq__(other) and \
+ self.name == other.name and \
+ self.attribute_list == other.attribute_list and \
+ self.ordinal == other.ordinal and \
+ self.typename == other.typename
+
+
+class ParameterList(NodeListBase):
+ """Represents a list of (method request or response) parameters."""
+
+ _list_item_type = Parameter
+
+
+class Struct(Definition):
+ """Represents a struct definition."""
+
+ def __init__(self, name, attribute_list, body, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert isinstance(body, StructBody) or body is None
+ super(Struct, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.body = body
+
+ def __eq__(self, other):
+ return super(Struct, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.body == other.body
+
+
+class StructField(Definition):
+ """Represents a struct field definition."""
+
+ def __init__(self, name, attribute_list, ordinal, typename, default_value,
+ **kwargs):
+ assert isinstance(name, str)
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(typename, str)
+ # The optional default value is currently either a value as a string or a
+ # "wrapped identifier".
+ assert default_value is None or isinstance(default_value, (str, tuple))
+ super(StructField, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.ordinal = ordinal
+ self.typename = typename
+ self.default_value = default_value
+
+ def __eq__(self, other):
+ return super(StructField, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.ordinal == other.ordinal and \
+ self.typename == other.typename and \
+ self.default_value == other.default_value
+
+
+# This needs to be declared after |StructField|.
+class StructBody(NodeListBase):
+ """Represents the body of (i.e., list of definitions inside) a struct."""
+
+ _list_item_type = (Const, Enum, StructField)
+
+
+class Union(Definition):
+ """Represents a union definition."""
+
+ def __init__(self, name, attribute_list, body, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert isinstance(body, UnionBody)
+ super(Union, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.body = body
+
+ def __eq__(self, other):
+ return super(Union, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.body == other.body
+
+
+class UnionField(Definition):
+
+ def __init__(self, name, attribute_list, ordinal, typename, **kwargs):
+ assert isinstance(name, str)
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(typename, str)
+ super(UnionField, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.ordinal = ordinal
+ self.typename = typename
+
+ def __eq__(self, other):
+ return super(UnionField, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.ordinal == other.ordinal and \
+ self.typename == other.typename
+
+
+class UnionBody(NodeListBase):
+
+ _list_item_type = UnionField
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
new file mode 100644
index 0000000000..06354b1d85
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
@@ -0,0 +1,254 @@
+# Copyright 2014 The Chromium 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 imp
+import os.path
+import sys
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("ply")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+from ply.lex import TOKEN
+
+from ..error import Error
+
+
+class LexError(Error):
+ """Class for errors from the lexer."""
+
+ def __init__(self, filename, message, lineno):
+ Error.__init__(self, filename, message, lineno=lineno)
+
+
+# We have methods which look like they could be functions:
+# pylint: disable=R0201
+class Lexer(object):
+
+ def __init__(self, filename):
+ self.filename = filename
+
+ ######################-- PRIVATE --######################
+
+ ##
+ ## Internal auxiliary methods
+ ##
+ def _error(self, msg, token):
+ raise LexError(self.filename, msg, token.lineno)
+
+ ##
+ ## Reserved keywords
+ ##
+ keywords = (
+ 'HANDLE',
+
+ 'IMPORT',
+ 'MODULE',
+ 'STRUCT',
+ 'UNION',
+ 'INTERFACE',
+ 'ENUM',
+ 'CONST',
+ 'TRUE',
+ 'FALSE',
+ 'DEFAULT',
+ 'ARRAY',
+ 'MAP',
+ 'ASSOCIATED'
+ )
+
+ keyword_map = {}
+ for keyword in keywords:
+ keyword_map[keyword.lower()] = keyword
+
+ ##
+ ## All the tokens recognized by the lexer
+ ##
+ tokens = keywords + (
+ # Identifiers
+ 'NAME',
+
+ # Constants
+ 'ORDINAL',
+ 'INT_CONST_DEC', 'INT_CONST_HEX',
+ 'FLOAT_CONST',
+
+ # String literals
+ 'STRING_LITERAL',
+
+ # Operators
+ 'MINUS',
+ 'PLUS',
+ 'AMP',
+ 'QSTN',
+
+ # Assignment
+ 'EQUALS',
+
+ # Request / response
+ 'RESPONSE',
+
+ # Delimiters
+ 'LPAREN', 'RPAREN', # ( )
+ 'LBRACKET', 'RBRACKET', # [ ]
+ 'LBRACE', 'RBRACE', # { }
+ 'LANGLE', 'RANGLE', # < >
+ 'SEMI', # ;
+ 'COMMA', 'DOT' # , .
+ )
+
+ ##
+ ## Regexes for use in tokens
+ ##
+
+ # valid C identifiers (K&R2: A.2.3)
+ identifier = r'[a-zA-Z_][0-9a-zA-Z_]*'
+
+ hex_prefix = '0[xX]'
+ hex_digits = '[0-9a-fA-F]+'
+
+ # integer constants (K&R2: A.2.5.1)
+ decimal_constant = '0|([1-9][0-9]*)'
+ hex_constant = hex_prefix+hex_digits
+ # Don't allow octal constants (even invalid octal).
+ octal_constant_disallowed = '0[0-9]+'
+
+ # character constants (K&R2: A.2.5.2)
+ # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line
+ # directives with Windows paths as filenames (..\..\dir\file)
+ # For the same reason, decimal_escape allows all digit sequences. We want to
+ # parse all correct code, even if it means to sometimes parse incorrect
+ # code.
+ #
+ simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])"""
+ decimal_escape = r"""(\d+)"""
+ hex_escape = r"""(x[0-9a-fA-F]+)"""
+ bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])"""
+
+ escape_sequence = \
+ r"""(\\("""+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))'
+
+ # string literals (K&R2: A.2.6)
+ string_char = r"""([^"\\\n]|"""+escape_sequence+')'
+ string_literal = '"'+string_char+'*"'
+ bad_string_literal = '"'+string_char+'*'+bad_escape+string_char+'*"'
+
+ # floating constants (K&R2: A.2.5.3)
+ exponent_part = r"""([eE][-+]?[0-9]+)"""
+ fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)"""
+ floating_constant = \
+ '(((('+fractional_constant+')'+ \
+ exponent_part+'?)|([0-9]+'+exponent_part+')))'
+
+ # Ordinals
+ ordinal = r'@[0-9]+'
+ missing_ordinal_value = r'@'
+ # Don't allow ordinal values in octal (even invalid octal, like 09) or
+ # hexadecimal.
+ octal_or_hex_ordinal_disallowed = r'@((0[0-9]+)|('+hex_prefix+hex_digits+'))'
+
+ ##
+ ## Rules for the normal state
+ ##
+ t_ignore = ' \t\r'
+
+ # Newlines
+ def t_NEWLINE(self, t):
+ r'\n+'
+ t.lexer.lineno += len(t.value)
+
+ # Operators
+ t_MINUS = r'-'
+ t_PLUS = r'\+'
+ t_AMP = r'&'
+ t_QSTN = r'\?'
+
+ # =
+ t_EQUALS = r'='
+
+ # =>
+ t_RESPONSE = r'=>'
+
+ # Delimiters
+ t_LPAREN = r'\('
+ t_RPAREN = r'\)'
+ t_LBRACKET = r'\['
+ t_RBRACKET = r'\]'
+ t_LBRACE = r'\{'
+ t_RBRACE = r'\}'
+ t_LANGLE = r'<'
+ t_RANGLE = r'>'
+ t_COMMA = r','
+ t_DOT = r'\.'
+ t_SEMI = r';'
+
+ t_STRING_LITERAL = string_literal
+
+ # The following floating and integer constants are defined as
+ # functions to impose a strict order (otherwise, decimal
+ # is placed before the others because its regex is longer,
+ # and this is bad)
+ #
+ @TOKEN(floating_constant)
+ def t_FLOAT_CONST(self, t):
+ return t
+
+ @TOKEN(hex_constant)
+ def t_INT_CONST_HEX(self, t):
+ return t
+
+ @TOKEN(octal_constant_disallowed)
+ def t_OCTAL_CONSTANT_DISALLOWED(self, t):
+ msg = "Octal values not allowed"
+ self._error(msg, t)
+
+ @TOKEN(decimal_constant)
+ def t_INT_CONST_DEC(self, t):
+ return t
+
+ # unmatched string literals are caught by the preprocessor
+
+ @TOKEN(bad_string_literal)
+ def t_BAD_STRING_LITERAL(self, t):
+ msg = "String contains invalid escape code"
+ self._error(msg, t)
+
+ # Handle ordinal-related tokens in the right order:
+ @TOKEN(octal_or_hex_ordinal_disallowed)
+ def t_OCTAL_OR_HEX_ORDINAL_DISALLOWED(self, t):
+ msg = "Octal and hexadecimal ordinal values not allowed"
+ self._error(msg, t)
+
+ @TOKEN(ordinal)
+ def t_ORDINAL(self, t):
+ return t
+
+ @TOKEN(missing_ordinal_value)
+ def t_BAD_ORDINAL(self, t):
+ msg = "Missing ordinal value"
+ self._error(msg, t)
+
+ @TOKEN(identifier)
+ def t_NAME(self, t):
+ t.type = self.keyword_map.get(t.value, "NAME")
+ return t
+
+ # Ignore C and C++ style comments
+ def t_COMMENT(self, t):
+ r'(/\*(.|\n)*?\*/)|(//.*(\n[ \t]*//.*)*)'
+ t.lexer.lineno += t.value.count("\n")
+
+ def t_error(self, t):
+ msg = "Illegal character %s" % repr(t.value[0])
+ self._error(msg, t)
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
new file mode 100644
index 0000000000..868fb45f33
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
@@ -0,0 +1,461 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates a syntax tree from a Mojo IDL file."""
+
+import imp
+import os.path
+import sys
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("ply")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+from ply import lex
+from ply import yacc
+
+from ..error import Error
+from . import ast
+from .lexer import Lexer
+
+
+_MAX_ORDINAL_VALUE = 0xffffffff
+_MAX_ARRAY_SIZE = 0xffffffff
+
+
+class ParseError(Error):
+ """Class for errors from the parser."""
+
+ def __init__(self, filename, message, lineno=None, snippet=None):
+ Error.__init__(self, filename, message, lineno=lineno,
+ addenda=([snippet] if snippet else None))
+
+
+# We have methods which look like they could be functions:
+# pylint: disable=R0201
+class Parser(object):
+
+ def __init__(self, lexer, source, filename):
+ self.tokens = lexer.tokens
+ self.source = source
+ self.filename = filename
+
+ # Names of functions
+ #
+ # In general, we name functions after the left-hand-side of the rule(s) that
+ # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|.
+ #
+ # There may be multiple functions handling rules for the same left-hand-side;
+ # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|),
+ # where N is a number (numbered starting from 1). Note that using multiple
+ # functions is actually more efficient than having single functions handle
+ # multiple rules (and, e.g., distinguishing them by examining |len(p)|).
+ #
+ # It's also possible to have a function handling multiple rules with different
+ # left-hand-sides. We do not do this.
+ #
+ # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details.
+
+ # TODO(vtl): Get rid of the braces in the module "statement". (Consider
+ # renaming "module" -> "package".) Then we'll be able to have a single rule
+ # for root (by making module "optional").
+ def p_root_1(self, p):
+ """root : """
+ p[0] = ast.Mojom(None, ast.ImportList(), [])
+
+ def p_root_2(self, p):
+ """root : root module"""
+ if p[1].module is not None:
+ raise ParseError(self.filename,
+ "Multiple \"module\" statements not allowed:",
+ p[2].lineno, snippet=self._GetSnippet(p[2].lineno))
+ if p[1].import_list.items or p[1].definition_list:
+ raise ParseError(
+ self.filename,
+ "\"module\" statements must precede imports and definitions:",
+ p[2].lineno, snippet=self._GetSnippet(p[2].lineno))
+ p[0] = p[1]
+ p[0].module = p[2]
+
+ def p_root_3(self, p):
+ """root : root import"""
+ if p[1].definition_list:
+ raise ParseError(self.filename,
+ "\"import\" statements must precede definitions:",
+ p[2].lineno, snippet=self._GetSnippet(p[2].lineno))
+ p[0] = p[1]
+ p[0].import_list.Append(p[2])
+
+ def p_root_4(self, p):
+ """root : root definition"""
+ p[0] = p[1]
+ p[0].definition_list.append(p[2])
+
+ def p_import(self, p):
+ """import : IMPORT STRING_LITERAL SEMI"""
+ # 'eval' the literal to strip the quotes.
+ # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.
+ p[0] = ast.Import(eval(p[2]), filename=self.filename, lineno=p.lineno(2))
+
+ def p_module(self, p):
+ """module : attribute_section MODULE identifier_wrapped SEMI"""
+ p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2))
+
+ def p_definition(self, p):
+ """definition : struct
+ | union
+ | interface
+ | enum
+ | const"""
+ p[0] = p[1]
+
+ def p_attribute_section_1(self, p):
+ """attribute_section : """
+ p[0] = None
+
+ def p_attribute_section_2(self, p):
+ """attribute_section : LBRACKET attribute_list RBRACKET"""
+ p[0] = p[2]
+
+ def p_attribute_list_1(self, p):
+ """attribute_list : """
+ p[0] = ast.AttributeList()
+
+ def p_attribute_list_2(self, p):
+ """attribute_list : nonempty_attribute_list"""
+ p[0] = p[1]
+
+ def p_nonempty_attribute_list_1(self, p):
+ """nonempty_attribute_list : attribute"""
+ p[0] = ast.AttributeList(p[1])
+
+ def p_nonempty_attribute_list_2(self, p):
+ """nonempty_attribute_list : nonempty_attribute_list COMMA attribute"""
+ p[0] = p[1]
+ p[0].Append(p[3])
+
+ def p_attribute_1(self, p):
+ """attribute : NAME EQUALS evaled_literal
+ | NAME EQUALS NAME"""
+ p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
+
+ def p_attribute_2(self, p):
+ """attribute : NAME"""
+ p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
+
+ def p_evaled_literal(self, p):
+ """evaled_literal : literal"""
+ # 'eval' the literal to strip the quotes. Handle keywords "true" and "false"
+ # specially since they cannot directly be evaluated to python boolean
+ # values.
+ if p[1] == "true":
+ p[0] = True
+ elif p[1] == "false":
+ p[0] = False
+ else:
+ p[0] = eval(p[1])
+
+ def p_struct_1(self, p):
+ """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
+ p[0] = ast.Struct(p[3], p[1], p[5])
+
+ def p_struct_2(self, p):
+ """struct : attribute_section STRUCT NAME SEMI"""
+ p[0] = ast.Struct(p[3], p[1], None)
+
+ def p_struct_body_1(self, p):
+ """struct_body : """
+ p[0] = ast.StructBody()
+
+ def p_struct_body_2(self, p):
+ """struct_body : struct_body const
+ | struct_body enum
+ | struct_body struct_field"""
+ p[0] = p[1]
+ p[0].Append(p[2])
+
+ def p_struct_field(self, p):
+ """struct_field : attribute_section typename NAME ordinal default SEMI"""
+ p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])
+
+ def p_union(self, p):
+ """union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI"""
+ p[0] = ast.Union(p[3], p[1], p[5])
+
+ def p_union_body_1(self, p):
+ """union_body : """
+ p[0] = ast.UnionBody()
+
+ def p_union_body_2(self, p):
+ """union_body : union_body union_field"""
+ p[0] = p[1]
+ p[1].Append(p[2])
+
+ def p_union_field(self, p):
+ """union_field : attribute_section typename NAME ordinal SEMI"""
+ p[0] = ast.UnionField(p[3], p[1], p[4], p[2])
+
+ def p_default_1(self, p):
+ """default : """
+ p[0] = None
+
+ def p_default_2(self, p):
+ """default : EQUALS constant"""
+ p[0] = p[2]
+
+ def p_interface(self, p):
+ """interface : attribute_section INTERFACE NAME LBRACE interface_body \
+ RBRACE SEMI"""
+ p[0] = ast.Interface(p[3], p[1], p[5])
+
+ def p_interface_body_1(self, p):
+ """interface_body : """
+ p[0] = ast.InterfaceBody()
+
+ def p_interface_body_2(self, p):
+ """interface_body : interface_body const
+ | interface_body enum
+ | interface_body method"""
+ p[0] = p[1]
+ p[0].Append(p[2])
+
+ def p_response_1(self, p):
+ """response : """
+ p[0] = None
+
+ def p_response_2(self, p):
+ """response : RESPONSE LPAREN parameter_list RPAREN"""
+ p[0] = p[3]
+
+ def p_method(self, p):
+ """method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \
+ response SEMI"""
+ p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])
+
+ def p_parameter_list_1(self, p):
+ """parameter_list : """
+ p[0] = ast.ParameterList()
+
+ def p_parameter_list_2(self, p):
+ """parameter_list : nonempty_parameter_list"""
+ p[0] = p[1]
+
+ def p_nonempty_parameter_list_1(self, p):
+ """nonempty_parameter_list : parameter"""
+ p[0] = ast.ParameterList(p[1])
+
+ def p_nonempty_parameter_list_2(self, p):
+ """nonempty_parameter_list : nonempty_parameter_list COMMA parameter"""
+ p[0] = p[1]
+ p[0].Append(p[3])
+
+ def p_parameter(self, p):
+ """parameter : attribute_section typename NAME ordinal"""
+ p[0] = ast.Parameter(p[3], p[1], p[4], p[2],
+ filename=self.filename, lineno=p.lineno(3))
+
+ def p_typename(self, p):
+ """typename : nonnullable_typename QSTN
+ | nonnullable_typename"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = p[1] + "?"
+
+ def p_nonnullable_typename(self, p):
+ """nonnullable_typename : basictypename
+ | array
+ | fixed_array
+ | associative_array
+ | interfacerequest"""
+ p[0] = p[1]
+
+ def p_basictypename(self, p):
+ """basictypename : identifier
+ | ASSOCIATED identifier
+ | handletype"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = "asso<" + p[2] + ">"
+
+ def p_handletype(self, p):
+ """handletype : HANDLE
+ | HANDLE LANGLE NAME RANGLE"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ if p[3] not in ('data_pipe_consumer',
+ 'data_pipe_producer',
+ 'message_pipe',
+ 'shared_buffer'):
+ # Note: We don't enable tracking of line numbers for everything, so we
+ # can't use |p.lineno(3)|.
+ raise ParseError(self.filename, "Invalid handle type %r:" % p[3],
+ lineno=p.lineno(1),
+ snippet=self._GetSnippet(p.lineno(1)))
+ p[0] = "handle<" + p[3] + ">"
+
+ def p_array(self, p):
+ """array : ARRAY LANGLE typename RANGLE"""
+ p[0] = p[3] + "[]"
+
+ def p_fixed_array(self, p):
+ """fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE"""
+ value = int(p[5])
+ if value == 0 or value > _MAX_ARRAY_SIZE:
+ raise ParseError(self.filename, "Fixed array size %d invalid:" % value,
+ lineno=p.lineno(5),
+ snippet=self._GetSnippet(p.lineno(5)))
+ p[0] = p[3] + "[" + p[5] + "]"
+
+ def p_associative_array(self, p):
+ """associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
+ p[0] = p[5] + "{" + p[3] + "}"
+
+ def p_interfacerequest(self, p):
+ """interfacerequest : identifier AMP
+ | ASSOCIATED identifier AMP"""
+ if len(p) == 3:
+ p[0] = p[1] + "&"
+ else:
+ p[0] = "asso<" + p[2] + "&>"
+
+ def p_ordinal_1(self, p):
+ """ordinal : """
+ p[0] = None
+
+ def p_ordinal_2(self, p):
+ """ordinal : ORDINAL"""
+ value = int(p[1][1:])
+ if value > _MAX_ORDINAL_VALUE:
+ raise ParseError(self.filename, "Ordinal value %d too large:" % value,
+ lineno=p.lineno(1),
+ snippet=self._GetSnippet(p.lineno(1)))
+ p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
+
+ def p_enum_1(self, p):
+ """enum : attribute_section ENUM NAME LBRACE enum_value_list \
+ RBRACE SEMI
+ | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \
+ COMMA RBRACE SEMI"""
+ p[0] = ast.Enum(p[3], p[1], p[5], filename=self.filename,
+ lineno=p.lineno(2))
+
+ def p_enum_2(self, p):
+ """enum : attribute_section ENUM NAME SEMI"""
+ p[0] = ast.Enum(p[3], p[1], None, filename=self.filename,
+ lineno=p.lineno(2))
+
+ def p_enum_value_list_1(self, p):
+ """enum_value_list : """
+ p[0] = ast.EnumValueList()
+
+ def p_enum_value_list_2(self, p):
+ """enum_value_list : nonempty_enum_value_list"""
+ p[0] = p[1]
+
+ def p_nonempty_enum_value_list_1(self, p):
+ """nonempty_enum_value_list : enum_value"""
+ p[0] = ast.EnumValueList(p[1])
+
+ def p_nonempty_enum_value_list_2(self, p):
+ """nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value"""
+ p[0] = p[1]
+ p[0].Append(p[3])
+
+ def p_enum_value(self, p):
+ """enum_value : attribute_section NAME
+ | attribute_section NAME EQUALS int
+ | attribute_section NAME EQUALS identifier_wrapped"""
+ p[0] = ast.EnumValue(p[2], p[1], p[4] if len(p) == 5 else None,
+ filename=self.filename, lineno=p.lineno(2))
+
+ def p_const(self, p):
+ """const : CONST typename NAME EQUALS constant SEMI"""
+ p[0] = ast.Const(p[3], p[2], p[5])
+
+ def p_constant(self, p):
+ """constant : literal
+ | identifier_wrapped"""
+ p[0] = p[1]
+
+ def p_identifier_wrapped(self, p):
+ """identifier_wrapped : identifier"""
+ p[0] = ('IDENTIFIER', p[1])
+
+ # TODO(vtl): Make this produce a "wrapped" identifier (probably as an
+ # |ast.Identifier|, to be added) and get rid of identifier_wrapped.
+ def p_identifier(self, p):
+ """identifier : NAME
+ | NAME DOT identifier"""
+ p[0] = ''.join(p[1:])
+
+ def p_literal(self, p):
+ """literal : int
+ | float
+ | TRUE
+ | FALSE
+ | DEFAULT
+ | STRING_LITERAL"""
+ p[0] = p[1]
+
+ def p_int(self, p):
+ """int : int_const
+ | PLUS int_const
+ | MINUS int_const"""
+ p[0] = ''.join(p[1:])
+
+ def p_int_const(self, p):
+ """int_const : INT_CONST_DEC
+ | INT_CONST_HEX"""
+ p[0] = p[1]
+
+ def p_float(self, p):
+ """float : FLOAT_CONST
+ | PLUS FLOAT_CONST
+ | MINUS FLOAT_CONST"""
+ p[0] = ''.join(p[1:])
+
+ def p_error(self, e):
+ if e is None:
+ # Unexpected EOF.
+ # TODO(vtl): Can we figure out what's missing?
+ raise ParseError(self.filename, "Unexpected end of file")
+
+ raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno,
+ snippet=self._GetSnippet(e.lineno))
+
+ def _GetSnippet(self, lineno):
+ return self.source.split('\n')[lineno - 1]
+
+
+def Parse(source, filename):
+ """Parse source file to AST.
+
+ Args:
+ source: The source text as a str.
+ filename: The filename that |source| originates from.
+
+ Returns:
+ The AST as a mojom.parse.ast.Mojom object.
+ """
+ lexer = Lexer(filename)
+ parser = Parser(lexer, source, filename)
+
+ lex.lex(object=lexer)
+ yacc.yacc(module=parser, debug=0, write_tables=0)
+
+ tree = yacc.parse(source)
+ return tree
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py
new file mode 100644
index 0000000000..d56faadb19
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py
@@ -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.
+
+import imp
+import os.path
+import shutil
+import sys
+import tempfile
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+from mojom import fileutil
+
+
+class FileUtilTest(unittest.TestCase):
+
+ def testEnsureDirectoryExists(self):
+ """Test that EnsureDirectoryExists fuctions correctly."""
+
+ temp_dir = tempfile.mkdtemp()
+ try:
+ self.assertTrue(os.path.exists(temp_dir))
+
+ # Directory does not exist, yet.
+ full = os.path.join(temp_dir, "foo", "bar")
+ self.assertFalse(os.path.exists(full))
+
+ # Create the directory.
+ fileutil.EnsureDirectoryExists(full)
+ self.assertTrue(os.path.exists(full))
+
+ # Trying to create it again does not cause an error.
+ fileutil.EnsureDirectoryExists(full)
+ self.assertTrue(os.path.exists(full))
+
+ # Bypass check for directory existence to tickle error handling that
+ # occurs in response to a race.
+ fileutil.EnsureDirectoryExists(full, always_try_to_create=True)
+ self.assertTrue(os.path.exists(full))
+ finally:
+ shutil.rmtree(temp_dir)
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py
new file mode 100644
index 0000000000..70c92a38fb
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py
@@ -0,0 +1,156 @@
+# Copyright 2014 The Chromium 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 imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+from mojom.generate import data
+from mojom.generate import module as mojom
+
+
+class DataTest(unittest.TestCase):
+
+ def testStructDataConversion(self):
+ """Tests that a struct can be converted from data."""
+ module = mojom.Module('test_module', 'test_namespace')
+ struct_data = {
+ 'name': 'SomeStruct',
+ 'enums': [],
+ 'constants': [],
+ 'fields': [
+ {'name': 'field1', 'kind': 'i32'},
+ {'name': 'field2', 'kind': 'i32', 'ordinal': 10},
+ {'name': 'field3', 'kind': 'i32', 'default': 15}]}
+
+ struct = data.StructFromData(module, struct_data)
+ struct.fields = map(lambda field:
+ data.StructFieldFromData(module, field, struct), struct.fields_data)
+ self.assertEquals(struct_data, data.StructToData(struct))
+
+ def testUnionDataConversion(self):
+ """Tests that a union can be converted from data."""
+ module = mojom.Module('test_module', 'test_namespace')
+ union_data = {
+ 'name': 'SomeUnion',
+ 'fields': [
+ {'name': 'field1', 'kind': 'i32'},
+ {'name': 'field2', 'kind': 'i32', 'ordinal': 10}]}
+
+ union = data.UnionFromData(module, union_data)
+ union.fields = map(lambda field:
+ data.UnionFieldFromData(module, field, union), union.fields_data)
+ self.assertEquals(union_data, data.UnionToData(union))
+
+ def testImportFromDataNoMissingImports(self):
+ """Tests that unions, structs, interfaces and enums are imported."""
+ module = mojom.Module('test_module', 'test_namespace')
+ imported_module = mojom.Module('import_module', 'import_namespace')
+ #TODO(azani): Init values in module.py.
+ #TODO(azani): Test that values are imported.
+ imported_module.values = {}
+ imported_data = {'module' : imported_module}
+
+
+ struct = mojom.Struct('TestStruct', module=module)
+ imported_module.kinds[struct.spec] = struct
+
+ union = mojom.Union('TestUnion', module=module)
+ imported_module.kinds[union.spec] = union
+
+ interface = mojom.Interface('TestInterface', module=module)
+ imported_module.kinds[interface.spec] = interface
+
+ enum = mojom.Enum('TestEnum', module=module)
+ imported_module.kinds[enum.spec] = enum
+
+ data.ImportFromData(module, imported_data)
+
+ # Test that the kind was imported.
+ self.assertIn(struct.spec, module.kinds)
+ self.assertEquals(struct.name, module.kinds[struct.spec].name)
+
+ self.assertIn(union.spec, module.kinds)
+ self.assertEquals(union.name, module.kinds[union.spec].name)
+
+ self.assertIn(interface.spec, module.kinds)
+ self.assertEquals(interface.name, module.kinds[interface.spec].name)
+
+ self.assertIn(enum.spec, module.kinds)
+ self.assertEquals(enum.name, module.kinds[enum.spec].name)
+
+ # Test that the imported kind is a copy and not the original.
+ self.assertIsNot(struct, module.kinds[struct.spec])
+ self.assertIsNot(union, module.kinds[union.spec])
+ self.assertIsNot(interface, module.kinds[interface.spec])
+ self.assertIsNot(enum, module.kinds[enum.spec])
+
+ def testImportFromDataNoExtraneousImports(self):
+ """Tests that arrays, maps and interface requests are not imported."""
+ module = mojom.Module('test_module', 'test_namespace')
+ imported_module = mojom.Module('import_module', 'import_namespace')
+ #TODO(azani): Init values in module.py.
+ imported_module.values = {}
+ imported_data = {'module' : imported_module}
+
+ array = mojom.Array(mojom.INT16, length=20)
+ imported_module.kinds[array.spec] = array
+
+ map_kind = mojom.Map(mojom.INT16, mojom.INT16)
+ imported_module.kinds[map_kind.spec] = map_kind
+
+ interface = mojom.Interface('TestInterface', module=module)
+ imported_module.kinds[interface.spec] = interface
+
+ interface_req = mojom.InterfaceRequest(interface)
+ imported_module.kinds[interface_req.spec] = interface_req
+
+ data.ImportFromData(module, imported_data)
+
+ self.assertNotIn(array.spec, module.kinds)
+ self.assertNotIn(map_kind.spec, module.kinds)
+ self.assertNotIn(interface_req.spec, module.kinds)
+
+ def testNonInterfaceAsInterfaceRequest(self):
+ """Tests that a non-interface cannot be used for interface requests."""
+ module = mojom.Module('test_module', 'test_namespace')
+ interface = mojom.Interface('TestInterface', module=module)
+ method_dict = {
+ 'name': 'Foo',
+ 'parameters': [{'name': 'foo', 'kind': 'r:i32'}],
+ }
+ with self.assertRaises(Exception) as e:
+ data.MethodFromData(module, method_dict, interface)
+ self.assertEquals(e.exception.__str__(),
+ 'Interface request requires \'i32\' to be an interface.')
+
+ def testNonInterfaceAsAssociatedInterface(self):
+ """Tests that a non-interface type cannot be used for associated
+ interfaces."""
+ module = mojom.Module('test_module', 'test_namespace')
+ interface = mojom.Interface('TestInterface', module=module)
+ method_dict = {
+ 'name': 'Foo',
+ 'parameters': [{'name': 'foo', 'kind': 'asso:i32'}],
+ }
+ with self.assertRaises(Exception) as e:
+ data.MethodFromData(module, method_dict, interface)
+ self.assertEquals(
+ e.exception.__str__(),
+ 'Associated interface requires \'i32\' to be an interface.')
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py
new file mode 100644
index 0000000000..a684773719
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py
@@ -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.
+
+import imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+from mojom.generate import generator
+
+
+class StringManipulationTest(unittest.TestCase):
+ """generator contains some string utilities, this tests only those."""
+
+ def testUnderToCamel(self):
+ """Tests UnderToCamel which converts underscore_separated to CamelCase."""
+ self.assertEquals("CamelCase", generator.UnderToCamel("camel_case"))
+ self.assertEquals("CamelCase", generator.UnderToCamel("CAMEL_CASE"))
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py
new file mode 100644
index 0000000000..75b7cd5437
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py
@@ -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.
+
+import imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+from mojom.generate import module as mojom
+
+
+class ModuleTest(unittest.TestCase):
+
+ def testNonInterfaceAsInterfaceRequest(self):
+ """Tests that a non-interface cannot be used for interface requests."""
+ module = mojom.Module('test_module', 'test_namespace')
+ struct = mojom.Struct('TestStruct', module=module)
+ with self.assertRaises(Exception) as e:
+ mojom.InterfaceRequest(struct)
+ self.assertEquals(
+ e.exception.__str__(),
+ 'Interface request requires \'x:TestStruct\' to be an interface.')
+
+ def testNonInterfaceAsAssociatedInterface(self):
+ """Tests that a non-interface type cannot be used for associated interfaces.
+ """
+ module = mojom.Module('test_module', 'test_namespace')
+ struct = mojom.Struct('TestStruct', module=module)
+ with self.assertRaises(Exception) as e:
+ mojom.AssociatedInterface(struct)
+ self.assertEquals(
+ e.exception.__str__(),
+ 'Associated interface requires \'x:TestStruct\' to be an interface.')
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py
new file mode 100644
index 0000000000..75f6d5163e
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py
@@ -0,0 +1,136 @@
+# Copyright 2015 The Chromium 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 imp
+import os.path
+import sys
+import unittest
+
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+from mojom.generate import pack
+from mojom.generate import module as mojom
+
+
+# TODO(yzshen): Move tests in pack_tests.py here.
+class PackTest(unittest.TestCase):
+ def _CheckPackSequence(self, kinds, fields, offsets):
+ """Checks the pack order and offsets of a sequence of mojom.Kinds.
+
+ Args:
+ kinds: A sequence of mojom.Kinds that specify the fields that are to be
+ created.
+ fields: The expected order of the resulting fields, with the integer "1"
+ first.
+ offsets: The expected order of offsets, with the integer "0" first.
+ """
+ struct = mojom.Struct('test')
+ index = 1
+ for kind in kinds:
+ struct.AddField('%d' % index, kind)
+ index += 1
+ ps = pack.PackedStruct(struct)
+ num_fields = len(ps.packed_fields)
+ self.assertEquals(len(kinds), num_fields)
+ for i in xrange(num_fields):
+ self.assertEquals('%d' % fields[i], ps.packed_fields[i].field.name)
+ self.assertEquals(offsets[i], ps.packed_fields[i].offset)
+
+ def testMinVersion(self):
+ """Tests that |min_version| is properly set for packed fields."""
+ struct = mojom.Struct('test')
+ struct.AddField('field_2', mojom.BOOL, 2)
+ struct.AddField('field_0', mojom.INT32, 0)
+ struct.AddField('field_1', mojom.INT64, 1)
+ ps = pack.PackedStruct(struct)
+
+ self.assertEquals('field_0', ps.packed_fields[0].field.name)
+ self.assertEquals('field_2', ps.packed_fields[1].field.name)
+ self.assertEquals('field_1', ps.packed_fields[2].field.name)
+
+ self.assertEquals(0, ps.packed_fields[0].min_version)
+ self.assertEquals(0, ps.packed_fields[1].min_version)
+ self.assertEquals(0, ps.packed_fields[2].min_version)
+
+ struct.fields[0].attributes = {'MinVersion': 1}
+ ps = pack.PackedStruct(struct)
+
+ self.assertEquals(0, ps.packed_fields[0].min_version)
+ self.assertEquals(1, ps.packed_fields[1].min_version)
+ self.assertEquals(0, ps.packed_fields[2].min_version)
+
+ def testGetVersionInfoEmptyStruct(self):
+ """Tests that pack.GetVersionInfo() never returns an empty list, even for
+ empty structs.
+ """
+ struct = mojom.Struct('test')
+ ps = pack.PackedStruct(struct)
+
+ versions = pack.GetVersionInfo(ps)
+ self.assertEquals(1, len(versions))
+ self.assertEquals(0, versions[0].version)
+ self.assertEquals(0, versions[0].num_fields)
+ self.assertEquals(8, versions[0].num_bytes)
+
+ def testGetVersionInfoComplexOrder(self):
+ """Tests pack.GetVersionInfo() using a struct whose definition order,
+ ordinal order and pack order for fields are all different.
+ """
+ struct = mojom.Struct('test')
+ struct.AddField('field_3', mojom.BOOL, ordinal=3,
+ attributes={'MinVersion': 3})
+ struct.AddField('field_0', mojom.INT32, ordinal=0)
+ struct.AddField('field_1', mojom.INT64, ordinal=1,
+ attributes={'MinVersion': 2})
+ struct.AddField('field_2', mojom.INT64, ordinal=2,
+ attributes={'MinVersion': 3})
+ ps = pack.PackedStruct(struct)
+
+ versions = pack.GetVersionInfo(ps)
+ self.assertEquals(3, len(versions))
+
+ self.assertEquals(0, versions[0].version)
+ self.assertEquals(1, versions[0].num_fields)
+ self.assertEquals(16, versions[0].num_bytes)
+
+ self.assertEquals(2, versions[1].version)
+ self.assertEquals(2, versions[1].num_fields)
+ self.assertEquals(24, versions[1].num_bytes)
+
+ self.assertEquals(3, versions[2].version)
+ self.assertEquals(4, versions[2].num_fields)
+ self.assertEquals(32, versions[2].num_bytes)
+
+ def testInterfaceAlignment(self):
+ """Tests that interfaces are aligned on 4-byte boundaries, although the size
+ of an interface is 8 bytes.
+ """
+ kinds = (mojom.INT32, mojom.Interface('test_interface'))
+ fields = (1, 2)
+ offsets = (0, 4)
+ self._CheckPackSequence(kinds, fields, offsets)
+
+ def testAssociatedInterfaceAlignment(self):
+ """Tests that associated interfaces are aligned on 4-byte boundaries,
+ although the size of an associated interface is 8 bytes.
+ """
+ kinds = (mojom.INT32,
+ mojom.AssociatedInterface(mojom.Interface('test_interface')))
+ fields = (1, 2)
+ offsets = (0, 4)
+ self._CheckPackSequence(kinds, fields, offsets)
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py
new file mode 100644
index 0000000000..dd28cdd120
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py
@@ -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.
+
+import imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+import mojom.parse.ast as ast
+
+
+class _TestNode(ast.NodeBase):
+ """Node type for tests."""
+
+ def __init__(self, value, **kwargs):
+ super(_TestNode, self).__init__(**kwargs)
+ self.value = value
+
+ def __eq__(self, other):
+ return super(_TestNode, self).__eq__(other) and self.value == other.value
+
+
+class _TestNodeList(ast.NodeListBase):
+ """Node list type for tests."""
+
+ _list_item_type = _TestNode
+
+
+class ASTTest(unittest.TestCase):
+ """Tests various AST classes."""
+
+ def testNodeBase(self):
+ # Test |__eq__()|; this is only used for testing, where we want to do
+ # comparison by value and ignore filenames/line numbers (for convenience).
+ node1 = ast.NodeBase(filename="hello.mojom", lineno=123)
+ node2 = ast.NodeBase()
+ self.assertEquals(node1, node2)
+ self.assertEquals(node2, node1)
+
+ # Check that |__ne__()| just defers to |__eq__()| properly.
+ self.assertFalse(node1 != node2)
+ self.assertFalse(node2 != node1)
+
+ # Check that |filename| and |lineno| are set properly (and are None by
+ # default).
+ self.assertEquals(node1.filename, "hello.mojom")
+ self.assertEquals(node1.lineno, 123)
+ self.assertIsNone(node2.filename)
+ self.assertIsNone(node2.lineno)
+
+ # |NodeBase|'s |__eq__()| should compare types (and a subclass's |__eq__()|
+ # should first defer to its superclass's).
+ node3 = _TestNode(123)
+ self.assertNotEqual(node1, node3)
+ self.assertNotEqual(node3, node1)
+ # Also test |__eq__()| directly.
+ self.assertFalse(node1 == node3)
+ self.assertFalse(node3 == node1)
+
+ node4 = _TestNode(123, filename="world.mojom", lineno=123)
+ self.assertEquals(node4, node3)
+ node5 = _TestNode(456)
+ self.assertNotEquals(node5, node4)
+
+ def testNodeListBase(self):
+ node1 = _TestNode(1, filename="foo.mojom", lineno=1)
+ # Equal to, but not the same as, |node1|:
+ node1b = _TestNode(1, filename="foo.mojom", lineno=1)
+ node2 = _TestNode(2, filename="foo.mojom", lineno=2)
+
+ nodelist1 = _TestNodeList() # Contains: (empty).
+ self.assertEquals(nodelist1, nodelist1)
+ self.assertEquals(nodelist1.items, [])
+ self.assertIsNone(nodelist1.filename)
+ self.assertIsNone(nodelist1.lineno)
+
+ nodelist2 = _TestNodeList(node1) # Contains: 1.
+ self.assertEquals(nodelist2, nodelist2)
+ self.assertEquals(nodelist2.items, [node1])
+ self.assertNotEqual(nodelist2, nodelist1)
+ self.assertEquals(nodelist2.filename, "foo.mojom")
+ self.assertEquals(nodelist2.lineno, 1)
+
+ nodelist3 = _TestNodeList([node2]) # Contains: 2.
+ self.assertEquals(nodelist3.items, [node2])
+ self.assertNotEqual(nodelist3, nodelist1)
+ self.assertNotEqual(nodelist3, nodelist2)
+ self.assertEquals(nodelist3.filename, "foo.mojom")
+ self.assertEquals(nodelist3.lineno, 2)
+
+ nodelist1.Append(node1b) # Contains: 1.
+ self.assertEquals(nodelist1.items, [node1])
+ self.assertEquals(nodelist1, nodelist2)
+ self.assertNotEqual(nodelist1, nodelist3)
+ self.assertEquals(nodelist1.filename, "foo.mojom")
+ self.assertEquals(nodelist1.lineno, 1)
+
+ nodelist1.Append(node2) # Contains: 1, 2.
+ self.assertEquals(nodelist1.items, [node1, node2])
+ self.assertNotEqual(nodelist1, nodelist2)
+ self.assertNotEqual(nodelist1, nodelist3)
+ self.assertEquals(nodelist1.lineno, 1)
+
+ nodelist2.Append(node2) # Contains: 1, 2.
+ self.assertEquals(nodelist2.items, [node1, node2])
+ self.assertEquals(nodelist2, nodelist1)
+ self.assertNotEqual(nodelist2, nodelist3)
+ self.assertEquals(nodelist2.lineno, 1)
+
+ nodelist3.Insert(node1) # Contains: 1, 2.
+ self.assertEquals(nodelist3.items, [node1, node2])
+ self.assertEquals(nodelist3, nodelist1)
+ self.assertEquals(nodelist3, nodelist2)
+ self.assertEquals(nodelist3.lineno, 1)
+
+ # Test iteration:
+ i = 1
+ for item in nodelist1:
+ self.assertEquals(item.value, i)
+ i += 1
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
new file mode 100644
index 0000000000..6822cbc8d0
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
@@ -0,0 +1,192 @@
+# Copyright 2014 The Chromium 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 imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("ply")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+from ply import lex
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+import mojom.parse.lexer
+
+
+# This (monkey-patching LexToken to make comparison value-based) is evil, but
+# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
+# for object identity.)
+def _LexTokenEq(self, other):
+ return self.type == other.type and self.value == other.value and \
+ self.lineno == other.lineno and self.lexpos == other.lexpos
+setattr(lex.LexToken, '__eq__', _LexTokenEq)
+
+
+def _MakeLexToken(token_type, value, lineno=1, lexpos=0):
+ """Makes a LexToken with the given parameters. (Note that lineno is 1-based,
+ but lexpos is 0-based.)"""
+ rv = lex.LexToken()
+ rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos
+ return rv
+
+
+def _MakeLexTokenForKeyword(keyword, **kwargs):
+ """Makes a LexToken for the given keyword."""
+ return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs)
+
+
+class LexerTest(unittest.TestCase):
+ """Tests |mojom.parse.lexer.Lexer|."""
+
+ def __init__(self, *args, **kwargs):
+ unittest.TestCase.__init__(self, *args, **kwargs)
+ # Clone all lexer instances from this one, since making a lexer is slow.
+ self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer("my_file.mojom"))
+
+ def testValidKeywords(self):
+ """Tests valid keywords."""
+ self.assertEquals(self._SingleTokenForInput("handle"),
+ _MakeLexTokenForKeyword("handle"))
+ self.assertEquals(self._SingleTokenForInput("import"),
+ _MakeLexTokenForKeyword("import"))
+ self.assertEquals(self._SingleTokenForInput("module"),
+ _MakeLexTokenForKeyword("module"))
+ self.assertEquals(self._SingleTokenForInput("struct"),
+ _MakeLexTokenForKeyword("struct"))
+ self.assertEquals(self._SingleTokenForInput("union"),
+ _MakeLexTokenForKeyword("union"))
+ self.assertEquals(self._SingleTokenForInput("interface"),
+ _MakeLexTokenForKeyword("interface"))
+ self.assertEquals(self._SingleTokenForInput("enum"),
+ _MakeLexTokenForKeyword("enum"))
+ self.assertEquals(self._SingleTokenForInput("const"),
+ _MakeLexTokenForKeyword("const"))
+ self.assertEquals(self._SingleTokenForInput("true"),
+ _MakeLexTokenForKeyword("true"))
+ self.assertEquals(self._SingleTokenForInput("false"),
+ _MakeLexTokenForKeyword("false"))
+ self.assertEquals(self._SingleTokenForInput("default"),
+ _MakeLexTokenForKeyword("default"))
+ self.assertEquals(self._SingleTokenForInput("array"),
+ _MakeLexTokenForKeyword("array"))
+ self.assertEquals(self._SingleTokenForInput("map"),
+ _MakeLexTokenForKeyword("map"))
+ self.assertEquals(self._SingleTokenForInput("associated"),
+ _MakeLexTokenForKeyword("associated"))
+
+ def testValidIdentifiers(self):
+ """Tests identifiers."""
+ self.assertEquals(self._SingleTokenForInput("abcd"),
+ _MakeLexToken("NAME", "abcd"))
+ self.assertEquals(self._SingleTokenForInput("AbC_d012_"),
+ _MakeLexToken("NAME", "AbC_d012_"))
+ self.assertEquals(self._SingleTokenForInput("_0123"),
+ _MakeLexToken("NAME", "_0123"))
+
+ def testInvalidIdentifiers(self):
+ with self.assertRaisesRegexp(
+ mojom.parse.lexer.LexError,
+ r"^my_file\.mojom:1: Error: Illegal character '\$'$"):
+ self._TokensForInput("$abc")
+ with self.assertRaisesRegexp(
+ mojom.parse.lexer.LexError,
+ r"^my_file\.mojom:1: Error: Illegal character '\$'$"):
+ self._TokensForInput("a$bc")
+
+ def testDecimalIntegerConstants(self):
+ self.assertEquals(self._SingleTokenForInput("0"),
+ _MakeLexToken("INT_CONST_DEC", "0"))
+ self.assertEquals(self._SingleTokenForInput("1"),
+ _MakeLexToken("INT_CONST_DEC", "1"))
+ self.assertEquals(self._SingleTokenForInput("123"),
+ _MakeLexToken("INT_CONST_DEC", "123"))
+ self.assertEquals(self._SingleTokenForInput("10"),
+ _MakeLexToken("INT_CONST_DEC", "10"))
+
+ def testValidTokens(self):
+ """Tests valid tokens (which aren't tested elsewhere)."""
+ # Keywords tested in |testValidKeywords|.
+ # NAME tested in |testValidIdentifiers|.
+ self.assertEquals(self._SingleTokenForInput("@123"),
+ _MakeLexToken("ORDINAL", "@123"))
+ self.assertEquals(self._SingleTokenForInput("456"),
+ _MakeLexToken("INT_CONST_DEC", "456"))
+ self.assertEquals(self._SingleTokenForInput("0x01aB2eF3"),
+ _MakeLexToken("INT_CONST_HEX", "0x01aB2eF3"))
+ self.assertEquals(self._SingleTokenForInput("123.456"),
+ _MakeLexToken("FLOAT_CONST", "123.456"))
+ self.assertEquals(self._SingleTokenForInput("\"hello\""),
+ _MakeLexToken("STRING_LITERAL", "\"hello\""))
+ self.assertEquals(self._SingleTokenForInput("+"),
+ _MakeLexToken("PLUS", "+"))
+ self.assertEquals(self._SingleTokenForInput("-"),
+ _MakeLexToken("MINUS", "-"))
+ self.assertEquals(self._SingleTokenForInput("&"),
+ _MakeLexToken("AMP", "&"))
+ self.assertEquals(self._SingleTokenForInput("?"),
+ _MakeLexToken("QSTN", "?"))
+ self.assertEquals(self._SingleTokenForInput("="),
+ _MakeLexToken("EQUALS", "="))
+ self.assertEquals(self._SingleTokenForInput("=>"),
+ _MakeLexToken("RESPONSE", "=>"))
+ self.assertEquals(self._SingleTokenForInput("("),
+ _MakeLexToken("LPAREN", "("))
+ self.assertEquals(self._SingleTokenForInput(")"),
+ _MakeLexToken("RPAREN", ")"))
+ self.assertEquals(self._SingleTokenForInput("["),
+ _MakeLexToken("LBRACKET", "["))
+ self.assertEquals(self._SingleTokenForInput("]"),
+ _MakeLexToken("RBRACKET", "]"))
+ self.assertEquals(self._SingleTokenForInput("{"),
+ _MakeLexToken("LBRACE", "{"))
+ self.assertEquals(self._SingleTokenForInput("}"),
+ _MakeLexToken("RBRACE", "}"))
+ self.assertEquals(self._SingleTokenForInput("<"),
+ _MakeLexToken("LANGLE", "<"))
+ self.assertEquals(self._SingleTokenForInput(">"),
+ _MakeLexToken("RANGLE", ">"))
+ self.assertEquals(self._SingleTokenForInput(";"),
+ _MakeLexToken("SEMI", ";"))
+ self.assertEquals(self._SingleTokenForInput(","),
+ _MakeLexToken("COMMA", ","))
+ self.assertEquals(self._SingleTokenForInput("."),
+ _MakeLexToken("DOT", "."))
+
+ def _TokensForInput(self, input_string):
+ """Gets a list of tokens for the given input string."""
+ lexer = self._zygote_lexer.clone()
+ lexer.input(input_string)
+ rv = []
+ while True:
+ tok = lexer.token()
+ if not tok:
+ return rv
+ rv.append(tok)
+
+ def _SingleTokenForInput(self, input_string):
+ """Gets the single token for the given input string. (Raises an exception if
+ the input string does not result in exactly one token.)"""
+ toks = self._TokensForInput(input_string)
+ assert len(toks) == 1
+ return toks[0]
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
new file mode 100644
index 0000000000..3f4ca871e3
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
@@ -0,0 +1,1497 @@
+# Copyright 2014 The Chromium 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 imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+import mojom.parse.ast as ast
+import mojom.parse.lexer as lexer
+import mojom.parse.parser as parser
+
+
+class ParserTest(unittest.TestCase):
+ """Tests |parser.Parse()|."""
+
+ def testTrivialValidSource(self):
+ """Tests a trivial, but valid, .mojom source."""
+
+ source = """\
+ // This is a comment.
+
+ module my_module;
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testSourceWithCrLfs(self):
+ """Tests a .mojom source with CR-LFs instead of LFs."""
+
+ source = "// This is a comment.\r\n\r\nmodule my_module;\r\n"
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testUnexpectedEOF(self):
+ """Tests a "truncated" .mojom source."""
+
+ source = """\
+ // This is a comment.
+
+ module my_module
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom: Error: Unexpected end of file$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testCommentLineNumbers(self):
+ """Tests that line numbers are correctly tracked when comments are
+ present."""
+
+ source1 = """\
+ // Isolated C++-style comments.
+
+ // Foo.
+ asdf1
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\n *asdf1$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ // Consecutive C++-style comments.
+ // Foo.
+ // Bar.
+
+ struct Yada { // Baz.
+ // Quux.
+ int32 x;
+ };
+
+ asdf2
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\n *asdf2$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ /* Single-line C-style comments. */
+ /* Foobar. */
+
+ /* Baz. */
+ asdf3
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\n *asdf3$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ source4 = """\
+ /* Multi-line C-style comments.
+ */
+ /*
+ Foo.
+ Bar.
+ */
+
+ /* Baz
+ Quux. */
+ asdf4
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\n *asdf4$"):
+ parser.Parse(source4, "my_file.mojom")
+
+
+ def testSimpleStruct(self):
+ """Tests a simple .mojom source that just defines a struct."""
+
+ source = """\
+ module my_module;
+
+ struct MyStruct {
+ int32 a;
+ double b;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, None, 'int32', None),
+ ast.StructField('b', None, None, 'double', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testSimpleStructWithoutModule(self):
+ """Tests a simple struct without an explict module statement."""
+
+ source = """\
+ struct MyStruct {
+ int32 a;
+ double b;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, None, 'int32', None),
+ ast.StructField('b', None, None, 'double', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidStructDefinitions(self):
+ """Tests all types of definitions that can occur in a struct."""
+
+ source = """\
+ struct MyStruct {
+ enum MyEnum { VALUE };
+ const double kMyConst = 1.23;
+ int32 a;
+ SomeOtherStruct b; // Invalidity detected at another stage.
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.Enum('MyEnum',
+ None,
+ ast.EnumValueList(
+ ast.EnumValue('VALUE', None, None))),
+ ast.Const('kMyConst', 'double', '1.23'),
+ ast.StructField('a', None, None, 'int32', None),
+ ast.StructField('b', None, None, 'SomeOtherStruct', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidStructDefinitions(self):
+ """Tests that definitions that aren't allowed in a struct are correctly
+ detected."""
+
+ source1 = """\
+ struct MyStruct {
+ MyMethod(int32 a);
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\(':\n"
+ r" *MyMethod\(int32 a\);$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ struct MyInnerStruct {
+ int32 a;
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
+ r" *struct MyInnerStruct {$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ interface MyInterface {
+ MyMethod(int32 a);
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'interface':\n"
+ r" *interface MyInterface {$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testMissingModuleName(self):
+ """Tests an (invalid) .mojom with a missing module name."""
+
+ source1 = """\
+ // Missing module name.
+ module ;
+ struct MyStruct {
+ int32 a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected ';':\n *module ;$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # Another similar case, but make sure that line-number tracking/reporting
+ # is correct.
+ source2 = """\
+ module
+ // This line intentionally left unblank.
+
+ struct MyStruct {
+ int32 a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected 'struct':\n"
+ r" *struct MyStruct {$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ def testMultipleModuleStatements(self):
+ """Tests an (invalid) .mojom with multiple module statements."""
+
+ source = """\
+ module foo;
+ module bar;
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Multiple \"module\" statements not "
+ r"allowed:\n *module bar;$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testModuleStatementAfterImport(self):
+ """Tests an (invalid) .mojom with a module statement after an import."""
+
+ source = """\
+ import "foo.mojom";
+ module foo;
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: \"module\" statements must precede imports "
+ r"and definitions:\n *module foo;$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testModuleStatementAfterDefinition(self):
+ """Tests an (invalid) .mojom with a module statement after a definition."""
+
+ source = """\
+ struct MyStruct {
+ int32 a;
+ };
+ module foo;
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: \"module\" statements must precede imports "
+ r"and definitions:\n *module foo;$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testImportStatementAfterDefinition(self):
+ """Tests an (invalid) .mojom with an import statement after a definition."""
+
+ source = """\
+ struct MyStruct {
+ int32 a;
+ };
+ import "foo.mojom";
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: \"import\" statements must precede "
+ r"definitions:\n *import \"foo.mojom\";$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testEnums(self):
+ """Tests that enum statements are correctly parsed."""
+
+ source = """\
+ module my_module;
+ enum MyEnum1 { VALUE1, VALUE2 }; // No trailing comma.
+ enum MyEnum2 {
+ VALUE1 = -1,
+ VALUE2 = 0,
+ VALUE3 = + 987, // Check that space is allowed.
+ VALUE4 = 0xAF12,
+ VALUE5 = -0x09bcd,
+ VALUE6 = VALUE5,
+ VALUE7, // Leave trailing comma.
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Enum(
+ 'MyEnum1',
+ None,
+ ast.EnumValueList([ast.EnumValue('VALUE1', None, None),
+ ast.EnumValue('VALUE2', None, None)])),
+ ast.Enum(
+ 'MyEnum2',
+ None,
+ ast.EnumValueList([ast.EnumValue('VALUE1', None, '-1'),
+ ast.EnumValue('VALUE2', None, '0'),
+ ast.EnumValue('VALUE3', None, '+987'),
+ ast.EnumValue('VALUE4', None, '0xAF12'),
+ ast.EnumValue('VALUE5', None, '-0x09bcd'),
+ ast.EnumValue('VALUE6', None, ('IDENTIFIER',
+ 'VALUE5')),
+ ast.EnumValue('VALUE7', None, None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidEnumInitializers(self):
+ """Tests that invalid enum initializers are correctly detected."""
+
+ # No values.
+ source1 = """\
+ enum MyEnum {
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '}':\n"
+ r" *};$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # Floating point value.
+ source2 = "enum MyEnum { VALUE = 0.123 };"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '0\.123':\n"
+ r"enum MyEnum { VALUE = 0\.123 };$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ # Boolean value.
+ source2 = "enum MyEnum { VALUE = true };"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected 'true':\n"
+ r"enum MyEnum { VALUE = true };$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ def testConsts(self):
+ """Tests some constants and struct members initialized with them."""
+
+ source = """\
+ module my_module;
+
+ struct MyStruct {
+ const int8 kNumber = -1;
+ int8 number@0 = kNumber;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct', None,
+ ast.StructBody(
+ [ast.Const('kNumber', 'int8', '-1'),
+ ast.StructField('number', None, ast.Ordinal(0), 'int8',
+ ('IDENTIFIER', 'kNumber'))]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testNoConditionals(self):
+ """Tests that ?: is not allowed."""
+
+ source = """\
+ module my_module;
+
+ enum MyEnum {
+ MY_ENUM_1 = 1 ? 2 : 3
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected '\?':\n"
+ r" *MY_ENUM_1 = 1 \? 2 : 3$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testSimpleOrdinals(self):
+ """Tests that (valid) ordinal values are scanned correctly."""
+
+ source = """\
+ module my_module;
+
+ // This isn't actually valid .mojom, but the problem (missing ordinals)
+ // should be handled at a different level.
+ struct MyStruct {
+ int32 a0@0;
+ int32 a1@1;
+ int32 a2@2;
+ int32 a9@9;
+ int32 a10 @10;
+ int32 a11 @11;
+ int32 a29 @29;
+ int32 a1234567890 @1234567890;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a0', None, ast.Ordinal(0), 'int32', None),
+ ast.StructField('a1', None, ast.Ordinal(1), 'int32', None),
+ ast.StructField('a2', None, ast.Ordinal(2), 'int32', None),
+ ast.StructField('a9', None, ast.Ordinal(9), 'int32', None),
+ ast.StructField('a10', None, ast.Ordinal(10), 'int32', None),
+ ast.StructField('a11', None, ast.Ordinal(11), 'int32', None),
+ ast.StructField('a29', None, ast.Ordinal(29), 'int32', None),
+ ast.StructField('a1234567890', None, ast.Ordinal(1234567890),
+ 'int32', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidOrdinals(self):
+ """Tests that (lexically) invalid ordinals are correctly detected."""
+
+ source1 = """\
+ module my_module;
+
+ struct MyStruct {
+ int32 a_missing@;
+ };
+ """
+ with self.assertRaisesRegexp(
+ lexer.LexError,
+ r"^my_file\.mojom:4: Error: Missing ordinal value$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ module my_module;
+
+ struct MyStruct {
+ int32 a_octal@01;
+ };
+ """
+ with self.assertRaisesRegexp(
+ lexer.LexError,
+ r"^my_file\.mojom:4: Error: "
+ r"Octal and hexadecimal ordinal values not allowed$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ module my_module; struct MyStruct { int32 a_invalid_octal@08; };
+ """
+ with self.assertRaisesRegexp(
+ lexer.LexError,
+ r"^my_file\.mojom:1: Error: "
+ r"Octal and hexadecimal ordinal values not allowed$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ source4 = "module my_module; struct MyStruct { int32 a_hex@0x1aB9; };"
+ with self.assertRaisesRegexp(
+ lexer.LexError,
+ r"^my_file\.mojom:1: Error: "
+ r"Octal and hexadecimal ordinal values not allowed$"):
+ parser.Parse(source4, "my_file.mojom")
+
+ source5 = "module my_module; struct MyStruct { int32 a_hex@0X0; };"
+ with self.assertRaisesRegexp(
+ lexer.LexError,
+ r"^my_file\.mojom:1: Error: "
+ r"Octal and hexadecimal ordinal values not allowed$"):
+ parser.Parse(source5, "my_file.mojom")
+
+ source6 = """\
+ struct MyStruct {
+ int32 a_too_big@999999999999;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: "
+ r"Ordinal value 999999999999 too large:\n"
+ r" *int32 a_too_big@999999999999;$"):
+ parser.Parse(source6, "my_file.mojom")
+
+ def testNestedNamespace(self):
+ """Tests that "nested" namespaces work."""
+
+ source = """\
+ module my.mod;
+
+ struct MyStruct {
+ int32 a;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my.mod'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(ast.StructField('a', None, None, 'int32', None)))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidHandleTypes(self):
+ """Tests (valid) handle types."""
+
+ source = """\
+ struct MyStruct {
+ handle a;
+ handle<data_pipe_consumer> b;
+ handle <data_pipe_producer> c;
+ handle < message_pipe > d;
+ handle
+ < shared_buffer
+ > e;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, None, 'handle', None),
+ ast.StructField('b', None, None, 'handle<data_pipe_consumer>',
+ None),
+ ast.StructField('c', None, None, 'handle<data_pipe_producer>',
+ None),
+ ast.StructField('d', None, None, 'handle<message_pipe>', None),
+ ast.StructField('e', None, None, 'handle<shared_buffer>',
+ None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidHandleType(self):
+ """Tests an invalid (unknown) handle type."""
+
+ source = """\
+ struct MyStruct {
+ handle<wtf_is_this> foo;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: "
+ r"Invalid handle type 'wtf_is_this':\n"
+ r" *handle<wtf_is_this> foo;$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testValidDefaultValues(self):
+ """Tests default values that are valid (to the parser)."""
+
+ source = """\
+ struct MyStruct {
+ int16 a0 = 0;
+ uint16 a1 = 0x0;
+ uint16 a2 = 0x00;
+ uint16 a3 = 0x01;
+ uint16 a4 = 0xcd;
+ int32 a5 = 12345;
+ int64 a6 = -12345;
+ int64 a7 = +12345;
+ uint32 a8 = 0x12cd3;
+ uint32 a9 = -0x12cD3;
+ uint32 a10 = +0x12CD3;
+ bool a11 = true;
+ bool a12 = false;
+ float a13 = 1.2345;
+ float a14 = -1.2345;
+ float a15 = +1.2345;
+ float a16 = 123.;
+ float a17 = .123;
+ double a18 = 1.23E10;
+ double a19 = 1.E-10;
+ double a20 = .5E+10;
+ double a21 = -1.23E10;
+ double a22 = +.123E10;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a0', None, None, 'int16', '0'),
+ ast.StructField('a1', None, None, 'uint16', '0x0'),
+ ast.StructField('a2', None, None, 'uint16', '0x00'),
+ ast.StructField('a3', None, None, 'uint16', '0x01'),
+ ast.StructField('a4', None, None, 'uint16', '0xcd'),
+ ast.StructField('a5' , None, None, 'int32', '12345'),
+ ast.StructField('a6', None, None, 'int64', '-12345'),
+ ast.StructField('a7', None, None, 'int64', '+12345'),
+ ast.StructField('a8', None, None, 'uint32', '0x12cd3'),
+ ast.StructField('a9', None, None, 'uint32', '-0x12cD3'),
+ ast.StructField('a10', None, None, 'uint32', '+0x12CD3'),
+ ast.StructField('a11', None, None, 'bool', 'true'),
+ ast.StructField('a12', None, None, 'bool', 'false'),
+ ast.StructField('a13', None, None, 'float', '1.2345'),
+ ast.StructField('a14', None, None, 'float', '-1.2345'),
+ ast.StructField('a15', None, None, 'float', '+1.2345'),
+ ast.StructField('a16', None, None, 'float', '123.'),
+ ast.StructField('a17', None, None, 'float', '.123'),
+ ast.StructField('a18', None, None, 'double', '1.23E10'),
+ ast.StructField('a19', None, None, 'double', '1.E-10'),
+ ast.StructField('a20', None, None, 'double', '.5E+10'),
+ ast.StructField('a21', None, None, 'double', '-1.23E10'),
+ ast.StructField('a22', None, None, 'double', '+.123E10')]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidFixedSizeArray(self):
+ """Tests parsing a fixed size array."""
+
+ source = """\
+ struct MyStruct {
+ array<int32> normal_array;
+ array<int32, 1> fixed_size_array_one_entry;
+ array<int32, 10> fixed_size_array_ten_entries;
+ array<array<array<int32, 1>>, 2> nested_arrays;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('normal_array', None, None, 'int32[]', None),
+ ast.StructField('fixed_size_array_one_entry', None, None,
+ 'int32[1]', None),
+ ast.StructField('fixed_size_array_ten_entries', None, None,
+ 'int32[10]', None),
+ ast.StructField('nested_arrays', None, None,
+ 'int32[1][][2]', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidNestedArray(self):
+ """Tests parsing a nested array."""
+
+ source = "struct MyStruct { array<array<int32>> nested_array; };"
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ ast.StructField('nested_array', None, None, 'int32[][]',
+ None)))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidFixedArraySize(self):
+ """Tests that invalid fixed array bounds are correctly detected."""
+
+ source1 = """\
+ struct MyStruct {
+ array<int32, 0> zero_size_array;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Fixed array size 0 invalid:\n"
+ r" *array<int32, 0> zero_size_array;$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ array<int32, 999999999999> too_big_array;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid:\n"
+ r" *array<int32, 999999999999> too_big_array;$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ array<int32, abcdefg> not_a_number;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n"
+ r" *array<int32, abcdefg> not_a_number;"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidAssociativeArrays(self):
+ """Tests that we can parse valid associative array structures."""
+
+ source1 = "struct MyStruct { map<string, uint8> data; };"
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('data', None, None, 'uint8{string}', None)]))])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ source2 = "interface MyInterface { MyMethod(map<string, uint8> a); };"
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ None,
+ None,
+ ast.ParameterList(
+ ast.Parameter('a', None, None, 'uint8{string}')),
+ None)))])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ source3 = "struct MyStruct { map<string, array<uint8>> data; };"
+ expected3 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('data', None, None, 'uint8[]{string}',
+ None)]))])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ def testValidMethod(self):
+ """Tests parsing method declarations."""
+
+ source1 = "interface MyInterface { MyMethod(int32 a); };"
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ None,
+ None,
+ ast.ParameterList(ast.Parameter('a', None, None, 'int32')),
+ None)))])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ source2 = """\
+ interface MyInterface {
+ MyMethod1@0(int32 a@0, int64 b@1);
+ MyMethod2@1() => ();
+ };
+ """
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ [ast.Method(
+ 'MyMethod1',
+ None,
+ ast.Ordinal(0),
+ ast.ParameterList([ast.Parameter('a', None, ast.Ordinal(0),
+ 'int32'),
+ ast.Parameter('b', None, ast.Ordinal(1),
+ 'int64')]),
+ None),
+ ast.Method(
+ 'MyMethod2',
+ None,
+ ast.Ordinal(1),
+ ast.ParameterList(),
+ ast.ParameterList())]))])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ source3 = """\
+ interface MyInterface {
+ MyMethod(string a) => (int32 a, bool b);
+ };
+ """
+ expected3 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ None,
+ None,
+ ast.ParameterList(ast.Parameter('a', None, None, 'string')),
+ ast.ParameterList([ast.Parameter('a', None, None, 'int32'),
+ ast.Parameter('b', None, None,
+ 'bool')]))))])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ def testInvalidMethods(self):
+ """Tests that invalid method declarations are correctly detected."""
+
+ # No trailing commas.
+ source1 = """\
+ interface MyInterface {
+ MyMethod(string a,);
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\)':\n"
+ r" *MyMethod\(string a,\);$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # No leading commas.
+ source2 = """\
+ interface MyInterface {
+ MyMethod(, string a);
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected ',':\n"
+ r" *MyMethod\(, string a\);$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ def testValidInterfaceDefinitions(self):
+ """Tests all types of definitions that can occur in an interface."""
+
+ source = """\
+ interface MyInterface {
+ enum MyEnum { VALUE };
+ const int32 kMyConst = 123;
+ MyMethod(int32 x) => (MyEnum y);
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ [ast.Enum('MyEnum',
+ None,
+ ast.EnumValueList(
+ ast.EnumValue('VALUE', None, None))),
+ ast.Const('kMyConst', 'int32', '123'),
+ ast.Method(
+ 'MyMethod',
+ None,
+ None,
+ ast.ParameterList(ast.Parameter('x', None, None, 'int32')),
+ ast.ParameterList(ast.Parameter('y', None, None,
+ 'MyEnum')))]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidInterfaceDefinitions(self):
+ """Tests that definitions that aren't allowed in an interface are correctly
+ detected."""
+
+ source1 = """\
+ interface MyInterface {
+ struct MyStruct {
+ int32 a;
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
+ r" *struct MyStruct {$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ interface MyInterface {
+ interface MyInnerInterface {
+ MyMethod(int32 x);
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'interface':\n"
+ r" *interface MyInnerInterface {$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ interface MyInterface {
+ int32 my_field;
+ };
+ """
+ # The parser thinks that "int32" is a plausible name for a method, so it's
+ # "my_field" that gives it away.
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'my_field':\n"
+ r" *int32 my_field;$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidAttributes(self):
+ """Tests parsing attributes (and attribute lists)."""
+
+ # Note: We use structs because they have (optional) attribute lists.
+
+ # Empty attribute list.
+ source1 = "[] struct MyStruct {};"
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ # One-element attribute list, with name value.
+ source2 = "[MyAttribute=MyName] struct MyStruct {};"
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ ast.AttributeList(ast.Attribute("MyAttribute", "MyName")),
+ ast.StructBody())])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ # Two-element attribute list, with one string value and one integer value.
+ source3 = "[MyAttribute1 = \"hello\", MyAttribute2 = 5] struct MyStruct {};"
+ expected3 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ ast.AttributeList([ast.Attribute("MyAttribute1", "hello"),
+ ast.Attribute("MyAttribute2", 5)]),
+ ast.StructBody())])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ # Various places that attribute list is allowed.
+ source4 = """\
+ [Attr0=0] module my_module;
+
+ [Attr1=1] struct MyStruct {
+ [Attr2=2] int32 a;
+ };
+ [Attr3=3] union MyUnion {
+ [Attr4=4] int32 a;
+ };
+ [Attr5=5] enum MyEnum {
+ [Attr6=6] a
+ };
+ [Attr7=7] interface MyInterface {
+ [Attr8=8] MyMethod([Attr9=9] int32 a) => ([Attr10=10] bool b);
+ };
+ """
+ expected4 = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'),
+ ast.AttributeList([ast.Attribute("Attr0", 0)])),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ ast.AttributeList(ast.Attribute("Attr1", 1)),
+ ast.StructBody(
+ ast.StructField(
+ 'a', ast.AttributeList([ast.Attribute("Attr2", 2)]),
+ None, 'int32', None))),
+ ast.Union(
+ 'MyUnion',
+ ast.AttributeList(ast.Attribute("Attr3", 3)),
+ ast.UnionBody(
+ ast.UnionField(
+ 'a', ast.AttributeList([ast.Attribute("Attr4", 4)]), None,
+ 'int32'))),
+ ast.Enum(
+ 'MyEnum',
+ ast.AttributeList(ast.Attribute("Attr5", 5)),
+ ast.EnumValueList(
+ ast.EnumValue(
+ 'VALUE', ast.AttributeList([ast.Attribute("Attr6", 6)]),
+ None))),
+ ast.Interface(
+ 'MyInterface',
+ ast.AttributeList(ast.Attribute("Attr7", 7)),
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ ast.AttributeList(ast.Attribute("Attr8", 8)),
+ None,
+ ast.ParameterList(
+ ast.Parameter(
+ 'a', ast.AttributeList([ast.Attribute("Attr9", 9)]),
+ None, 'int32')),
+ ast.ParameterList(
+ ast.Parameter(
+ 'b',
+ ast.AttributeList([ast.Attribute("Attr10", 10)]),
+ None, 'bool')))))])
+ self.assertEquals(parser.Parse(source4, "my_file.mojom"), expected4)
+
+ # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()|
+ # literal (non-name) values, which is extremely dubious.)
+
+ def testInvalidAttributes(self):
+ """Tests that invalid attributes and attribute lists are correctly
+ detected."""
+
+ # Trailing commas not allowed.
+ source1 = "[MyAttribute=MyName,] struct MyStruct {};"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '\]':\n"
+ r"\[MyAttribute=MyName,\] struct MyStruct {};$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # Missing value.
+ source2 = "[MyAttribute=] struct MyStruct {};"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '\]':\n"
+ r"\[MyAttribute=\] struct MyStruct {};$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ # Missing key.
+ source3 = "[=MyName] struct MyStruct {};"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '=':\n"
+ r"\[=MyName\] struct MyStruct {};$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidImports(self):
+ """Tests parsing import statements."""
+
+ # One import (no module statement).
+ source1 = "import \"somedir/my.mojom\";"
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(ast.Import("somedir/my.mojom")),
+ [])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ # Two imports (no module statement).
+ source2 = """\
+ import "somedir/my1.mojom";
+ import "somedir/my2.mojom";
+ """
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList([ast.Import("somedir/my1.mojom"),
+ ast.Import("somedir/my2.mojom")]),
+ [])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ # Imports with module statement.
+ source3 = """\
+ module my_module;
+ import "somedir/my1.mojom";
+ import "somedir/my2.mojom";
+ """
+ expected3 = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList([ast.Import("somedir/my1.mojom"),
+ ast.Import("somedir/my2.mojom")]),
+ [])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ def testInvalidImports(self):
+ """Tests that invalid import statements are correctly detected."""
+
+ source1 = """\
+ // Make the error occur on line 2.
+ import invalid
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'invalid':\n"
+ r" *import invalid$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ import // Missing string.
+ struct MyStruct {
+ int32 a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
+ r" *struct MyStruct {$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ import "foo.mojom" // Missing semicolon.
+ struct MyStruct {
+ int32 a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
+ r" *struct MyStruct {$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidNullableTypes(self):
+ """Tests parsing nullable types."""
+
+ source = """\
+ struct MyStruct {
+ int32? a; // This is actually invalid, but handled at a different
+ // level.
+ string? b;
+ array<int32> ? c;
+ array<string ? > ? d;
+ array<array<int32>?>? e;
+ array<int32, 1>? f;
+ array<string?, 1>? g;
+ some_struct? h;
+ handle? i;
+ handle<data_pipe_consumer>? j;
+ handle<data_pipe_producer>? k;
+ handle<message_pipe>? l;
+ handle<shared_buffer>? m;
+ some_interface&? n;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, None,'int32?', None),
+ ast.StructField('b', None, None,'string?', None),
+ ast.StructField('c', None, None,'int32[]?', None),
+ ast.StructField('d', None, None,'string?[]?', None),
+ ast.StructField('e', None, None,'int32[]?[]?', None),
+ ast.StructField('f', None, None,'int32[1]?', None),
+ ast.StructField('g', None, None,'string?[1]?', None),
+ ast.StructField('h', None, None,'some_struct?', None),
+ ast.StructField('i', None, None,'handle?', None),
+ ast.StructField('j', None, None,'handle<data_pipe_consumer>?',
+ None),
+ ast.StructField('k', None, None,'handle<data_pipe_producer>?',
+ None),
+ ast.StructField('l', None, None,'handle<message_pipe>?', None),
+ ast.StructField('m', None, None,'handle<shared_buffer>?',
+ None),
+ ast.StructField('n', None, None,'some_interface&?', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidNullableTypes(self):
+ """Tests that invalid nullable types are correctly detected."""
+ source1 = """\
+ struct MyStruct {
+ string?? a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\?':\n"
+ r" *string\?\? a;$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ handle?<data_pipe_consumer> a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '<':\n"
+ r" *handle\?<data_pipe_consumer> a;$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ some_interface?& a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '&':\n"
+ r" *some_interface\?& a;$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testSimpleUnion(self):
+ """Tests a simple .mojom source that just defines a union."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ int32 a;
+ double b;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Union(
+ 'MyUnion',
+ None,
+ ast.UnionBody([
+ ast.UnionField('a', None, None, 'int32'),
+ ast.UnionField('b', None, None, 'double')
+ ]))])
+ actual = parser.Parse(source, "my_file.mojom")
+ self.assertEquals(actual, expected)
+
+ def testUnionWithOrdinals(self):
+ """Test that ordinals are assigned to fields."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ int32 a @10;
+ double b @30;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Union(
+ 'MyUnion',
+ None,
+ ast.UnionBody([
+ ast.UnionField('a', None, ast.Ordinal(10), 'int32'),
+ ast.UnionField('b', None, ast.Ordinal(30), 'double')
+ ]))])
+ actual = parser.Parse(source, "my_file.mojom")
+ self.assertEquals(actual, expected)
+
+ def testUnionWithStructMembers(self):
+ """Test that struct members are accepted."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ SomeStruct s;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Union(
+ 'MyUnion',
+ None,
+ ast.UnionBody([
+ ast.UnionField('s', None, None, 'SomeStruct')
+ ]))])
+ actual = parser.Parse(source, "my_file.mojom")
+ self.assertEquals(actual, expected)
+
+ def testUnionWithArrayMember(self):
+ """Test that array members are accepted."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ array<int32> a;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Union(
+ 'MyUnion',
+ None,
+ ast.UnionBody([
+ ast.UnionField('a', None, None, 'int32[]')
+ ]))])
+ actual = parser.Parse(source, "my_file.mojom")
+ self.assertEquals(actual, expected)
+
+ def testUnionWithMapMember(self):
+ """Test that map members are accepted."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ map<int32, string> m;
+ };
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Union(
+ 'MyUnion',
+ None,
+ ast.UnionBody([
+ ast.UnionField('m', None, None, 'string{int32}')
+ ]))])
+ actual = parser.Parse(source, "my_file.mojom")
+ self.assertEquals(actual, expected)
+
+ def testUnionDisallowNestedStruct(self):
+ """Tests that structs cannot be nested in unions."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ struct MyStruct {
+ int32 a;
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected 'struct':\n"
+ r" *struct MyStruct {$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testUnionDisallowNestedInterfaces(self):
+ """Tests that interfaces cannot be nested in unions."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ interface MyInterface {
+ MyMethod(int32 a);
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected 'interface':\n"
+ r" *interface MyInterface {$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testUnionDisallowNestedUnion(self):
+ """Tests that unions cannot be nested in unions."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ union MyOtherUnion {
+ int32 a;
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected 'union':\n"
+ r" *union MyOtherUnion {$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testUnionDisallowNestedEnum(self):
+ """Tests that enums cannot be nested in unions."""
+ source = """\
+ module my_module;
+
+ union MyUnion {
+ enum MyEnum {
+ A,
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected 'enum':\n"
+ r" *enum MyEnum {$"):
+ parser.Parse(source, "my_file.mojom")
+
+ def testValidAssociatedKinds(self):
+ """Tests parsing associated interfaces and requests."""
+ source1 = """\
+ struct MyStruct {
+ associated MyInterface a;
+ associated MyInterface& b;
+ associated MyInterface? c;
+ associated MyInterface&? d;
+ };
+ """
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, None,'asso<MyInterface>', None),
+ ast.StructField('b', None, None,'asso<MyInterface&>', None),
+ ast.StructField('c', None, None,'asso<MyInterface>?', None),
+ ast.StructField('d', None, None,'asso<MyInterface&>?',
+ None)]))])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ source2 = """\
+ interface MyInterface {
+ MyMethod(associated A a) =>(associated B& b);
+ };"""
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ None,
+ None,
+ ast.ParameterList(
+ ast.Parameter('a', None, None, 'asso<A>')),
+ ast.ParameterList(
+ ast.Parameter('b', None, None, 'asso<B&>')))))])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ def testInvalidAssociatedKinds(self):
+ """Tests that invalid associated interfaces and requests are correctly
+ detected."""
+ source1 = """\
+ struct MyStruct {
+ associated associated SomeInterface a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'associated':\n"
+ r" *associated associated SomeInterface a;$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ associated handle a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'handle':\n"
+ r" *associated handle a;$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ associated? MyInterface& a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\?':\n"
+ r" *associated\? MyInterface& a;$"):
+ parser.Parse(source3, "my_file.mojom")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py
new file mode 100755
index 0000000000..b160de6690
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.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.
+
+"""Simple testing utility to just run the mojom parser."""
+
+
+import os.path
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ os.path.pardir, os.path.pardir))
+
+from mojom.parse.parser import Parse, ParseError
+
+
+def main(argv):
+ if len(argv) < 2:
+ print "usage: %s filename" % argv[0]
+ return 0
+
+ for filename in argv[1:]:
+ with open(filename) as f:
+ print "%s:" % filename
+ try:
+ print Parse(f.read(), filename)
+ except ParseError, e:
+ print e
+ return 1
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py
new file mode 100755
index 0000000000..899d40e584
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py
@@ -0,0 +1,34 @@
+#!/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.
+
+"""Simple testing utility to just run the mojom translate stage."""
+
+
+import os.path
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ os.path.pardir, os.path.pardir))
+
+from mojom.parse.parser import Parse
+from mojom.parse.translate import Translate
+
+
+def main(argv):
+ if len(argv) < 2:
+ print "usage: %s filename" % sys.argv[0]
+ return 1
+
+ for filename in argv[1:]:
+ with open(filename) as f:
+ print "%s:" % filename
+ print Translate(Parse(f.read(), filename),
+ os.path.splitext(os.path.basename(filename))[0])
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py
new file mode 100644
index 0000000000..25203329f4
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py
@@ -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.
+
+import imp
+import os.path
+import sys
+import unittest
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+try:
+ imp.find_module("mojom")
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
+from mojom.parse import ast
+from mojom.parse import translate
+
+
+class TranslateTest(unittest.TestCase):
+ """Tests |parser.Parse()|."""
+
+ def testSimpleArray(self):
+ """Tests a simple int32[]."""
+ # pylint: disable=W0212
+ self.assertEquals(translate._MapKind("int32[]"), "a:i32")
+
+ def testAssociativeArray(self):
+ """Tests a simple uint8{string}."""
+ # pylint: disable=W0212
+ self.assertEquals(translate._MapKind("uint8{string}"), "m[s][u8]")
+
+ def testLeftToRightAssociativeArray(self):
+ """Makes sure that parsing is done from right to left on the internal kinds
+ in the presence of an associative array."""
+ # pylint: disable=W0212
+ self.assertEquals(translate._MapKind("uint8[]{string}"), "m[s][a:u8]")
+
+ def testTranslateSimpleUnions(self):
+ """Makes sure that a simple union is translated correctly."""
+ tree = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Union("SomeUnion", None, ast.UnionBody(
+ [ast.UnionField("a", None, None, "int32"),
+ ast.UnionField("b", None, None, "string")]))])
+ expected = [{
+ "name": "SomeUnion",
+ "fields": [{"kind": "i32", "name": "a"},
+ {"kind": "s", "name": "b"}]}]
+ actual = translate.Translate(tree, "mojom_tree")
+ self.assertEquals(actual["unions"], expected)
+
+ def testMapTreeForTypeRaisesWithDuplicate(self):
+ """Verifies _MapTreeForType() raises when passed two values with the same
+ name."""
+ methods = [ast.Method('dup', None, None, ast.ParameterList(), None),
+ ast.Method('dup', None, None, ast.ParameterList(), None)]
+ self.assertRaises(Exception, translate._MapTreeForType,
+ (lambda x: x, methods, '', 'scope'))
+
+ def testAssociatedKinds(self):
+ """Tests type spec translation of associated interfaces and requests."""
+ # pylint: disable=W0212
+ self.assertEquals(translate._MapKind("asso<SomeInterface>?"),
+ "?asso:x:SomeInterface")
+ self.assertEquals(translate._MapKind("asso<SomeInterface&>?"),
+ "?asso:r:x:SomeInterface")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py
new file mode 100644
index 0000000000..2a4b17b29b
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py
@@ -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.
+
+import fnmatch
+from os import walk
+from os.path import join
+import sys
+
+
+def FindFiles(top, pattern, **kwargs):
+ """Finds files under |top| matching the glob pattern |pattern|, returning a
+ list of paths."""
+ matches = []
+ for dirpath, _, filenames in walk(top, **kwargs):
+ for filename in fnmatch.filter(filenames, pattern):
+ matches.append(join(dirpath, filename))
+ return matches
+
+
+def main(argv):
+ if len(argv) != 3:
+ print "usage: %s path pattern" % argv[0]
+ return 1
+
+ for filename in FindFiles(argv[1], argv[2]):
+ print filename
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py
new file mode 100644
index 0000000000..20ef461969
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py
@@ -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.
+
+import os.path
+from subprocess import check_call
+import sys
+
+
+def RunBindingsGenerator(out_dir, root_dir, mojom_file, extra_flags=None):
+ out_dir = os.path.abspath(out_dir)
+ root_dir = os.path.abspath(root_dir)
+ mojom_file = os.path.abspath(mojom_file)
+
+ # The mojom file should be under the root directory somewhere.
+ assert mojom_file.startswith(root_dir)
+ mojom_reldir = os.path.dirname(os.path.relpath(mojom_file, root_dir))
+
+ # TODO(vtl): Abstract out the "main" functions, so that we can just import
+ # the bindings generator (which would be more portable and easier to use in
+ # tests).
+ this_dir = os.path.dirname(os.path.abspath(__file__))
+ # We're in src/mojo/public/tools/bindings/pylib/mojom_tests/support;
+ # mojom_bindings_generator.py is in .../bindings.
+ bindings_generator = os.path.join(this_dir, os.pardir, os.pardir, os.pardir,
+ "mojom_bindings_generator.py")
+
+ args = ["python", bindings_generator,
+ "-o", os.path.join(out_dir, mojom_reldir)]
+ if extra_flags:
+ args.extend(extra_flags)
+ args.append(mojom_file)
+
+ check_call(args)
+
+
+def main(argv):
+ if len(argv) < 4:
+ print "usage: %s out_dir root_dir mojom_file [extra_flags]" % argv[0]
+ return 1
+
+ RunBindingsGenerator(argv[1], argv[2], argv[3], extra_flags=argv[4:])
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/mojo/public/tools/chrome_ipc/generate_mojom.py b/mojo/public/tools/chrome_ipc/generate_mojom.py
new file mode 100755
index 0000000000..04e933bc41
--- /dev/null
+++ b/mojo/public/tools/chrome_ipc/generate_mojom.py
@@ -0,0 +1,453 @@
+#! /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.
+"""A generator of mojom interfaces and typemaps from Chrome IPC messages.
+
+For example,
+generate_mojom.py content/common/file_utilities_messages.h
+ --output_mojom=content/common/file_utilities.mojom
+ --output_typemap=content/common/file_utilities.typemap
+"""
+
+import argparse
+import logging
+import os
+import re
+import subprocess
+import sys
+
+_MESSAGE_PATTERN = re.compile(
+ r'(?:\n|^)IPC_(SYNC_)?MESSAGE_(ROUTED|CONTROL)(\d_)?(\d)')
+_VECTOR_PATTERN = re.compile(r'std::(vector|set)<(.*)>')
+_MAP_PATTERN = re.compile(r'std::map<(.*), *(.*)>')
+_NAMESPACE_PATTERN = re.compile(r'([a-z_]*?)::([A-Z].*)')
+
+_unused_arg_count = 0
+
+
+def _git_grep(pattern, paths_pattern):
+ try:
+ args = ['git', 'grep', '-l', '-e', pattern, '--'] + paths_pattern
+ result = subprocess.check_output(args).strip().splitlines()
+ logging.debug('%s => %s', ' '.join(args), result)
+ return result
+ except subprocess.CalledProcessError:
+ logging.debug('%s => []', ' '.join(args))
+ return []
+
+
+def _git_multigrep(patterns, paths):
+ """Find a list of files that match all of the provided patterns."""
+ if isinstance(paths, str):
+ paths = [paths]
+ if isinstance(patterns, str):
+ patterns = [patterns]
+ for pattern in patterns:
+ # Search only the files that matched previous patterns.
+ paths = _git_grep(pattern, paths)
+ if not paths:
+ return []
+ return paths
+
+
+class Typemap(object):
+
+ def __init__(self, typemap_files):
+ self._typemap_files = typemap_files
+ self._custom_mappings = {}
+ self._new_custom_mappings = {}
+ self._imports = set()
+ self._public_includes = set()
+ self._traits_includes = set()
+ self._enums = set()
+
+ def load_typemaps(self):
+ for typemap in self._typemap_files:
+ self.load_typemap(typemap)
+
+ def load_typemap(self, path):
+ typemap = {}
+ with open(path) as f:
+ content = f.read().replace('=\n', '=')
+ exec content in typemap
+ for mapping in typemap['type_mappings']:
+ mojom, native = mapping.split('=')
+ self._custom_mappings[native] = {'name': mojom,
+ 'mojom': typemap['mojom'].strip('/')}
+
+ def generate_typemap(self, output_mojom, input_filename, namespace):
+ new_mappings = sorted(self._format_new_mappings(namespace))
+ if not new_mappings:
+ return
+ yield """# 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.
+"""
+ yield 'mojom = "//%s"' % output_mojom
+ yield 'public_headers = [%s\n]' % ''.join(
+ '\n "//%s",' % include for include in sorted(self._public_includes))
+ yield 'traits_headers = [%s\n]' % ''.join(
+ '\n "//%s",' % include
+ for include in sorted(self._traits_includes.union([os.path.normpath(
+ input_filename)])))
+ yield 'deps = [ "//ipc" ]'
+ yield 'type_mappings = [\n %s\n]' % '\n '.join(new_mappings)
+
+ def _format_new_mappings(self, namespace):
+ for native, mojom in self._new_custom_mappings.iteritems():
+ yield '"%s.%s=::%s",' % (namespace, mojom, native)
+
+ def format_new_types(self):
+ for native_type, typename in self._new_custom_mappings.iteritems():
+ if native_type in self._enums:
+ yield '[Native]\nenum %s;\n' % typename
+ else:
+ yield '[Native]\nstruct %s;\n' % typename
+
+ _BUILTINS = {
+ 'bool': 'bool',
+ 'int': 'int32',
+ 'unsigned': 'uint32',
+ 'char': 'uint8',
+ 'unsigned char': 'uint8',
+ 'short': 'int16',
+ 'unsigned short': 'uint16',
+ 'int8_t': 'int8',
+ 'int16_t': 'int16',
+ 'int32_t': 'int32',
+ 'int64_t': 'int64',
+ 'uint8_t': 'uint8',
+ 'uint16_t': 'uint16',
+ 'uint32_t': 'uint32',
+ 'uint64_t': 'uint64',
+ 'float': 'float',
+ 'double': 'double',
+ 'std::string': 'string',
+ 'base::string16': 'string',
+ 'base::FilePath::StringType': 'string',
+ 'base::SharedMemoryHandle': 'handle<shared_memory>',
+ 'IPC::PlatformFileForTransit': 'handle',
+ 'base::FileDescriptor': 'handle',
+ }
+
+ def lookup_type(self, typename):
+ try:
+ return self._BUILTINS[typename]
+ except KeyError:
+ pass
+
+ vector_match = _VECTOR_PATTERN.search(typename)
+ if vector_match:
+ return 'array<%s>' % self.lookup_type(vector_match.groups()[1].strip())
+ map_match = _MAP_PATTERN.search(typename)
+ if map_match:
+ return 'map<%s, %s>' % tuple(self.lookup_type(t.strip())
+ for t in map_match.groups())
+ try:
+ result = self._custom_mappings[typename]['name']
+ mojom = self._custom_mappings[typename].get('mojom', None)
+ if mojom:
+ self._imports.add(mojom)
+ return result
+ except KeyError:
+ pass
+
+ match = _NAMESPACE_PATTERN.match(typename)
+ if match:
+ namespace, name = match.groups()
+ else:
+ namespace = ''
+ name = typename
+ namespace = namespace.replace('::', '.')
+ cpp_name = name
+ name = name.replace('::', '')
+
+ if name.endswith('Params'):
+ try:
+ _, name = name.rsplit('Msg_')
+ except ValueError:
+ try:
+ _, name = name.split('_', 1)
+ except ValueError:
+ pass
+
+ if namespace.endswith('.mojom'):
+ generated_mojom_name = '%s.%s' % (namespace, name)
+ elif not namespace:
+ generated_mojom_name = 'mojom.%s' % name
+ else:
+ generated_mojom_name = '%s.mojom.%s' % (namespace, name)
+
+ self._new_custom_mappings[typename] = name
+ self._add_includes(namespace, cpp_name, typename)
+ generated_mojom_name = name
+ self._custom_mappings[typename] = {'name': generated_mojom_name}
+ return generated_mojom_name
+
+ def _add_includes(self, namespace, name, fullname):
+ name_components = name.split('::')
+ is_enum = False
+ for i in xrange(len(name_components)):
+ subname = '::'.join(name_components[i:])
+ extra_names = name_components[:i] + [subname]
+ patterns = [r'\(struct\|class\|enum\)[A-Z_ ]* %s {' % s
+ for s in extra_names]
+ if namespace:
+ patterns.extend(r'namespace %s' % namespace_component
+ for namespace_component in namespace.split('.'))
+ includes = _git_multigrep(patterns, '*.h')
+ if includes:
+ if _git_grep(r'enum[A-Z_ ]* %s {' % subname, includes):
+ self._enums.add(fullname)
+ is_enum = True
+ logging.info('%s => public_headers = %s', fullname, includes)
+ self._public_includes.update(includes)
+ break
+
+ if is_enum:
+ patterns = ['IPC_ENUM_TRAITS[A-Z_]*(%s' % fullname]
+ else:
+ patterns = [r'\(IPC_STRUCT_TRAITS_BEGIN(\|ParamTraits<\)%s' % fullname]
+ includes = _git_multigrep(
+ patterns,
+ ['*messages.h', '*struct_traits.h', 'ipc/ipc_message_utils.h'])
+ if includes:
+ logging.info('%s => traits_headers = %s', fullname, includes)
+ self._traits_includes.update(includes)
+
+ def format_imports(self):
+ for import_name in sorted(self._imports):
+ yield 'import "%s";' % import_name
+ if self._imports:
+ yield ''
+
+
+class Argument(object):
+
+ def __init__(self, typename, name):
+ self.typename = typename.strip()
+ self.name = name.strip().replace('\n', '').replace(' ', '_').lower()
+ if not self.name:
+ global _unused_arg_count
+ self.name = 'unnamed_arg%d' % _unused_arg_count
+ _unused_arg_count += 1
+
+ def format(self, typemaps):
+ return '%s %s' % (typemaps.lookup_type(self.typename), self.name)
+
+
+class Message(object):
+
+ def __init__(self, match, content):
+ self.sync = bool(match[0])
+ self.routed = match[1] == 'ROUTED'
+ self.args = []
+ self.response_args = []
+ if self.sync:
+ num_expected_args = int(match[2][:-1])
+ num_expected_response_args = int(match[3])
+ else:
+ num_expected_args = int(match[3])
+ num_expected_response_args = 0
+ body = content.split(',')
+ name = body[0].strip()
+ try:
+ self.group, self.name = name.split('Msg_')
+ except ValueError:
+ try:
+ self.group, self.name = name.split('_')
+ except ValueError:
+ self.group = 'UnnamedInterface'
+ self.name = name
+ self.group = '%s%s' % (self.group, match[1].title())
+ args = list(self.parse_args(','.join(body[1:])))
+ if len(args) != num_expected_args + num_expected_response_args:
+ raise Exception('Incorrect number of args parsed for %s' % (name))
+ self.args = args[:num_expected_args]
+ self.response_args = args[num_expected_args:]
+
+ def parse_args(self, args_str):
+ args_str = args_str.strip()
+ if not args_str:
+ return
+ looking_for_type = False
+ type_start = 0
+ comment_start = None
+ comment_end = None
+ type_end = None
+ angle_bracket_nesting = 0
+ i = 0
+ while i < len(args_str):
+ if args_str[i] == ',' and not angle_bracket_nesting:
+ looking_for_type = True
+ if type_end is None:
+ type_end = i
+ elif args_str[i:i + 2] == '/*':
+ if type_end is None:
+ type_end = i
+ comment_start = i + 2
+ comment_end = args_str.index('*/', i + 2)
+ i = comment_end + 1
+ elif args_str[i:i + 2] == '//':
+ if type_end is None:
+ type_end = i
+ comment_start = i + 2
+ comment_end = args_str.index('\n', i + 2)
+ i = comment_end
+ elif args_str[i] == '<':
+ angle_bracket_nesting += 1
+ elif args_str[i] == '>':
+ angle_bracket_nesting -= 1
+ elif looking_for_type and args_str[i].isalpha():
+ if comment_start is not None and comment_end is not None:
+ yield Argument(args_str[type_start:type_end],
+ args_str[comment_start:comment_end])
+ else:
+ yield Argument(args_str[type_start:type_end], '')
+ type_start = i
+ type_end = None
+ comment_start = None
+ comment_end = None
+ looking_for_type = False
+ i += 1
+ if comment_start is not None and comment_end is not None:
+ yield Argument(args_str[type_start:type_end],
+ args_str[comment_start:comment_end])
+ else:
+ yield Argument(args_str[type_start:type_end], '')
+
+ def format(self, typemaps):
+ result = '%s(%s)' % (self.name, ','.join('\n %s' % arg.format(typemaps)
+ for arg in self.args))
+ if self.sync:
+ result += ' => (%s)' % (',\n'.join('\n %s' % arg.format(typemaps)
+ for arg in self.response_args))
+ result = '[Sync]\n %s' % result
+ return '%s;' % result
+
+
+class Generator(object):
+
+ def __init__(self, input_name, output_namespace):
+ self._input_name = input_name
+ with open(input_name) as f:
+ self._content = f.read()
+ self._namespace = output_namespace
+ self._typemaps = Typemap(self._find_typemaps())
+ self._interface_definitions = []
+
+ def _get_messages(self):
+ for m in _MESSAGE_PATTERN.finditer(self._content):
+ i = m.end() + 1
+ while i < len(self._content):
+ if self._content[i:i + 2] == '/*':
+ i = self._content.index('*/', i + 2) + 1
+ elif self._content[i] == ')':
+ yield Message(m.groups(), self._content[m.end() + 1:i])
+ break
+ i += 1
+
+ def _extract_messages(self):
+ grouped_messages = {}
+ for m in self._get_messages():
+ grouped_messages.setdefault(m.group, []).append(m)
+ self._typemaps.load_typemaps()
+ for interface, messages in grouped_messages.iteritems():
+ self._interface_definitions.append(self._format_interface(interface,
+ messages))
+
+ def count(self):
+ grouped_messages = {}
+ for m in self._get_messages():
+ grouped_messages.setdefault(m.group, []).append(m)
+ return sum(len(messages) for messages in grouped_messages.values())
+
+ def generate_mojom(self):
+ self._extract_messages()
+ if not self._interface_definitions:
+ return
+ yield """// 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.
+"""
+ yield 'module %s;\n' % self._namespace
+ for import_statement in self._typemaps.format_imports():
+ yield import_statement
+ for typemap in self._typemaps.format_new_types():
+ yield typemap
+ for interface in self._interface_definitions:
+ yield interface
+ yield ''
+
+ def generate_typemap(self, output_mojom, input_filename):
+ return '\n'.join(self._typemaps.generate_typemap(
+ output_mojom, input_filename, self._namespace)).strip()
+
+ @staticmethod
+ def _find_typemaps():
+ return subprocess.check_output(
+ ['git', 'ls-files', '*.typemap']).strip().split('\n')
+
+ def _format_interface(self, name, messages):
+ return 'interface %s {\n %s\n};' % (name,
+ '\n '.join(m.format(self._typemaps)
+ for m in messages))
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('input', help='input messages.h file')
+ parser.add_argument(
+ '--output_namespace',
+ default='mojom',
+ help='the mojom module name to use in the generated mojom file '
+ '(default: %(default)s)')
+ parser.add_argument('--output_mojom', help='output mojom path')
+ parser.add_argument('--output_typemap', help='output typemap path')
+ parser.add_argument(
+ '--count',
+ action='store_true',
+ default=False,
+ help='count the number of messages in the input instead of generating '
+ 'a mojom file')
+ parser.add_argument('-v',
+ '--verbose',
+ action='store_true',
+ help='enable logging')
+ parser.add_argument('-vv', action='store_true', help='enable debug logging')
+ return parser.parse_args()
+
+
+def main():
+ args = parse_args()
+ if args.vv:
+ logging.basicConfig(level=logging.DEBUG)
+ elif args.verbose:
+ logging.basicConfig(level=logging.INFO)
+ generator = Generator(args.input, args.output_namespace)
+ if args.count:
+ count = generator.count()
+ if count:
+ print '%d %s' % (generator.count(), args.input)
+ return
+ mojom = '\n'.join(generator.generate_mojom()).strip()
+ if not mojom:
+ return
+ typemap = generator.generate_typemap(args.output_mojom, args.input)
+
+ if args.output_mojom:
+ with open(args.output_mojom, 'w') as f:
+ f.write(mojom)
+ else:
+ print mojom
+ if typemap:
+ if args.output_typemap:
+ with open(args.output_typemap, 'w') as f:
+ f.write(typemap)
+ else:
+ print typemap
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/mojo/public/tools/gn/zip.py b/mojo/public/tools/gn/zip.py
new file mode 100755
index 0000000000..adc9cb1cba
--- /dev/null
+++ b/mojo/public/tools/gn/zip.py
@@ -0,0 +1,86 @@
+#!/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.
+
+# TODO(brettw) bug 582594: merge this with build/android/gn/zip.py and update
+# callers to use the existing template rather than invoking this directly.
+
+"""Archives a set of files.
+"""
+
+import optparse
+import os
+import sys
+import zipfile
+
+sys.path.append(os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, os.pardir, os.pardir,
+ "build"))
+import gn_helpers
+
+sys.path.append(os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, os.pardir, os.pardir,
+ 'build', 'android', 'gyp'))
+from util import build_utils
+
+
+def DoZip(inputs, link_inputs, zip_inputs, output, base_dir):
+ files = []
+ with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as outfile:
+ for f in inputs:
+ file_name = os.path.relpath(f, base_dir)
+ files.append(file_name)
+ build_utils.AddToZipHermetic(outfile, file_name, f)
+ for f in link_inputs:
+ realf = os.path.realpath(f) # Resolve symlinks.
+ file_name = os.path.relpath(realf, base_dir)
+ files.append(file_name)
+ build_utils.AddToZipHermetic(outfile, file_name, realf)
+ for zf_name in zip_inputs:
+ with zipfile.ZipFile(zf_name, 'r') as zf:
+ for f in zf.namelist():
+ if f not in files:
+ files.append(f)
+ build_utils.AddToZipHermetic(outfile, f, data=zf.read(f))
+
+
+def main():
+ parser = optparse.OptionParser()
+
+ parser.add_option('--inputs',
+ help='GN format list of files to archive.')
+ parser.add_option('--link-inputs',
+ help='GN-format list of files to archive. Symbolic links are resolved.')
+ parser.add_option('--zip-inputs',
+ help='GN-format list of zip files to re-archive.')
+ parser.add_option('--output', help='Path to output archive.')
+ parser.add_option('--base-dir',
+ help='If provided, the paths in the archive will be '
+ 'relative to this directory', default='.')
+
+ options, _ = parser.parse_args()
+
+ inputs = []
+ if (options.inputs):
+ parser = gn_helpers.GNValueParser(options.inputs)
+ inputs = parser.ParseList()
+
+ link_inputs = []
+ if options.link_inputs:
+ parser = gn_helpers.GNValueParser(options.link_inputs)
+ link_inputs = parser.ParseList()
+
+ zip_inputs = []
+ if options.zip_inputs:
+ parser = gn_helpers.GNValueParser(options.zip_inputs)
+ zip_inputs = parser.ParseList()
+
+ output = options.output
+ base_dir = options.base_dir
+
+ DoZip(inputs, link_inputs, zip_inputs, output, base_dir)
+
+if __name__ == '__main__':
+ sys.exit(main())