|  | // Copyright 2013 The Flutter 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 a historical legacy, predating the proc table API. It has been | 
|  | // updated to continue to work with the proc table, but new tests should not | 
|  | // rely on replacements set up here, but instead use test-local replacements | 
|  | // for any functions relevant to that test. | 
|  | // | 
|  | // Over time existing tests should be migrated and this file should be removed. | 
|  |  | 
|  | #include <cstring> | 
|  | #include <unordered_map> | 
|  | #include <utility> | 
|  |  | 
|  | #include "flutter/shell/platform/embedder/embedder.h" | 
|  | #include "flutter/shell/platform/linux/fl_method_codec_private.h" | 
|  | #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" | 
|  | #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_response.h" | 
|  | #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | const int32_t kFlutterSemanticsNodeIdBatchEnd = -1; | 
|  | const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1; | 
|  |  | 
|  | struct _FlutterEngineTexture { | 
|  | bool has_new_frame; | 
|  | }; | 
|  |  | 
|  | struct _FlutterEngine { | 
|  | bool running = false; | 
|  | FlutterPlatformMessageCallback platform_message_callback; | 
|  | FlutterTaskRunnerPostTaskCallback platform_post_task_callback; | 
|  | void* user_data; | 
|  | std::unordered_map<int64_t, _FlutterEngineTexture> textures; | 
|  |  | 
|  | _FlutterEngine(FlutterPlatformMessageCallback platform_message_callback, | 
|  | FlutterTaskRunnerPostTaskCallback platform_post_task_callback, | 
|  | void* user_data) | 
|  | : platform_message_callback(platform_message_callback), | 
|  | platform_post_task_callback(platform_post_task_callback), | 
|  | user_data(user_data) {} | 
|  | }; | 
|  |  | 
|  | struct _FlutterPlatformMessageResponseHandle { | 
|  | FlutterDataCallback data_callback; | 
|  | void* user_data; | 
|  | std::string channel; | 
|  | bool released; | 
|  |  | 
|  | // Constructor for a response handle generated by the engine. | 
|  | explicit _FlutterPlatformMessageResponseHandle(std::string channel) | 
|  | : data_callback(nullptr), | 
|  | user_data(nullptr), | 
|  | channel(std::move(channel)), | 
|  | released(false) {} | 
|  |  | 
|  | // Constructor for a response handle generated by the shell. | 
|  | _FlutterPlatformMessageResponseHandle(FlutterDataCallback data_callback, | 
|  | void* user_data) | 
|  | : data_callback(data_callback), user_data(user_data), released(false) {} | 
|  | }; | 
|  |  | 
|  | struct _FlutterTaskRunner { | 
|  | uint64_t task; | 
|  | std::string channel; | 
|  | const FlutterPlatformMessageResponseHandle* response_handle; | 
|  | uint8_t* message; | 
|  | size_t message_size; | 
|  |  | 
|  | _FlutterTaskRunner( | 
|  | uint64_t task, | 
|  | const std::string& channel, | 
|  | const FlutterPlatformMessageResponseHandle* response_handle, | 
|  | const uint8_t* message, | 
|  | size_t message_size) | 
|  | : task(task), | 
|  | channel(channel), | 
|  | response_handle(response_handle), | 
|  | message_size(message_size) { | 
|  | if (message_size > 0) { | 
|  | this->message = static_cast<uint8_t*>(malloc(message_size)); | 
|  | memcpy(this->message, message, message_size); | 
|  | } else { | 
|  | this->message = nullptr; | 
|  | } | 
|  | } | 
|  | ~_FlutterTaskRunner() { | 
|  | if (response_handle != nullptr) { | 
|  | EXPECT_TRUE(response_handle->released); | 
|  | delete response_handle; | 
|  | } | 
|  | free(message); | 
|  | } | 
|  | }; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Send a response from the engine. | 
|  | static void send_response( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const std::string& channel, | 
|  | const FlutterPlatformMessageResponseHandle* response_handle, | 
|  | const uint8_t* message, | 
|  | size_t message_size) { | 
|  | if (response_handle == nullptr) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | FlutterTask task; | 
|  | task.runner = new _FlutterTaskRunner(1234, channel, response_handle, message, | 
|  | message_size); | 
|  | task.task = task.runner->task; | 
|  | engine->platform_post_task_callback(task, 0, engine->user_data); | 
|  | } | 
|  |  | 
|  | // Send a message from the engine. | 
|  | static void send_message(FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const std::string& channel, | 
|  | const uint8_t* message, | 
|  | size_t message_size) { | 
|  | FlutterTask task; | 
|  | task.runner = | 
|  | new _FlutterTaskRunner(1234, channel, nullptr, message, message_size); | 
|  | task.task = task.runner->task; | 
|  | engine->platform_post_task_callback(task, 0, engine->user_data); | 
|  | } | 
|  |  | 
|  | static void invoke_method(FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const std::string& channel, | 
|  | const gchar* name, | 
|  | FlValue* args) { | 
|  | g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | 
|  | g_autoptr(GError) error = nullptr; | 
|  | g_autoptr(GBytes) message = fl_method_codec_encode_method_call( | 
|  | FL_METHOD_CODEC(codec), name, args, &error); | 
|  | EXPECT_NE(message, nullptr); | 
|  | EXPECT_EQ(error, nullptr); | 
|  |  | 
|  | FlutterTask task; | 
|  | task.runner = new _FlutterTaskRunner( | 
|  | 1234, channel, nullptr, | 
|  | static_cast<const uint8_t*>(g_bytes_get_data(message, nullptr)), | 
|  | g_bytes_get_size(message)); | 
|  | task.task = task.runner->task; | 
|  | engine->platform_post_task_callback(task, 0, engine->user_data); | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineCreateAOTData( | 
|  | const FlutterEngineAOTDataSource* source, | 
|  | FlutterEngineAOTData* data_out) { | 
|  | *data_out = nullptr; | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineInitialize(size_t version, | 
|  | const FlutterRendererConfig* config, | 
|  | const FlutterProjectArgs* args, | 
|  | void* user_data, | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) * | 
|  | engine_out) { | 
|  | EXPECT_NE(config, nullptr); | 
|  |  | 
|  | EXPECT_NE(args, nullptr); | 
|  | EXPECT_NE(args->platform_message_callback, nullptr); | 
|  | EXPECT_NE(args->custom_task_runners, nullptr); | 
|  | EXPECT_NE(args->custom_task_runners->platform_task_runner, nullptr); | 
|  | EXPECT_NE(args->custom_task_runners->platform_task_runner->post_task_callback, | 
|  | nullptr); | 
|  |  | 
|  | EXPECT_NE(user_data, nullptr); | 
|  |  | 
|  | EXPECT_EQ(config->type, kOpenGL); | 
|  |  | 
|  | *engine_out = new _FlutterEngine( | 
|  | args->platform_message_callback, | 
|  | args->custom_task_runners->platform_task_runner->post_task_callback, | 
|  | user_data); | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineRunInitialized( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine) { | 
|  | engine->running = true; | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineRun(size_t version, | 
|  | const FlutterRendererConfig* config, | 
|  | const FlutterProjectArgs* args, | 
|  | void* user_data, | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) * | 
|  | engine_out) { | 
|  | EXPECT_NE(config, nullptr); | 
|  | EXPECT_NE(args, nullptr); | 
|  | EXPECT_NE(user_data, nullptr); | 
|  | EXPECT_NE(engine_out, nullptr); | 
|  |  | 
|  | FlutterEngineResult result = | 
|  | FlutterEngineInitialize(version, config, args, user_data, engine_out); | 
|  | if (result != kSuccess) { | 
|  | return result; | 
|  | } | 
|  | return FlutterEngineRunInitialized(*engine_out); | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) | 
|  | engine) { | 
|  | delete engine; | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine) | 
|  | engine) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineSendWindowMetricsEvent( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const FlutterWindowMetricsEvent* event) { | 
|  | EXPECT_TRUE(engine->running); | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineSendPointerEvent( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const FlutterPointerEvent* events, | 
|  | size_t events_count) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine) | 
|  | engine, | 
|  | const FlutterKeyEvent* event, | 
|  | FlutterKeyEventCallback callback, | 
|  | void* user_data) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FLUTTER_EXPORT | 
|  | FlutterEngineResult FlutterEngineSendPlatformMessage( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const FlutterPlatformMessage* message) { | 
|  | EXPECT_TRUE(engine->running); | 
|  |  | 
|  | if (strcmp(message->channel, "test/echo") == 0) { | 
|  | // Responds with the same message received. | 
|  | send_response(engine, message->channel, message->response_handle, | 
|  | message->message, message->message_size); | 
|  | } else if (strcmp(message->channel, "test/send-message") == 0) { | 
|  | // Triggers the engine to send a message. | 
|  | send_response(engine, message->channel, message->response_handle, nullptr, | 
|  | 0); | 
|  | send_message(engine, "test/messages", message->message, | 
|  | message->message_size); | 
|  | } else if (strcmp(message->channel, "test/standard-method") == 0) { | 
|  | g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | 
|  | g_autoptr(GBytes) m = g_bytes_new(message->message, message->message_size); | 
|  | g_autofree gchar* name = nullptr; | 
|  | g_autoptr(FlValue) args = nullptr; | 
|  | g_autoptr(GError) error = nullptr; | 
|  | EXPECT_TRUE(fl_method_codec_decode_method_call(FL_METHOD_CODEC(codec), m, | 
|  | &name, &args, &error)); | 
|  | EXPECT_EQ(error, nullptr); | 
|  |  | 
|  | g_autoptr(GBytes) response = nullptr; | 
|  | if (strcmp(name, "Echo") == 0) { | 
|  | // Returns args as a success result. | 
|  | response = fl_method_codec_encode_success_envelope(FL_METHOD_CODEC(codec), | 
|  | args, &error); | 
|  | EXPECT_EQ(error, nullptr); | 
|  | } else if (strcmp(name, "Error") == 0) { | 
|  | // Returns an error result. | 
|  | const gchar* code = nullptr; | 
|  | const gchar* message = nullptr; | 
|  | FlValue* details = nullptr; | 
|  | if (fl_value_get_length(args) >= 2) { | 
|  | FlValue* code_value = fl_value_get_list_value(args, 0); | 
|  | EXPECT_EQ(fl_value_get_type(code_value), FL_VALUE_TYPE_STRING); | 
|  | code = fl_value_get_string(code_value); | 
|  | FlValue* message_value = fl_value_get_list_value(args, 1); | 
|  | message = fl_value_get_type(message_value) == FL_VALUE_TYPE_STRING | 
|  | ? fl_value_get_string(message_value) | 
|  | : nullptr; | 
|  | } | 
|  | if (fl_value_get_length(args) >= 3) { | 
|  | details = fl_value_get_list_value(args, 2); | 
|  | } | 
|  | response = fl_method_codec_encode_error_envelope( | 
|  | FL_METHOD_CODEC(codec), code, message, details, &error); | 
|  | EXPECT_EQ(error, nullptr); | 
|  | } else if (strcmp(name, "InvokeMethod") == 0) { | 
|  | // Gets the engine to call the shell. | 
|  | if (fl_value_get_length(args) == 3) { | 
|  | FlValue* channel_value = fl_value_get_list_value(args, 0); | 
|  | EXPECT_EQ(fl_value_get_type(channel_value), FL_VALUE_TYPE_STRING); | 
|  | const gchar* channel = fl_value_get_string(channel_value); | 
|  | FlValue* name_value = fl_value_get_list_value(args, 1); | 
|  | EXPECT_EQ(fl_value_get_type(name_value), FL_VALUE_TYPE_STRING); | 
|  | const gchar* name = fl_value_get_string(name_value); | 
|  | FlValue* method_args = fl_value_get_list_value(args, 2); | 
|  | invoke_method(engine, channel, name, method_args); | 
|  | } | 
|  | response = fl_method_codec_encode_success_envelope(FL_METHOD_CODEC(codec), | 
|  | nullptr, &error); | 
|  | EXPECT_EQ(error, nullptr); | 
|  | } else { | 
|  | // Returns "not implemented". | 
|  | response = g_bytes_new(nullptr, 0); | 
|  | } | 
|  |  | 
|  | send_response( | 
|  | engine, message->channel, message->response_handle, | 
|  | static_cast<const uint8_t*>(g_bytes_get_data(response, nullptr)), | 
|  | g_bytes_get_size(response)); | 
|  | } else if (strcmp(message->channel, "test/nullptr-response") == 0) { | 
|  | // Sends a null response. | 
|  | send_response(engine, message->channel, message->response_handle, nullptr, | 
|  | 0); | 
|  | } else if (strcmp(message->channel, "test/standard-event") == 0) { | 
|  | // Send a message so the shell can check the events sent. | 
|  | send_message(engine, "test/events", message->message, | 
|  | message->message_size); | 
|  | } else if (strcmp(message->channel, "test/failure") == 0) { | 
|  | // Generates an internal error. | 
|  | return kInternalInconsistency; | 
|  | } else if (strcmp(message->channel, "test/key-event-handled") == 0 || | 
|  | strcmp(message->channel, "test/key-event-not-handled") == 0) { | 
|  | bool value = strcmp(message->channel, "test/key-event-handled") == 0; | 
|  | g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); | 
|  | g_autoptr(FlValue) handledValue = fl_value_new_map(); | 
|  | fl_value_set_string_take(handledValue, "handled", fl_value_new_bool(value)); | 
|  | g_autoptr(GBytes) response = fl_message_codec_encode_message( | 
|  | FL_MESSAGE_CODEC(codec), handledValue, nullptr); | 
|  | send_response( | 
|  | engine, message->channel, message->response_handle, | 
|  | static_cast<const uint8_t*>(g_bytes_get_data(response, nullptr)), | 
|  | g_bytes_get_size(response)); | 
|  | } else if (strcmp(message->channel, "test/key-event-delayed") == 0) { | 
|  | static std::unique_ptr<const FlutterPlatformMessageResponseHandle> | 
|  | delayed_response_handle = nullptr; | 
|  | g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); | 
|  | g_autoptr(FlValue) handledValue = fl_value_new_map(); | 
|  | fl_value_set_string_take(handledValue, "handled", fl_value_new_bool(true)); | 
|  | g_autoptr(GBytes) response = fl_message_codec_encode_message( | 
|  | FL_MESSAGE_CODEC(codec), handledValue, nullptr); | 
|  | if (delayed_response_handle == nullptr) { | 
|  | delayed_response_handle.reset(message->response_handle); | 
|  | } else { | 
|  | send_response( | 
|  | engine, message->channel, message->response_handle, | 
|  | static_cast<const uint8_t*>(g_bytes_get_data(response, nullptr)), | 
|  | g_bytes_get_size(response)); | 
|  | send_response( | 
|  | engine, message->channel, delayed_response_handle.release(), | 
|  | static_cast<const uint8_t*>(g_bytes_get_data(response, nullptr)), | 
|  | g_bytes_get_size(response)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | FlutterDataCallback data_callback, | 
|  | void* user_data, | 
|  | FlutterPlatformMessageResponseHandle** response_out) { | 
|  | EXPECT_TRUE(engine->running); | 
|  | EXPECT_NE(data_callback, nullptr); | 
|  | EXPECT_NE(user_data, nullptr); | 
|  |  | 
|  | _FlutterPlatformMessageResponseHandle* handle = | 
|  | new _FlutterPlatformMessageResponseHandle(data_callback, user_data); | 
|  |  | 
|  | *response_out = handle; | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | FlutterPlatformMessageResponseHandle* response) { | 
|  | EXPECT_NE(engine, nullptr); | 
|  | EXPECT_NE(response, nullptr); | 
|  |  | 
|  | EXPECT_TRUE(engine->running); | 
|  |  | 
|  | EXPECT_FALSE(response->released); | 
|  | response->released = true; | 
|  |  | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineSendPlatformMessageResponse( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | const FlutterPlatformMessageResponseHandle* handle, | 
|  | const uint8_t* data, | 
|  | size_t data_length) { | 
|  | EXPECT_NE(engine, nullptr); | 
|  | EXPECT_NE(handle, nullptr); | 
|  |  | 
|  | EXPECT_TRUE(engine->running); | 
|  |  | 
|  | // Send a message so the shell can check the responses received. | 
|  | if (handle->channel != "test/responses") { | 
|  | send_message(engine, "test/responses", data, data_length); | 
|  | } | 
|  |  | 
|  | EXPECT_FALSE(handle->released); | 
|  |  | 
|  | delete handle; | 
|  |  | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineRunTask(FLUTTER_API_SYMBOL(FlutterEngine) | 
|  | engine, | 
|  | const FlutterTask* task) { | 
|  | EXPECT_NE(engine, nullptr); | 
|  | EXPECT_NE(task, nullptr); | 
|  | EXPECT_NE(task->runner, nullptr); | 
|  |  | 
|  | FlutterTaskRunner runner = task->runner; | 
|  | EXPECT_NE(runner, nullptr); | 
|  | const FlutterPlatformMessageResponseHandle* response_handle = | 
|  | runner->response_handle; | 
|  | if (response_handle != nullptr) { | 
|  | EXPECT_NE(response_handle->data_callback, nullptr); | 
|  | response_handle->data_callback(runner->message, runner->message_size, | 
|  | response_handle->user_data); | 
|  | } else { | 
|  | _FlutterPlatformMessageResponseHandle* handle = | 
|  | new _FlutterPlatformMessageResponseHandle(runner->channel); | 
|  |  | 
|  | FlutterPlatformMessage message; | 
|  | message.struct_size = sizeof(FlutterPlatformMessage); | 
|  | message.channel = runner->channel.c_str(); | 
|  | message.message = runner->message; | 
|  | message.message_size = runner->message_size; | 
|  | message.response_handle = handle; | 
|  | engine->platform_message_callback(&message, engine->user_data); | 
|  | } | 
|  |  | 
|  | delete runner; | 
|  |  | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | bool FlutterEngineRunsAOTCompiledDartCode() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineUpdateLocales(FLUTTER_API_SYMBOL(FlutterEngine) | 
|  | engine, | 
|  | const FlutterLocale** locales, | 
|  | size_t locales_count) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineUpdateSemanticsEnabled( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | bool enabled) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | FlutterAccessibilityFeature features) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineDispatchSemanticsAction( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | uint64_t id, | 
|  | FlutterSemanticsAction action, | 
|  | const uint8_t* data, | 
|  | size_t data_length) { | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineRegisterExternalTexture( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | int64_t texture_identifier) { | 
|  | _FlutterEngineTexture texture; | 
|  | texture.has_new_frame = false; | 
|  | engine->textures[texture_identifier] = texture; | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineMarkExternalTextureFrameAvailable( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | int64_t texture_identifier) { | 
|  | auto val = engine->textures.find(texture_identifier); | 
|  | if (val == std::end(engine->textures)) { | 
|  | return kInvalidArguments; | 
|  | } | 
|  | val->second.has_new_frame = true; | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | FlutterEngineResult FlutterEngineUnregisterExternalTexture( | 
|  | FLUTTER_API_SYMBOL(FlutterEngine) engine, | 
|  | int64_t texture_identifier) { | 
|  | auto val = engine->textures.find(texture_identifier); | 
|  | if (val == std::end(engine->textures)) { | 
|  | return kInvalidArguments; | 
|  | } | 
|  | engine->textures.erase(texture_identifier); | 
|  | return kSuccess; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | FlutterEngineResult FlutterEngineGetProcAddresses( | 
|  | FlutterEngineProcTable* table) { | 
|  | if (!table) { | 
|  | return kInvalidArguments; | 
|  | } | 
|  |  | 
|  | FlutterEngineProcTable empty_table = {}; | 
|  | *table = empty_table; | 
|  |  | 
|  | table->CreateAOTData = &FlutterEngineCreateAOTData; | 
|  | table->CollectAOTData = &FlutterEngineCollectAOTData; | 
|  | table->Run = &FlutterEngineRun; | 
|  | table->Shutdown = &FlutterEngineShutdown; | 
|  | table->Initialize = &FlutterEngineInitialize; | 
|  | table->Deinitialize = &FlutterEngineDeinitialize; | 
|  | table->RunInitialized = &FlutterEngineRunInitialized; | 
|  | table->SendWindowMetricsEvent = &FlutterEngineSendWindowMetricsEvent; | 
|  | table->SendPointerEvent = &FlutterEngineSendPointerEvent; | 
|  | table->SendKeyEvent = &FlutterEngineSendKeyEvent; | 
|  | table->SendPlatformMessage = &FlutterEngineSendPlatformMessage; | 
|  | table->PlatformMessageCreateResponseHandle = | 
|  | &FlutterPlatformMessageCreateResponseHandle; | 
|  | table->PlatformMessageReleaseResponseHandle = | 
|  | &FlutterPlatformMessageReleaseResponseHandle; | 
|  | table->SendPlatformMessageResponse = | 
|  | &FlutterEngineSendPlatformMessageResponse; | 
|  | table->RunTask = &FlutterEngineRunTask; | 
|  | table->UpdateLocales = &FlutterEngineUpdateLocales; | 
|  | table->UpdateSemanticsEnabled = &FlutterEngineUpdateSemanticsEnabled; | 
|  | table->DispatchSemanticsAction = &FlutterEngineDispatchSemanticsAction; | 
|  | table->RunsAOTCompiledDartCode = &FlutterEngineRunsAOTCompiledDartCode; | 
|  | table->RegisterExternalTexture = &FlutterEngineRegisterExternalTexture; | 
|  | table->MarkExternalTextureFrameAvailable = | 
|  | &FlutterEngineMarkExternalTextureFrameAvailable; | 
|  | table->UnregisterExternalTexture = &FlutterEngineUnregisterExternalTexture; | 
|  | table->UpdateAccessibilityFeatures = | 
|  | &FlutterEngineUpdateAccessibilityFeatures; | 
|  | return kSuccess; | 
|  | } |