1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | // FLUTTER_NOLINT |
5 | |
6 | #define RAPIDJSON_HAS_STDSTRING 1 |
7 | #include "flutter/shell/common/shell.h" |
8 | |
9 | #include <memory> |
10 | #include <sstream> |
11 | #include <vector> |
12 | |
13 | #include "flutter/assets/directory_asset_bundle.h" |
14 | #include "flutter/fml/file.h" |
15 | #include "flutter/fml/icu_util.h" |
16 | #include "flutter/fml/log_settings.h" |
17 | #include "flutter/fml/logging.h" |
18 | #include "flutter/fml/make_copyable.h" |
19 | #include "flutter/fml/message_loop.h" |
20 | #include "flutter/fml/paths.h" |
21 | #include "flutter/fml/trace_event.h" |
22 | #include "flutter/fml/unique_fd.h" |
23 | #include "flutter/runtime/dart_vm.h" |
24 | #include "flutter/shell/common/engine.h" |
25 | #include "flutter/shell/common/persistent_cache.h" |
26 | #include "flutter/shell/common/skia_event_tracer_impl.h" |
27 | #include "flutter/shell/common/switches.h" |
28 | #include "flutter/shell/common/vsync_waiter.h" |
29 | #include "rapidjson/stringbuffer.h" |
30 | #include "rapidjson/writer.h" |
31 | #include "third_party/dart/runtime/include/dart_tools_api.h" |
32 | #include "third_party/skia/include/core/SkGraphics.h" |
33 | #include "third_party/skia/include/utils/SkBase64.h" |
34 | #include "third_party/tonic/common/log.h" |
35 | |
36 | namespace flutter { |
37 | |
38 | constexpr char kSkiaChannel[] = "flutter/skia" ; |
39 | constexpr char kSystemChannel[] = "flutter/system" ; |
40 | constexpr char kTypeKey[] = "type" ; |
41 | constexpr char kFontChange[] = "fontsChange" ; |
42 | |
43 | std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread( |
44 | DartVMRef vm, |
45 | TaskRunners task_runners, |
46 | const PlatformData platform_data, |
47 | Settings settings, |
48 | fml::RefPtr<const DartSnapshot> isolate_snapshot, |
49 | const Shell::CreateCallback<PlatformView>& on_create_platform_view, |
50 | const Shell::CreateCallback<Rasterizer>& on_create_rasterizer) { |
51 | if (!task_runners.IsValid()) { |
52 | FML_LOG(ERROR) << "Task runners to run the shell were invalid." ; |
53 | return nullptr; |
54 | } |
55 | |
56 | auto shell = |
57 | std::unique_ptr<Shell>(new Shell(std::move(vm), task_runners, settings)); |
58 | |
59 | // Create the rasterizer on the raster thread. |
60 | std::promise<std::unique_ptr<Rasterizer>> rasterizer_promise; |
61 | auto rasterizer_future = rasterizer_promise.get_future(); |
62 | std::promise<fml::WeakPtr<SnapshotDelegate>> snapshot_delegate_promise; |
63 | auto snapshot_delegate_future = snapshot_delegate_promise.get_future(); |
64 | fml::TaskRunner::RunNowOrPostTask( |
65 | task_runners.GetRasterTaskRunner(), [&rasterizer_promise, // |
66 | &snapshot_delegate_promise, |
67 | on_create_rasterizer, // |
68 | shell = shell.get() // |
69 | ]() { |
70 | TRACE_EVENT0("flutter" , "ShellSetupGPUSubsystem" ); |
71 | std::unique_ptr<Rasterizer> rasterizer(on_create_rasterizer(*shell)); |
72 | snapshot_delegate_promise.set_value(rasterizer->GetSnapshotDelegate()); |
73 | rasterizer_promise.set_value(std::move(rasterizer)); |
74 | }); |
75 | |
76 | // Create the platform view on the platform thread (this thread). |
77 | auto platform_view = on_create_platform_view(*shell.get()); |
78 | if (!platform_view || !platform_view->GetWeakPtr()) { |
79 | return nullptr; |
80 | } |
81 | |
82 | // Ask the platform view for the vsync waiter. This will be used by the engine |
83 | // to create the animator. |
84 | auto vsync_waiter = platform_view->CreateVSyncWaiter(); |
85 | if (!vsync_waiter) { |
86 | return nullptr; |
87 | } |
88 | |
89 | // Create the IO manager on the IO thread. The IO manager must be initialized |
90 | // first because it has state that the other subsystems depend on. It must |
91 | // first be booted and the necessary references obtained to initialize the |
92 | // other subsystems. |
93 | std::promise<std::unique_ptr<ShellIOManager>> io_manager_promise; |
94 | auto io_manager_future = io_manager_promise.get_future(); |
95 | std::promise<fml::WeakPtr<ShellIOManager>> weak_io_manager_promise; |
96 | auto weak_io_manager_future = weak_io_manager_promise.get_future(); |
97 | std::promise<fml::RefPtr<SkiaUnrefQueue>> unref_queue_promise; |
98 | auto unref_queue_future = unref_queue_promise.get_future(); |
99 | auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner(); |
100 | |
101 | // TODO(gw280): The WeakPtr here asserts that we are derefing it on the |
102 | // same thread as it was created on. We are currently on the IO thread |
103 | // inside this lambda but we need to deref the PlatformView, which was |
104 | // constructed on the platform thread. |
105 | // |
106 | // https://github.com/flutter/flutter/issues/42948 |
107 | fml::TaskRunner::RunNowOrPostTask( |
108 | io_task_runner, |
109 | [&io_manager_promise, // |
110 | &weak_io_manager_promise, // |
111 | &unref_queue_promise, // |
112 | platform_view = platform_view->GetWeakPtr(), // |
113 | io_task_runner, // |
114 | is_backgrounded_sync_switch = shell->GetIsGpuDisabledSyncSwitch() // |
115 | ]() { |
116 | TRACE_EVENT0("flutter" , "ShellSetupIOSubsystem" ); |
117 | auto io_manager = std::make_unique<ShellIOManager>( |
118 | platform_view.getUnsafe()->CreateResourceContext(), |
119 | is_backgrounded_sync_switch, io_task_runner); |
120 | weak_io_manager_promise.set_value(io_manager->GetWeakPtr()); |
121 | unref_queue_promise.set_value(io_manager->GetSkiaUnrefQueue()); |
122 | io_manager_promise.set_value(std::move(io_manager)); |
123 | }); |
124 | |
125 | // Send dispatcher_maker to the engine constructor because shell won't have |
126 | // platform_view set until Shell::Setup is called later. |
127 | auto dispatcher_maker = platform_view->GetDispatcherMaker(); |
128 | |
129 | // Create the engine on the UI thread. |
130 | std::promise<std::unique_ptr<Engine>> engine_promise; |
131 | auto engine_future = engine_promise.get_future(); |
132 | fml::TaskRunner::RunNowOrPostTask( |
133 | shell->GetTaskRunners().GetUITaskRunner(), |
134 | fml::MakeCopyable([&engine_promise, // |
135 | shell = shell.get(), // |
136 | &dispatcher_maker, // |
137 | &platform_data, // |
138 | isolate_snapshot = std::move(isolate_snapshot), // |
139 | vsync_waiter = std::move(vsync_waiter), // |
140 | &weak_io_manager_future, // |
141 | &snapshot_delegate_future, // |
142 | &unref_queue_future // |
143 | ]() mutable { |
144 | TRACE_EVENT0("flutter" , "ShellSetupUISubsystem" ); |
145 | const auto& task_runners = shell->GetTaskRunners(); |
146 | |
147 | // The animator is owned by the UI thread but it gets its vsync pulses |
148 | // from the platform. |
149 | auto animator = std::make_unique<Animator>(*shell, task_runners, |
150 | std::move(vsync_waiter)); |
151 | |
152 | engine_promise.set_value(std::make_unique<Engine>( |
153 | *shell, // |
154 | dispatcher_maker, // |
155 | *shell->GetDartVM(), // |
156 | std::move(isolate_snapshot), // |
157 | task_runners, // |
158 | platform_data, // |
159 | shell->GetSettings(), // |
160 | std::move(animator), // |
161 | weak_io_manager_future.get(), // |
162 | unref_queue_future.get(), // |
163 | snapshot_delegate_future.get() // |
164 | )); |
165 | })); |
166 | |
167 | if (!shell->Setup(std::move(platform_view), // |
168 | engine_future.get(), // |
169 | rasterizer_future.get(), // |
170 | io_manager_future.get()) // |
171 | ) { |
172 | return nullptr; |
173 | } |
174 | |
175 | return shell; |
176 | } |
177 | |
178 | static void Tokenize(const std::string& input, |
179 | std::vector<std::string>* results, |
180 | char delimiter) { |
181 | std::istringstream ss(input); |
182 | std::string token; |
183 | while (std::getline(ss, token, delimiter)) { |
184 | results->push_back(token); |
185 | } |
186 | } |
187 | |
188 | // Though there can be multiple shells, some settings apply to all components in |
189 | // the process. These have to be setup before the shell or any of its |
190 | // sub-components can be initialized. In a perfect world, this would be empty. |
191 | // TODO(chinmaygarde): The unfortunate side effect of this call is that settings |
192 | // that cause shell initialization failures will still lead to some of their |
193 | // settings being applied. |
194 | static void PerformInitializationTasks(Settings& settings) { |
195 | { |
196 | fml::LogSettings log_settings; |
197 | log_settings.min_log_level = |
198 | settings.verbose_logging ? fml::LOG_INFO : fml::LOG_ERROR; |
199 | fml::SetLogSettings(log_settings); |
200 | } |
201 | |
202 | static std::once_flag gShellSettingsInitialization = {}; |
203 | std::call_once(gShellSettingsInitialization, [&settings] { |
204 | if (settings.engine_start_timestamp.count() == 0) { |
205 | settings.engine_start_timestamp = |
206 | std::chrono::microseconds(Dart_TimelineGetMicros()); |
207 | } |
208 | |
209 | tonic::SetLogHandler( |
210 | [](const char* message) { FML_LOG(ERROR) << message; }); |
211 | |
212 | if (settings.trace_skia) { |
213 | InitSkiaEventTracer(settings.trace_skia); |
214 | } |
215 | |
216 | if (!settings.trace_allowlist.empty()) { |
217 | std::vector<std::string> prefixes; |
218 | Tokenize(settings.trace_allowlist, &prefixes, ','); |
219 | fml::tracing::TraceSetAllowlist(prefixes); |
220 | } |
221 | |
222 | if (!settings.skia_deterministic_rendering_on_cpu) { |
223 | SkGraphics::Init(); |
224 | } else { |
225 | FML_DLOG(INFO) << "Skia deterministic rendering is enabled." ; |
226 | } |
227 | |
228 | if (settings.icu_initialization_required) { |
229 | if (settings.icu_data_path.size() != 0) { |
230 | fml::icu::InitializeICU(settings.icu_data_path); |
231 | } else if (settings.icu_mapper) { |
232 | fml::icu::InitializeICUFromMapping(settings.icu_mapper()); |
233 | } else { |
234 | FML_DLOG(WARNING) << "Skipping ICU initialization in the shell." ; |
235 | } |
236 | } |
237 | }); |
238 | } |
239 | |
240 | std::unique_ptr<Shell> Shell::Create( |
241 | TaskRunners task_runners, |
242 | Settings settings, |
243 | const Shell::CreateCallback<PlatformView>& on_create_platform_view, |
244 | const Shell::CreateCallback<Rasterizer>& on_create_rasterizer) { |
245 | return Shell::Create(std::move(task_runners), // |
246 | PlatformData{/* default platform data */}, // |
247 | std::move(settings), // |
248 | std::move(on_create_platform_view), // |
249 | std::move(on_create_rasterizer) // |
250 | ); |
251 | } |
252 | |
253 | std::unique_ptr<Shell> Shell::Create( |
254 | TaskRunners task_runners, |
255 | const PlatformData platform_data, |
256 | Settings settings, |
257 | Shell::CreateCallback<PlatformView> on_create_platform_view, |
258 | Shell::CreateCallback<Rasterizer> on_create_rasterizer) { |
259 | PerformInitializationTasks(settings); |
260 | PersistentCache::SetCacheSkSL(settings.cache_sksl); |
261 | |
262 | TRACE_EVENT0("flutter" , "Shell::Create" ); |
263 | |
264 | auto vm = DartVMRef::Create(settings); |
265 | FML_CHECK(vm) << "Must be able to initialize the VM." ; |
266 | |
267 | auto vm_data = vm->GetVMData(); |
268 | |
269 | return Shell::Create(std::move(task_runners), // |
270 | std::move(platform_data), // |
271 | std::move(settings), // |
272 | vm_data->GetIsolateSnapshot(), // isolate snapshot |
273 | on_create_platform_view, // |
274 | on_create_rasterizer, // |
275 | std::move(vm) // |
276 | ); |
277 | } |
278 | |
279 | std::unique_ptr<Shell> Shell::Create( |
280 | TaskRunners task_runners, |
281 | const PlatformData platform_data, |
282 | Settings settings, |
283 | fml::RefPtr<const DartSnapshot> isolate_snapshot, |
284 | const Shell::CreateCallback<PlatformView>& on_create_platform_view, |
285 | const Shell::CreateCallback<Rasterizer>& on_create_rasterizer, |
286 | DartVMRef vm) { |
287 | PerformInitializationTasks(settings); |
288 | PersistentCache::SetCacheSkSL(settings.cache_sksl); |
289 | |
290 | TRACE_EVENT0("flutter" , "Shell::CreateWithSnapshots" ); |
291 | |
292 | if (!task_runners.IsValid() || !on_create_platform_view || |
293 | !on_create_rasterizer) { |
294 | return nullptr; |
295 | } |
296 | |
297 | fml::AutoResetWaitableEvent latch; |
298 | std::unique_ptr<Shell> shell; |
299 | fml::TaskRunner::RunNowOrPostTask( |
300 | task_runners.GetPlatformTaskRunner(), |
301 | fml::MakeCopyable([&latch, // |
302 | vm = std::move(vm), // |
303 | &shell, // |
304 | task_runners = std::move(task_runners), // |
305 | platform_data, // |
306 | settings, // |
307 | isolate_snapshot = std::move(isolate_snapshot), // |
308 | on_create_platform_view, // |
309 | on_create_rasterizer // |
310 | ]() mutable { |
311 | shell = CreateShellOnPlatformThread(std::move(vm), |
312 | std::move(task_runners), // |
313 | platform_data, // |
314 | settings, // |
315 | std::move(isolate_snapshot), // |
316 | on_create_platform_view, // |
317 | on_create_rasterizer // |
318 | ); |
319 | latch.Signal(); |
320 | })); |
321 | latch.Wait(); |
322 | return shell; |
323 | } |
324 | |
325 | Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings) |
326 | : task_runners_(std::move(task_runners)), |
327 | settings_(std::move(settings)), |
328 | vm_(std::move(vm)), |
329 | is_gpu_disabled_sync_switch_(new fml::SyncSwitch()), |
330 | weak_factory_(this), |
331 | weak_factory_gpu_(nullptr) { |
332 | FML_CHECK(vm_) << "Must have access to VM to create a shell." ; |
333 | FML_DCHECK(task_runners_.IsValid()); |
334 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
335 | |
336 | // Generate a WeakPtrFactory for use with the raster thread. This does not |
337 | // need to wait on a latch because it can only ever be used from the raster |
338 | // thread from this class, so we have ordering guarantees. |
339 | fml::TaskRunner::RunNowOrPostTask( |
340 | task_runners_.GetRasterTaskRunner(), fml::MakeCopyable([this]() mutable { |
341 | this->weak_factory_gpu_ = |
342 | std::make_unique<fml::TaskRunnerAffineWeakPtrFactory<Shell>>(this); |
343 | })); |
344 | |
345 | // Install service protocol handlers. |
346 | |
347 | service_protocol_handlers_[ServiceProtocol::kScreenshotExtensionName] = { |
348 | task_runners_.GetRasterTaskRunner(), |
349 | std::bind(&Shell::OnServiceProtocolScreenshot, this, |
350 | std::placeholders::_1, std::placeholders::_2)}; |
351 | service_protocol_handlers_[ServiceProtocol::kScreenshotSkpExtensionName] = { |
352 | task_runners_.GetRasterTaskRunner(), |
353 | std::bind(&Shell::OnServiceProtocolScreenshotSKP, this, |
354 | std::placeholders::_1, std::placeholders::_2)}; |
355 | service_protocol_handlers_[ServiceProtocol::kRunInViewExtensionName] = { |
356 | task_runners_.GetUITaskRunner(), |
357 | std::bind(&Shell::OnServiceProtocolRunInView, this, std::placeholders::_1, |
358 | std::placeholders::_2)}; |
359 | service_protocol_handlers_ |
360 | [ServiceProtocol::kFlushUIThreadTasksExtensionName] = { |
361 | task_runners_.GetUITaskRunner(), |
362 | std::bind(&Shell::OnServiceProtocolFlushUIThreadTasks, this, |
363 | std::placeholders::_1, std::placeholders::_2)}; |
364 | service_protocol_handlers_ |
365 | [ServiceProtocol::kSetAssetBundlePathExtensionName] = { |
366 | task_runners_.GetUITaskRunner(), |
367 | std::bind(&Shell::OnServiceProtocolSetAssetBundlePath, this, |
368 | std::placeholders::_1, std::placeholders::_2)}; |
369 | service_protocol_handlers_ |
370 | [ServiceProtocol::kGetDisplayRefreshRateExtensionName] = { |
371 | task_runners_.GetUITaskRunner(), |
372 | std::bind(&Shell::OnServiceProtocolGetDisplayRefreshRate, this, |
373 | std::placeholders::_1, std::placeholders::_2)}; |
374 | service_protocol_handlers_[ServiceProtocol::kGetSkSLsExtensionName] = { |
375 | task_runners_.GetIOTaskRunner(), |
376 | std::bind(&Shell::OnServiceProtocolGetSkSLs, this, std::placeholders::_1, |
377 | std::placeholders::_2)}; |
378 | service_protocol_handlers_ |
379 | [ServiceProtocol::kEstimateRasterCacheMemoryExtensionName] = { |
380 | task_runners_.GetRasterTaskRunner(), |
381 | std::bind(&Shell::OnServiceProtocolEstimateRasterCacheMemory, this, |
382 | std::placeholders::_1, std::placeholders::_2)}; |
383 | } |
384 | |
385 | Shell::~Shell() { |
386 | PersistentCache::GetCacheForProcess()->RemoveWorkerTaskRunner( |
387 | task_runners_.GetIOTaskRunner()); |
388 | |
389 | vm_->GetServiceProtocol()->RemoveHandler(this); |
390 | |
391 | fml::AutoResetWaitableEvent ui_latch, gpu_latch, platform_latch, io_latch; |
392 | |
393 | fml::TaskRunner::RunNowOrPostTask( |
394 | task_runners_.GetUITaskRunner(), |
395 | fml::MakeCopyable([engine = std::move(engine_), &ui_latch]() mutable { |
396 | engine.reset(); |
397 | ui_latch.Signal(); |
398 | })); |
399 | ui_latch.Wait(); |
400 | |
401 | fml::TaskRunner::RunNowOrPostTask( |
402 | task_runners_.GetRasterTaskRunner(), |
403 | fml::MakeCopyable([rasterizer = std::move(rasterizer_), |
404 | weak_factory_gpu = std::move(weak_factory_gpu_), |
405 | &gpu_latch]() mutable { |
406 | rasterizer.reset(); |
407 | weak_factory_gpu.reset(); |
408 | gpu_latch.Signal(); |
409 | })); |
410 | gpu_latch.Wait(); |
411 | |
412 | fml::TaskRunner::RunNowOrPostTask( |
413 | task_runners_.GetIOTaskRunner(), |
414 | fml::MakeCopyable([io_manager = std::move(io_manager_), |
415 | platform_view = platform_view_.get(), |
416 | &io_latch]() mutable { |
417 | io_manager.reset(); |
418 | if (platform_view) { |
419 | platform_view->ReleaseResourceContext(); |
420 | } |
421 | io_latch.Signal(); |
422 | })); |
423 | |
424 | io_latch.Wait(); |
425 | |
426 | // The platform view must go last because it may be holding onto platform side |
427 | // counterparts to resources owned by subsystems running on other threads. For |
428 | // example, the NSOpenGLContext on the Mac. |
429 | fml::TaskRunner::RunNowOrPostTask( |
430 | task_runners_.GetPlatformTaskRunner(), |
431 | fml::MakeCopyable([platform_view = std::move(platform_view_), |
432 | &platform_latch]() mutable { |
433 | platform_view.reset(); |
434 | platform_latch.Signal(); |
435 | })); |
436 | platform_latch.Wait(); |
437 | } |
438 | |
439 | void Shell::NotifyLowMemoryWarning() const { |
440 | auto trace_id = fml::tracing::TraceNonce(); |
441 | TRACE_EVENT_ASYNC_BEGIN0("flutter" , "Shell::NotifyLowMemoryWarning" , |
442 | trace_id); |
443 | // This does not require a current isolate but does require a running VM. |
444 | // Since a valid shell will not be returned to the embedder without a valid |
445 | // DartVMRef, we can be certain that this is a safe spot to assume a VM is |
446 | // running. |
447 | ::Dart_NotifyLowMemory(); |
448 | |
449 | task_runners_.GetRasterTaskRunner()->PostTask( |
450 | [rasterizer = rasterizer_->GetWeakPtr(), trace_id = trace_id]() { |
451 | if (rasterizer) { |
452 | rasterizer->NotifyLowMemoryWarning(); |
453 | } |
454 | TRACE_EVENT_ASYNC_END0("flutter" , "Shell::NotifyLowMemoryWarning" , |
455 | trace_id); |
456 | }); |
457 | // The IO Manager uses resource cache limits of 0, so it is not necessary |
458 | // to purge them. |
459 | } |
460 | |
461 | void Shell::RunEngine(RunConfiguration run_configuration) { |
462 | RunEngine(std::move(run_configuration), nullptr); |
463 | } |
464 | |
465 | void Shell::RunEngine( |
466 | RunConfiguration run_configuration, |
467 | const std::function<void(Engine::RunStatus)>& result_callback) { |
468 | auto result = [platform_runner = task_runners_.GetPlatformTaskRunner(), |
469 | result_callback](Engine::RunStatus run_result) { |
470 | if (!result_callback) { |
471 | return; |
472 | } |
473 | platform_runner->PostTask( |
474 | [result_callback, run_result]() { result_callback(run_result); }); |
475 | }; |
476 | FML_DCHECK(is_setup_); |
477 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
478 | |
479 | fml::TaskRunner::RunNowOrPostTask( |
480 | task_runners_.GetUITaskRunner(), |
481 | fml::MakeCopyable( |
482 | [run_configuration = std::move(run_configuration), |
483 | weak_engine = weak_engine_, result]() mutable { |
484 | if (!weak_engine) { |
485 | FML_LOG(ERROR) |
486 | << "Could not launch engine with configuration - no engine." ; |
487 | result(Engine::RunStatus::Failure); |
488 | return; |
489 | } |
490 | auto run_result = weak_engine->Run(std::move(run_configuration)); |
491 | if (run_result == flutter::Engine::RunStatus::Failure) { |
492 | FML_LOG(ERROR) << "Could not launch engine with configuration." ; |
493 | } |
494 | result(run_result); |
495 | })); |
496 | } |
497 | |
498 | std::optional<DartErrorCode> Shell::GetUIIsolateLastError() const { |
499 | FML_DCHECK(is_setup_); |
500 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
501 | |
502 | if (!weak_engine_) { |
503 | return std::nullopt; |
504 | } |
505 | switch (weak_engine_->GetUIIsolateLastError()) { |
506 | case tonic::kCompilationErrorType: |
507 | return DartErrorCode::CompilationError; |
508 | case tonic::kApiErrorType: |
509 | return DartErrorCode::ApiError; |
510 | case tonic::kUnknownErrorType: |
511 | return DartErrorCode::UnknownError; |
512 | case tonic::kNoError: |
513 | return DartErrorCode::NoError; |
514 | } |
515 | return DartErrorCode::UnknownError; |
516 | } |
517 | |
518 | bool Shell::EngineHasLivePorts() const { |
519 | FML_DCHECK(is_setup_); |
520 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
521 | |
522 | if (!weak_engine_) { |
523 | return false; |
524 | } |
525 | |
526 | return weak_engine_->UIIsolateHasLivePorts(); |
527 | } |
528 | |
529 | bool Shell::IsSetup() const { |
530 | return is_setup_; |
531 | } |
532 | |
533 | bool Shell::Setup(std::unique_ptr<PlatformView> platform_view, |
534 | std::unique_ptr<Engine> engine, |
535 | std::unique_ptr<Rasterizer> rasterizer, |
536 | std::unique_ptr<ShellIOManager> io_manager) { |
537 | if (is_setup_) { |
538 | return false; |
539 | } |
540 | |
541 | if (!platform_view || !engine || !rasterizer || !io_manager) { |
542 | return false; |
543 | } |
544 | |
545 | platform_view_ = std::move(platform_view); |
546 | engine_ = std::move(engine); |
547 | rasterizer_ = std::move(rasterizer); |
548 | io_manager_ = std::move(io_manager); |
549 | |
550 | // The weak ptr must be generated in the platform thread which owns the unique |
551 | // ptr. |
552 | weak_engine_ = engine_->GetWeakPtr(); |
553 | weak_rasterizer_ = rasterizer_->GetWeakPtr(); |
554 | weak_platform_view_ = platform_view_->GetWeakPtr(); |
555 | |
556 | // Setup the time-consuming default font manager right after engine created. |
557 | fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), |
558 | [engine = weak_engine_] { |
559 | if (engine) { |
560 | engine->SetupDefaultFontManager(); |
561 | } |
562 | }); |
563 | |
564 | is_setup_ = true; |
565 | |
566 | vm_->GetServiceProtocol()->AddHandler(this, GetServiceProtocolDescription()); |
567 | |
568 | PersistentCache::GetCacheForProcess()->AddWorkerTaskRunner( |
569 | task_runners_.GetIOTaskRunner()); |
570 | |
571 | PersistentCache::GetCacheForProcess()->SetIsDumpingSkp( |
572 | settings_.dump_skp_on_shader_compilation); |
573 | |
574 | if (settings_.purge_persistent_cache) { |
575 | PersistentCache::GetCacheForProcess()->Purge(); |
576 | } |
577 | |
578 | // TODO(gw280): The WeakPtr here asserts that we are derefing it on the |
579 | // same thread as it was created on. Shell is constructed on the platform |
580 | // thread but we need to call into the Engine on the UI thread, so we need |
581 | // to use getUnsafe() here to avoid failing the assertion. |
582 | // |
583 | // https://github.com/flutter/flutter/issues/42947 |
584 | display_refresh_rate_ = weak_engine_.getUnsafe()->GetDisplayRefreshRate(); |
585 | |
586 | return true; |
587 | } |
588 | |
589 | const Settings& Shell::GetSettings() const { |
590 | return settings_; |
591 | } |
592 | |
593 | const TaskRunners& Shell::GetTaskRunners() const { |
594 | return task_runners_; |
595 | } |
596 | |
597 | fml::TaskRunnerAffineWeakPtr<Rasterizer> Shell::GetRasterizer() const { |
598 | FML_DCHECK(is_setup_); |
599 | return weak_rasterizer_; |
600 | } |
601 | |
602 | fml::WeakPtr<Engine> Shell::GetEngine() { |
603 | FML_DCHECK(is_setup_); |
604 | return weak_engine_; |
605 | } |
606 | |
607 | fml::WeakPtr<PlatformView> Shell::GetPlatformView() { |
608 | FML_DCHECK(is_setup_); |
609 | return weak_platform_view_; |
610 | } |
611 | |
612 | DartVM* Shell::GetDartVM() { |
613 | return &vm_; |
614 | } |
615 | |
616 | // |PlatformView::Delegate| |
617 | void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) { |
618 | TRACE_EVENT0("flutter" , "Shell::OnPlatformViewCreated" ); |
619 | FML_DCHECK(is_setup_); |
620 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
621 | |
622 | // Note: |
623 | // This is a synchronous operation because certain platforms depend on |
624 | // setup/suspension of all activities that may be interacting with the GPU in |
625 | // a synchronous fashion. |
626 | fml::AutoResetWaitableEvent latch; |
627 | auto raster_task = |
628 | fml::MakeCopyable([&waiting_for_first_frame = waiting_for_first_frame_, |
629 | rasterizer = rasterizer_->GetWeakPtr(), // |
630 | surface = std::move(surface), // |
631 | &latch]() mutable { |
632 | if (rasterizer) { |
633 | rasterizer->Setup(std::move(surface)); |
634 | } |
635 | |
636 | waiting_for_first_frame.store(true); |
637 | |
638 | // Step 3: All done. Signal the latch that the platform thread is |
639 | // waiting on. |
640 | latch.Signal(); |
641 | }); |
642 | |
643 | // The normal flow executed by this method is that the platform thread is |
644 | // starting the sequence and waiting on the latch. Later the UI thread posts |
645 | // raster_task to the raster thread which signals the latch. If the raster and |
646 | // the platform threads are the same this results in a deadlock as the |
647 | // raster_task will never be posted to the plaform/raster thread that is |
648 | // blocked on a latch. To avoid the described deadlock, if the raster and the |
649 | // platform threads are the same, should_post_raster_task will be false, and |
650 | // then instead of posting a task to the raster thread, the ui thread just |
651 | // signals the latch and the platform/raster thread follows with executing |
652 | // raster_task. |
653 | bool should_post_raster_task = task_runners_.GetRasterTaskRunner() != |
654 | task_runners_.GetPlatformTaskRunner(); |
655 | |
656 | auto ui_task = [engine = engine_->GetWeakPtr(), // |
657 | raster_task_runner = task_runners_.GetRasterTaskRunner(), // |
658 | raster_task, should_post_raster_task, |
659 | &latch // |
660 | ] { |
661 | if (engine) { |
662 | engine->OnOutputSurfaceCreated(); |
663 | } |
664 | // Step 2: Next, tell the raster thread that it should create a surface for |
665 | // its rasterizer. |
666 | if (should_post_raster_task) { |
667 | fml::TaskRunner::RunNowOrPostTask(raster_task_runner, raster_task); |
668 | } else { |
669 | // See comment on should_post_raster_task, in this case we just unblock |
670 | // the platform thread. |
671 | latch.Signal(); |
672 | } |
673 | }; |
674 | |
675 | // Threading: Capture platform view by raw pointer and not the weak pointer. |
676 | // We are going to use the pointer on the IO thread which is not safe with a |
677 | // weak pointer. However, we are preventing the platform view from being |
678 | // collected by using a latch. |
679 | auto* platform_view = platform_view_.get(); |
680 | |
681 | FML_DCHECK(platform_view); |
682 | |
683 | auto io_task = [io_manager = io_manager_->GetWeakPtr(), platform_view, |
684 | ui_task_runner = task_runners_.GetUITaskRunner(), ui_task] { |
685 | if (io_manager && !io_manager->GetResourceContext()) { |
686 | io_manager->NotifyResourceContextAvailable( |
687 | platform_view->CreateResourceContext()); |
688 | } |
689 | // Step 1: Next, post a task on the UI thread to tell the engine that it has |
690 | // an output surface. |
691 | fml::TaskRunner::RunNowOrPostTask(ui_task_runner, ui_task); |
692 | }; |
693 | |
694 | fml::TaskRunner::RunNowOrPostTask(task_runners_.GetIOTaskRunner(), io_task); |
695 | |
696 | latch.Wait(); |
697 | if (!should_post_raster_task) { |
698 | // See comment on should_post_raster_task, in this case the raster_task |
699 | // wasn't executed, and we just run it here as the platform thread |
700 | // is the raster thread. |
701 | raster_task(); |
702 | } |
703 | } |
704 | |
705 | // |PlatformView::Delegate| |
706 | void Shell::OnPlatformViewDestroyed() { |
707 | TRACE_EVENT0("flutter" , "Shell::OnPlatformViewDestroyed" ); |
708 | FML_DCHECK(is_setup_); |
709 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
710 | |
711 | // Note: |
712 | // This is a synchronous operation because certain platforms depend on |
713 | // setup/suspension of all activities that may be interacting with the GPU in |
714 | // a synchronous fashion. |
715 | |
716 | fml::AutoResetWaitableEvent latch; |
717 | |
718 | auto io_task = [io_manager = io_manager_.get(), &latch]() { |
719 | // Execute any pending Skia object deletions while GPU access is still |
720 | // allowed. |
721 | io_manager->GetIsGpuDisabledSyncSwitch()->Execute( |
722 | fml::SyncSwitch::Handlers().SetIfFalse( |
723 | [&] { io_manager->GetSkiaUnrefQueue()->Drain(); })); |
724 | // Step 3: All done. Signal the latch that the platform thread is waiting |
725 | // on. |
726 | latch.Signal(); |
727 | }; |
728 | |
729 | auto raster_task = [rasterizer = rasterizer_->GetWeakPtr(), |
730 | io_task_runner = task_runners_.GetIOTaskRunner(), |
731 | io_task]() { |
732 | if (rasterizer) { |
733 | rasterizer->Teardown(); |
734 | } |
735 | // Step 2: Next, tell the IO thread to complete its remaining work. |
736 | fml::TaskRunner::RunNowOrPostTask(io_task_runner, io_task); |
737 | }; |
738 | |
739 | // The normal flow executed by this method is that the platform thread is |
740 | // starting the sequence and waiting on the latch. Later the UI thread posts |
741 | // raster_task to the raster thread triggers signaling the latch(on the IO |
742 | // thread). If the raster and the platform threads are the same this results |
743 | // in a deadlock as the raster_task will never be posted to the plaform/raster |
744 | // thread that is blocked on a latch. To avoid the described deadlock, if the |
745 | // raster and the platform threads are the same, should_post_raster_task will |
746 | // be false, and then instead of posting a task to the raster thread, the ui |
747 | // thread just signals the latch and the platform/raster thread follows with |
748 | // executing raster_task. |
749 | bool should_post_raster_task = task_runners_.GetRasterTaskRunner() != |
750 | task_runners_.GetPlatformTaskRunner(); |
751 | |
752 | auto ui_task = [engine = engine_->GetWeakPtr(), |
753 | raster_task_runner = task_runners_.GetRasterTaskRunner(), |
754 | raster_task, should_post_raster_task, &latch]() { |
755 | if (engine) { |
756 | engine->OnOutputSurfaceDestroyed(); |
757 | } |
758 | // Step 1: Next, tell the raster thread that its rasterizer should suspend |
759 | // access to the underlying surface. |
760 | if (should_post_raster_task) { |
761 | fml::TaskRunner::RunNowOrPostTask(raster_task_runner, raster_task); |
762 | } else { |
763 | // See comment on should_post_raster_task, in this case we just unblock |
764 | // the platform thread. |
765 | latch.Signal(); |
766 | } |
767 | }; |
768 | |
769 | // Step 0: Post a task onto the UI thread to tell the engine that its output |
770 | // surface is about to go away. |
771 | fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), ui_task); |
772 | latch.Wait(); |
773 | if (!should_post_raster_task) { |
774 | // See comment on should_post_raster_task, in this case the raster_task |
775 | // wasn't executed, and we just run it here as the platform thread |
776 | // is the raster thread. |
777 | raster_task(); |
778 | latch.Wait(); |
779 | } |
780 | } |
781 | |
782 | // |PlatformView::Delegate| |
783 | void Shell::OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) { |
784 | FML_DCHECK(is_setup_); |
785 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
786 | |
787 | // This is the formula Android uses. |
788 | // https://android.googlesource.com/platform/frameworks/base/+/master/libs/hwui/renderthread/CacheManager.cpp#41 |
789 | size_t max_bytes = metrics.physical_width * metrics.physical_height * 12 * 4; |
790 | task_runners_.GetRasterTaskRunner()->PostTask( |
791 | [rasterizer = rasterizer_->GetWeakPtr(), max_bytes] { |
792 | if (rasterizer) { |
793 | rasterizer->SetResourceCacheMaxBytes(max_bytes, false); |
794 | } |
795 | }); |
796 | |
797 | task_runners_.GetUITaskRunner()->PostTask( |
798 | [engine = engine_->GetWeakPtr(), metrics]() { |
799 | if (engine) { |
800 | engine->SetViewportMetrics(metrics); |
801 | } |
802 | }); |
803 | } |
804 | |
805 | // |PlatformView::Delegate| |
806 | void Shell::OnPlatformViewDispatchPlatformMessage( |
807 | fml::RefPtr<PlatformMessage> message) { |
808 | FML_DCHECK(is_setup_); |
809 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
810 | |
811 | task_runners_.GetUITaskRunner()->PostTask( |
812 | [engine = engine_->GetWeakPtr(), message = std::move(message)] { |
813 | if (engine) { |
814 | engine->DispatchPlatformMessage(std::move(message)); |
815 | } |
816 | }); |
817 | } |
818 | |
819 | // |PlatformView::Delegate| |
820 | void Shell::OnPlatformViewDispatchPointerDataPacket( |
821 | std::unique_ptr<PointerDataPacket> packet) { |
822 | TRACE_EVENT0("flutter" , "Shell::OnPlatformViewDispatchPointerDataPacket" ); |
823 | TRACE_FLOW_BEGIN("flutter" , "PointerEvent" , next_pointer_flow_id_); |
824 | FML_DCHECK(is_setup_); |
825 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
826 | task_runners_.GetUITaskRunner()->PostTask( |
827 | fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet), |
828 | flow_id = next_pointer_flow_id_]() mutable { |
829 | if (engine) { |
830 | engine->DispatchPointerDataPacket(std::move(packet), flow_id); |
831 | } |
832 | })); |
833 | next_pointer_flow_id_++; |
834 | } |
835 | |
836 | // |PlatformView::Delegate| |
837 | void Shell::OnPlatformViewDispatchSemanticsAction(int32_t id, |
838 | SemanticsAction action, |
839 | std::vector<uint8_t> args) { |
840 | FML_DCHECK(is_setup_); |
841 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
842 | |
843 | task_runners_.GetUITaskRunner()->PostTask( |
844 | [engine = engine_->GetWeakPtr(), id, action, args = std::move(args)] { |
845 | if (engine) { |
846 | engine->DispatchSemanticsAction(id, action, std::move(args)); |
847 | } |
848 | }); |
849 | } |
850 | |
851 | // |PlatformView::Delegate| |
852 | void Shell::OnPlatformViewSetSemanticsEnabled(bool enabled) { |
853 | FML_DCHECK(is_setup_); |
854 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
855 | |
856 | task_runners_.GetUITaskRunner()->PostTask( |
857 | [engine = engine_->GetWeakPtr(), enabled] { |
858 | if (engine) { |
859 | engine->SetSemanticsEnabled(enabled); |
860 | } |
861 | }); |
862 | } |
863 | |
864 | // |PlatformView::Delegate| |
865 | void Shell::OnPlatformViewSetAccessibilityFeatures(int32_t flags) { |
866 | FML_DCHECK(is_setup_); |
867 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
868 | |
869 | task_runners_.GetUITaskRunner()->PostTask( |
870 | [engine = engine_->GetWeakPtr(), flags] { |
871 | if (engine) { |
872 | engine->SetAccessibilityFeatures(flags); |
873 | } |
874 | }); |
875 | } |
876 | |
877 | // |PlatformView::Delegate| |
878 | void Shell::OnPlatformViewRegisterTexture( |
879 | std::shared_ptr<flutter::Texture> texture) { |
880 | FML_DCHECK(is_setup_); |
881 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
882 | |
883 | task_runners_.GetRasterTaskRunner()->PostTask( |
884 | [rasterizer = rasterizer_->GetWeakPtr(), texture] { |
885 | if (rasterizer) { |
886 | if (auto* registry = rasterizer->GetTextureRegistry()) { |
887 | registry->RegisterTexture(texture); |
888 | } |
889 | } |
890 | }); |
891 | } |
892 | |
893 | // |PlatformView::Delegate| |
894 | void Shell::OnPlatformViewUnregisterTexture(int64_t texture_id) { |
895 | FML_DCHECK(is_setup_); |
896 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
897 | |
898 | task_runners_.GetRasterTaskRunner()->PostTask( |
899 | [rasterizer = rasterizer_->GetWeakPtr(), texture_id]() { |
900 | if (rasterizer) { |
901 | if (auto* registry = rasterizer->GetTextureRegistry()) { |
902 | registry->UnregisterTexture(texture_id); |
903 | } |
904 | } |
905 | }); |
906 | } |
907 | |
908 | // |PlatformView::Delegate| |
909 | void Shell::OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) { |
910 | FML_DCHECK(is_setup_); |
911 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
912 | |
913 | // Tell the rasterizer that one of its textures has a new frame available. |
914 | task_runners_.GetRasterTaskRunner()->PostTask( |
915 | [rasterizer = rasterizer_->GetWeakPtr(), texture_id]() { |
916 | auto* registry = rasterizer->GetTextureRegistry(); |
917 | |
918 | if (!registry) { |
919 | return; |
920 | } |
921 | |
922 | auto texture = registry->GetTexture(texture_id); |
923 | |
924 | if (!texture) { |
925 | return; |
926 | } |
927 | |
928 | texture->MarkNewFrameAvailable(); |
929 | }); |
930 | |
931 | // Schedule a new frame without having to rebuild the layer tree. |
932 | task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() { |
933 | if (engine) { |
934 | engine->ScheduleFrame(false); |
935 | } |
936 | }); |
937 | } |
938 | |
939 | // |PlatformView::Delegate| |
940 | void Shell::OnPlatformViewSetNextFrameCallback(const fml::closure& closure) { |
941 | FML_DCHECK(is_setup_); |
942 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
943 | |
944 | task_runners_.GetRasterTaskRunner()->PostTask( |
945 | [rasterizer = rasterizer_->GetWeakPtr(), closure = closure]() { |
946 | if (rasterizer) { |
947 | rasterizer->SetNextFrameCallback(std::move(closure)); |
948 | } |
949 | }); |
950 | } |
951 | |
952 | // |Animator::Delegate| |
953 | void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_target_time) { |
954 | FML_DCHECK(is_setup_); |
955 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
956 | |
957 | // record the target time for use by rasterizer. |
958 | { |
959 | std::scoped_lock time_recorder_lock(time_recorder_mutex_); |
960 | latest_frame_target_time_.emplace(frame_target_time); |
961 | } |
962 | if (engine_) { |
963 | engine_->BeginFrame(frame_target_time); |
964 | } |
965 | } |
966 | |
967 | // |Animator::Delegate| |
968 | void Shell::OnAnimatorNotifyIdle(int64_t deadline) { |
969 | FML_DCHECK(is_setup_); |
970 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
971 | |
972 | if (engine_) { |
973 | engine_->NotifyIdle(deadline); |
974 | } |
975 | } |
976 | |
977 | // |Animator::Delegate| |
978 | void Shell::OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline, |
979 | fml::TimePoint frame_target_time) { |
980 | FML_DCHECK(is_setup_); |
981 | |
982 | // record the target time for use by rasterizer. |
983 | { |
984 | std::scoped_lock time_recorder_lock(time_recorder_mutex_); |
985 | if (!latest_frame_target_time_) { |
986 | latest_frame_target_time_ = frame_target_time; |
987 | } else if (latest_frame_target_time_ < frame_target_time) { |
988 | latest_frame_target_time_ = frame_target_time; |
989 | } |
990 | } |
991 | |
992 | task_runners_.GetRasterTaskRunner()->PostTask( |
993 | [&waiting_for_first_frame = waiting_for_first_frame_, |
994 | &waiting_for_first_frame_condition = waiting_for_first_frame_condition_, |
995 | rasterizer = rasterizer_->GetWeakPtr(), |
996 | pipeline = std::move(pipeline)]() { |
997 | if (rasterizer) { |
998 | rasterizer->Draw(pipeline); |
999 | |
1000 | if (waiting_for_first_frame.load()) { |
1001 | waiting_for_first_frame.store(false); |
1002 | waiting_for_first_frame_condition.notify_all(); |
1003 | } |
1004 | } |
1005 | }); |
1006 | } |
1007 | |
1008 | // |Animator::Delegate| |
1009 | void Shell::OnAnimatorDrawLastLayerTree() { |
1010 | FML_DCHECK(is_setup_); |
1011 | |
1012 | task_runners_.GetRasterTaskRunner()->PostTask( |
1013 | [rasterizer = rasterizer_->GetWeakPtr()]() { |
1014 | if (rasterizer) { |
1015 | rasterizer->DrawLastLayerTree(); |
1016 | } |
1017 | }); |
1018 | } |
1019 | |
1020 | // |Engine::Delegate| |
1021 | void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update, |
1022 | CustomAccessibilityActionUpdates actions) { |
1023 | FML_DCHECK(is_setup_); |
1024 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1025 | |
1026 | task_runners_.GetPlatformTaskRunner()->PostTask( |
1027 | [view = platform_view_->GetWeakPtr(), update = std::move(update), |
1028 | actions = std::move(actions)] { |
1029 | if (view) { |
1030 | view->UpdateSemantics(std::move(update), std::move(actions)); |
1031 | } |
1032 | }); |
1033 | } |
1034 | |
1035 | // |Engine::Delegate| |
1036 | void Shell::OnEngineHandlePlatformMessage( |
1037 | fml::RefPtr<PlatformMessage> message) { |
1038 | FML_DCHECK(is_setup_); |
1039 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1040 | |
1041 | if (message->channel() == kSkiaChannel) { |
1042 | HandleEngineSkiaMessage(std::move(message)); |
1043 | return; |
1044 | } |
1045 | |
1046 | task_runners_.GetPlatformTaskRunner()->PostTask( |
1047 | [view = platform_view_->GetWeakPtr(), message = std::move(message)]() { |
1048 | if (view) { |
1049 | view->HandlePlatformMessage(std::move(message)); |
1050 | } |
1051 | }); |
1052 | } |
1053 | |
1054 | void Shell::HandleEngineSkiaMessage(fml::RefPtr<PlatformMessage> message) { |
1055 | const auto& data = message->data(); |
1056 | |
1057 | rapidjson::Document document; |
1058 | document.Parse(reinterpret_cast<const char*>(data.data()), data.size()); |
1059 | if (document.HasParseError() || !document.IsObject()) |
1060 | return; |
1061 | auto root = document.GetObject(); |
1062 | auto method = root.FindMember("method" ); |
1063 | if (method->value != "Skia.setResourceCacheMaxBytes" ) |
1064 | return; |
1065 | auto args = root.FindMember("args" ); |
1066 | if (args == root.MemberEnd() || !args->value.IsInt()) |
1067 | return; |
1068 | |
1069 | task_runners_.GetRasterTaskRunner()->PostTask( |
1070 | [rasterizer = rasterizer_->GetWeakPtr(), max_bytes = args->value.GetInt(), |
1071 | response = std::move(message->response())] { |
1072 | if (rasterizer) { |
1073 | rasterizer->SetResourceCacheMaxBytes(static_cast<size_t>(max_bytes), |
1074 | true); |
1075 | } |
1076 | if (response) { |
1077 | // The framework side expects this to be valid json encoded as a list. |
1078 | // Return `[true]` to signal success. |
1079 | std::vector<uint8_t> data = {'[', 't', 'r', 'u', 'e', ']'}; |
1080 | response->Complete( |
1081 | std::make_unique<fml::DataMapping>(std::move(data))); |
1082 | } |
1083 | }); |
1084 | } |
1085 | |
1086 | // |Engine::Delegate| |
1087 | void Shell::OnPreEngineRestart() { |
1088 | FML_DCHECK(is_setup_); |
1089 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1090 | |
1091 | fml::AutoResetWaitableEvent latch; |
1092 | fml::TaskRunner::RunNowOrPostTask( |
1093 | task_runners_.GetPlatformTaskRunner(), |
1094 | [view = platform_view_->GetWeakPtr(), &latch]() { |
1095 | if (view) { |
1096 | view->OnPreEngineRestart(); |
1097 | } |
1098 | latch.Signal(); |
1099 | }); |
1100 | // This is blocking as any embedded platform views has to be flushed before |
1101 | // we re-run the Dart code. |
1102 | latch.Wait(); |
1103 | } |
1104 | |
1105 | // |Engine::Delegate| |
1106 | void Shell::UpdateIsolateDescription(const std::string isolate_name, |
1107 | int64_t isolate_port) { |
1108 | Handler::Description description(isolate_port, isolate_name); |
1109 | vm_->GetServiceProtocol()->SetHandlerDescription(this, description); |
1110 | } |
1111 | |
1112 | void Shell::SetNeedsReportTimings(bool value) { |
1113 | needs_report_timings_ = value; |
1114 | } |
1115 | |
1116 | // |Engine::Delegate| |
1117 | std::unique_ptr<std::vector<std::string>> Shell::ComputePlatformResolvedLocale( |
1118 | const std::vector<std::string>& supported_locale_data) { |
1119 | return ComputePlatformViewResolvedLocale(supported_locale_data); |
1120 | } |
1121 | |
1122 | // |PlatformView::Delegate| |
1123 | std::unique_ptr<std::vector<std::string>> |
1124 | Shell::ComputePlatformViewResolvedLocale( |
1125 | const std::vector<std::string>& supported_locale_data) { |
1126 | return platform_view_->ComputePlatformResolvedLocales(supported_locale_data); |
1127 | } |
1128 | |
1129 | void Shell::ReportTimings() { |
1130 | FML_DCHECK(is_setup_); |
1131 | FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); |
1132 | |
1133 | auto timings = std::move(unreported_timings_); |
1134 | unreported_timings_ = {}; |
1135 | task_runners_.GetUITaskRunner()->PostTask([timings, engine = weak_engine_] { |
1136 | if (engine) { |
1137 | engine->ReportTimings(std::move(timings)); |
1138 | } |
1139 | }); |
1140 | } |
1141 | |
1142 | size_t Shell::UnreportedFramesCount() const { |
1143 | // Check that this is running on the raster thread to avoid race conditions. |
1144 | FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); |
1145 | FML_DCHECK(unreported_timings_.size() % FrameTiming::kCount == 0); |
1146 | return unreported_timings_.size() / FrameTiming::kCount; |
1147 | } |
1148 | |
1149 | void Shell::OnFrameRasterized(const FrameTiming& timing) { |
1150 | FML_DCHECK(is_setup_); |
1151 | FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); |
1152 | |
1153 | // The C++ callback defined in settings.h and set by Flutter runner. This is |
1154 | // independent of the timings report to the Dart side. |
1155 | if (settings_.frame_rasterized_callback) { |
1156 | settings_.frame_rasterized_callback(timing); |
1157 | } |
1158 | |
1159 | if (!needs_report_timings_) { |
1160 | return; |
1161 | } |
1162 | |
1163 | for (auto phase : FrameTiming::kPhases) { |
1164 | unreported_timings_.push_back( |
1165 | timing.Get(phase).ToEpochDelta().ToMicroseconds()); |
1166 | } |
1167 | |
1168 | // In tests using iPhone 6S with profile mode, sending a batch of 1 frame or a |
1169 | // batch of 100 frames have roughly the same cost of less than 0.1ms. Sending |
1170 | // a batch of 500 frames costs about 0.2ms. The 1 second threshold usually |
1171 | // kicks in before we reaching the following 100 frames threshold. The 100 |
1172 | // threshold here is mainly for unit tests (so we don't have to write a |
1173 | // 1-second unit test), and make sure that our vector won't grow too big with |
1174 | // future 120fps, 240fps, or 1000fps displays. |
1175 | // |
1176 | // In the profile/debug mode, the timings are used by development tools which |
1177 | // require a latency of no more than 100ms. Hence we lower that 1-second |
1178 | // threshold to 100ms because performance overhead isn't that critical in |
1179 | // those cases. |
1180 | if (!first_frame_rasterized_ || UnreportedFramesCount() >= 100) { |
1181 | first_frame_rasterized_ = true; |
1182 | ReportTimings(); |
1183 | } else if (!frame_timings_report_scheduled_) { |
1184 | #if FLUTTER_RELEASE |
1185 | constexpr int kBatchTimeInMilliseconds = 1000; |
1186 | #else |
1187 | constexpr int kBatchTimeInMilliseconds = 100; |
1188 | #endif |
1189 | |
1190 | // Also make sure that frame times get reported with a max latency of 1 |
1191 | // second. Otherwise, the timings of last few frames of an animation may |
1192 | // never be reported until the next animation starts. |
1193 | frame_timings_report_scheduled_ = true; |
1194 | task_runners_.GetRasterTaskRunner()->PostDelayedTask( |
1195 | [self = weak_factory_gpu_->GetWeakPtr()]() { |
1196 | if (!self.get()) { |
1197 | return; |
1198 | } |
1199 | self->frame_timings_report_scheduled_ = false; |
1200 | if (self->UnreportedFramesCount() > 0) { |
1201 | self->ReportTimings(); |
1202 | } |
1203 | }, |
1204 | fml::TimeDelta::FromMilliseconds(kBatchTimeInMilliseconds)); |
1205 | } |
1206 | } |
1207 | |
1208 | fml::Milliseconds Shell::GetFrameBudget() { |
1209 | if (display_refresh_rate_ > 0) { |
1210 | return fml::RefreshRateToFrameBudget(display_refresh_rate_.load()); |
1211 | } else { |
1212 | return fml::kDefaultFrameBudget; |
1213 | } |
1214 | } |
1215 | |
1216 | fml::TimePoint Shell::GetLatestFrameTargetTime() const { |
1217 | std::scoped_lock time_recorder_lock(time_recorder_mutex_); |
1218 | FML_CHECK(latest_frame_target_time_.has_value()) |
1219 | << "GetLatestFrameTargetTime called before OnAnimatorBeginFrame" ; |
1220 | return latest_frame_target_time_.value(); |
1221 | } |
1222 | |
1223 | // |ServiceProtocol::Handler| |
1224 | fml::RefPtr<fml::TaskRunner> Shell::GetServiceProtocolHandlerTaskRunner( |
1225 | std::string_view method) const { |
1226 | FML_DCHECK(is_setup_); |
1227 | auto found = service_protocol_handlers_.find(method); |
1228 | if (found != service_protocol_handlers_.end()) { |
1229 | return found->second.first; |
1230 | } |
1231 | return task_runners_.GetUITaskRunner(); |
1232 | } |
1233 | |
1234 | // |ServiceProtocol::Handler| |
1235 | bool Shell::HandleServiceProtocolMessage( |
1236 | std::string_view method, // one if the extension names specified above. |
1237 | const ServiceProtocolMap& params, |
1238 | rapidjson::Document* response) { |
1239 | auto found = service_protocol_handlers_.find(method); |
1240 | if (found != service_protocol_handlers_.end()) { |
1241 | return found->second.second(params, response); |
1242 | } |
1243 | return false; |
1244 | } |
1245 | |
1246 | // |ServiceProtocol::Handler| |
1247 | ServiceProtocol::Handler::Description Shell::GetServiceProtocolDescription() |
1248 | const { |
1249 | return { |
1250 | engine_->GetUIIsolateMainPort(), |
1251 | engine_->GetUIIsolateName(), |
1252 | }; |
1253 | } |
1254 | |
1255 | static void ServiceProtocolParameterError(rapidjson::Document* response, |
1256 | std::string error_details) { |
1257 | auto& allocator = response->GetAllocator(); |
1258 | response->SetObject(); |
1259 | const int64_t kInvalidParams = -32602; |
1260 | response->AddMember("code" , kInvalidParams, allocator); |
1261 | response->AddMember("message" , "Invalid params" , allocator); |
1262 | { |
1263 | rapidjson::Value details(rapidjson::kObjectType); |
1264 | details.AddMember("details" , error_details, allocator); |
1265 | response->AddMember("data" , details, allocator); |
1266 | } |
1267 | } |
1268 | |
1269 | static void ServiceProtocolFailureError(rapidjson::Document* response, |
1270 | std::string message) { |
1271 | auto& allocator = response->GetAllocator(); |
1272 | response->SetObject(); |
1273 | const int64_t kJsonServerError = -32000; |
1274 | response->AddMember("code" , kJsonServerError, allocator); |
1275 | response->AddMember("message" , message, allocator); |
1276 | } |
1277 | |
1278 | // Service protocol handler |
1279 | bool Shell::OnServiceProtocolScreenshot( |
1280 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1281 | rapidjson::Document* response) { |
1282 | FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); |
1283 | auto screenshot = rasterizer_->ScreenshotLastLayerTree( |
1284 | Rasterizer::ScreenshotType::CompressedImage, true); |
1285 | if (screenshot.data) { |
1286 | response->SetObject(); |
1287 | auto& allocator = response->GetAllocator(); |
1288 | response->AddMember("type" , "Screenshot" , allocator); |
1289 | rapidjson::Value image; |
1290 | image.SetString(static_cast<const char*>(screenshot.data->data()), |
1291 | screenshot.data->size(), allocator); |
1292 | response->AddMember("screenshot" , image, allocator); |
1293 | return true; |
1294 | } |
1295 | ServiceProtocolFailureError(response, "Could not capture image screenshot." ); |
1296 | return false; |
1297 | } |
1298 | |
1299 | // Service protocol handler |
1300 | bool Shell::OnServiceProtocolScreenshotSKP( |
1301 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1302 | rapidjson::Document* response) { |
1303 | FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); |
1304 | auto screenshot = rasterizer_->ScreenshotLastLayerTree( |
1305 | Rasterizer::ScreenshotType::SkiaPicture, true); |
1306 | if (screenshot.data) { |
1307 | response->SetObject(); |
1308 | auto& allocator = response->GetAllocator(); |
1309 | response->AddMember("type" , "ScreenshotSkp" , allocator); |
1310 | rapidjson::Value skp; |
1311 | skp.SetString(static_cast<const char*>(screenshot.data->data()), |
1312 | screenshot.data->size(), allocator); |
1313 | response->AddMember("skp" , skp, allocator); |
1314 | return true; |
1315 | } |
1316 | ServiceProtocolFailureError(response, "Could not capture SKP screenshot." ); |
1317 | return false; |
1318 | } |
1319 | |
1320 | // Service protocol handler |
1321 | bool Shell::OnServiceProtocolRunInView( |
1322 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1323 | rapidjson::Document* response) { |
1324 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1325 | |
1326 | if (params.count("mainScript" ) == 0) { |
1327 | ServiceProtocolParameterError(response, |
1328 | "'mainScript' parameter is missing." ); |
1329 | return false; |
1330 | } |
1331 | |
1332 | if (params.count("assetDirectory" ) == 0) { |
1333 | ServiceProtocolParameterError(response, |
1334 | "'assetDirectory' parameter is missing." ); |
1335 | return false; |
1336 | } |
1337 | |
1338 | std::string main_script_path = |
1339 | fml::paths::FromURI(params.at("mainScript" ).data()); |
1340 | std::string asset_directory_path = |
1341 | fml::paths::FromURI(params.at("assetDirectory" ).data()); |
1342 | |
1343 | auto main_script_file_mapping = |
1344 | std::make_unique<fml::FileMapping>(fml::OpenFile( |
1345 | main_script_path.c_str(), false, fml::FilePermission::kRead)); |
1346 | |
1347 | auto isolate_configuration = IsolateConfiguration::CreateForKernel( |
1348 | std::move(main_script_file_mapping)); |
1349 | |
1350 | RunConfiguration configuration(std::move(isolate_configuration)); |
1351 | |
1352 | configuration.SetEntrypointAndLibrary(engine_->GetLastEntrypoint(), |
1353 | engine_->GetLastEntrypointLibrary()); |
1354 | |
1355 | configuration.AddAssetResolver( |
1356 | std::make_unique<DirectoryAssetBundle>(fml::OpenDirectory( |
1357 | asset_directory_path.c_str(), false, fml::FilePermission::kRead))); |
1358 | |
1359 | auto& allocator = response->GetAllocator(); |
1360 | response->SetObject(); |
1361 | if (engine_->Restart(std::move(configuration))) { |
1362 | response->AddMember("type" , "Success" , allocator); |
1363 | auto new_description = GetServiceProtocolDescription(); |
1364 | rapidjson::Value view(rapidjson::kObjectType); |
1365 | new_description.Write(this, view, allocator); |
1366 | response->AddMember("view" , view, allocator); |
1367 | return true; |
1368 | } else { |
1369 | FML_DLOG(ERROR) << "Could not run configuration in engine." ; |
1370 | ServiceProtocolFailureError(response, |
1371 | "Could not run configuration in engine." ); |
1372 | return false; |
1373 | } |
1374 | |
1375 | FML_DCHECK(false); |
1376 | return false; |
1377 | } |
1378 | |
1379 | // Service protocol handler |
1380 | bool Shell::OnServiceProtocolFlushUIThreadTasks( |
1381 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1382 | rapidjson::Document* response) { |
1383 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1384 | // This API should not be invoked by production code. |
1385 | // It can potentially starve the service isolate if the main isolate pauses |
1386 | // at a breakpoint or is in an infinite loop. |
1387 | // |
1388 | // It should be invoked from the VM Service and and blocks it until UI thread |
1389 | // tasks are processed. |
1390 | response->SetObject(); |
1391 | response->AddMember("type" , "Success" , response->GetAllocator()); |
1392 | return true; |
1393 | } |
1394 | |
1395 | bool Shell::OnServiceProtocolGetDisplayRefreshRate( |
1396 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1397 | rapidjson::Document* response) { |
1398 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1399 | response->SetObject(); |
1400 | response->AddMember("type" , "DisplayRefreshRate" , response->GetAllocator()); |
1401 | response->AddMember("fps" , engine_->GetDisplayRefreshRate(), |
1402 | response->GetAllocator()); |
1403 | return true; |
1404 | } |
1405 | |
1406 | bool Shell::OnServiceProtocolGetSkSLs( |
1407 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1408 | rapidjson::Document* response) { |
1409 | FML_DCHECK(task_runners_.GetIOTaskRunner()->RunsTasksOnCurrentThread()); |
1410 | response->SetObject(); |
1411 | response->AddMember("type" , "GetSkSLs" , response->GetAllocator()); |
1412 | |
1413 | rapidjson::Value shaders_json(rapidjson::kObjectType); |
1414 | PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess(); |
1415 | std::vector<PersistentCache::SkSLCache> sksls = persistent_cache->LoadSkSLs(); |
1416 | for (const auto& sksl : sksls) { |
1417 | size_t b64_size = |
1418 | SkBase64::Encode(sksl.second->data(), sksl.second->size(), nullptr); |
1419 | sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1); |
1420 | char* b64_char = static_cast<char*>(b64_data->writable_data()); |
1421 | SkBase64::Encode(sksl.second->data(), sksl.second->size(), b64_char); |
1422 | b64_char[b64_size] = 0; // make it null terminated for printing |
1423 | rapidjson::Value shader_value(b64_char, response->GetAllocator()); |
1424 | rapidjson::Value shader_key(PersistentCache::SkKeyToFilePath(*sksl.first), |
1425 | response->GetAllocator()); |
1426 | shaders_json.AddMember(shader_key, shader_value, response->GetAllocator()); |
1427 | } |
1428 | response->AddMember("SkSLs" , shaders_json, response->GetAllocator()); |
1429 | return true; |
1430 | } |
1431 | |
1432 | bool Shell::OnServiceProtocolEstimateRasterCacheMemory( |
1433 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1434 | rapidjson::Document* response) { |
1435 | FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); |
1436 | const auto& raster_cache = rasterizer_->compositor_context()->raster_cache(); |
1437 | response->SetObject(); |
1438 | response->AddMember("type" , "EstimateRasterCacheMemory" , |
1439 | response->GetAllocator()); |
1440 | response->AddMember<uint64_t>("layerBytes" , |
1441 | raster_cache.EstimateLayerCacheByteSize(), |
1442 | response->GetAllocator()); |
1443 | response->AddMember<uint64_t>("pictureBytes" , |
1444 | raster_cache.EstimatePictureCacheByteSize(), |
1445 | response->GetAllocator()); |
1446 | return true; |
1447 | } |
1448 | |
1449 | // Service protocol handler |
1450 | bool Shell::OnServiceProtocolSetAssetBundlePath( |
1451 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
1452 | rapidjson::Document* response) { |
1453 | FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
1454 | |
1455 | if (params.count("assetDirectory" ) == 0) { |
1456 | ServiceProtocolParameterError(response, |
1457 | "'assetDirectory' parameter is missing." ); |
1458 | return false; |
1459 | } |
1460 | |
1461 | auto& allocator = response->GetAllocator(); |
1462 | response->SetObject(); |
1463 | |
1464 | auto asset_manager = std::make_shared<AssetManager>(); |
1465 | |
1466 | asset_manager->PushFront(std::make_unique<DirectoryAssetBundle>( |
1467 | fml::OpenDirectory(params.at("assetDirectory" ).data(), false, |
1468 | fml::FilePermission::kRead))); |
1469 | |
1470 | if (engine_->UpdateAssetManager(std::move(asset_manager))) { |
1471 | response->AddMember("type" , "Success" , allocator); |
1472 | auto new_description = GetServiceProtocolDescription(); |
1473 | rapidjson::Value view(rapidjson::kObjectType); |
1474 | new_description.Write(this, view, allocator); |
1475 | response->AddMember("view" , view, allocator); |
1476 | return true; |
1477 | } else { |
1478 | FML_DLOG(ERROR) << "Could not update asset directory." ; |
1479 | ServiceProtocolFailureError(response, "Could not update asset directory." ); |
1480 | return false; |
1481 | } |
1482 | |
1483 | FML_DCHECK(false); |
1484 | return false; |
1485 | } |
1486 | |
1487 | Rasterizer::Screenshot Shell::Screenshot( |
1488 | Rasterizer::ScreenshotType screenshot_type, |
1489 | bool base64_encode) { |
1490 | TRACE_EVENT0("flutter" , "Shell::Screenshot" ); |
1491 | fml::AutoResetWaitableEvent latch; |
1492 | Rasterizer::Screenshot screenshot; |
1493 | fml::TaskRunner::RunNowOrPostTask( |
1494 | task_runners_.GetRasterTaskRunner(), [&latch, // |
1495 | rasterizer = GetRasterizer(), // |
1496 | &screenshot, // |
1497 | screenshot_type, // |
1498 | base64_encode // |
1499 | ]() { |
1500 | if (rasterizer) { |
1501 | screenshot = rasterizer->ScreenshotLastLayerTree(screenshot_type, |
1502 | base64_encode); |
1503 | } |
1504 | latch.Signal(); |
1505 | }); |
1506 | latch.Wait(); |
1507 | return screenshot; |
1508 | } |
1509 | |
1510 | fml::Status Shell::WaitForFirstFrame(fml::TimeDelta timeout) { |
1511 | FML_DCHECK(is_setup_); |
1512 | if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread() || |
1513 | task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()) { |
1514 | return fml::Status(fml::StatusCode::kFailedPrecondition, |
1515 | "WaitForFirstFrame called from thread that can't wait " |
1516 | "because it is responsible for generating the frame." ); |
1517 | } |
1518 | |
1519 | std::unique_lock<std::mutex> lock(waiting_for_first_frame_mutex_); |
1520 | bool success = waiting_for_first_frame_condition_.wait_for( |
1521 | lock, std::chrono::milliseconds(timeout.ToMilliseconds()), |
1522 | [&waiting_for_first_frame = waiting_for_first_frame_] { |
1523 | return !waiting_for_first_frame.load(); |
1524 | }); |
1525 | if (success) { |
1526 | return fml::Status(); |
1527 | } else { |
1528 | return fml::Status(fml::StatusCode::kDeadlineExceeded, "timeout" ); |
1529 | } |
1530 | } |
1531 | |
1532 | bool Shell::ReloadSystemFonts() { |
1533 | FML_DCHECK(is_setup_); |
1534 | FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); |
1535 | |
1536 | if (!engine_) { |
1537 | return false; |
1538 | } |
1539 | engine_->GetFontCollection().GetFontCollection()->SetupDefaultFontManager(); |
1540 | engine_->GetFontCollection().GetFontCollection()->ClearFontFamilyCache(); |
1541 | // After system fonts are reloaded, we send a system channel message |
1542 | // to notify flutter framework. |
1543 | rapidjson::Document document; |
1544 | document.SetObject(); |
1545 | auto& allocator = document.GetAllocator(); |
1546 | rapidjson::Value message_value; |
1547 | message_value.SetString(kFontChange, allocator); |
1548 | document.AddMember(kTypeKey, message_value, allocator); |
1549 | |
1550 | rapidjson::StringBuffer buffer; |
1551 | rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
1552 | document.Accept(writer); |
1553 | std::string message = buffer.GetString(); |
1554 | fml::RefPtr<PlatformMessage> fontsChangeMessage = |
1555 | fml::MakeRefCounted<flutter::PlatformMessage>( |
1556 | kSystemChannel, std::vector<uint8_t>(message.begin(), message.end()), |
1557 | nullptr); |
1558 | |
1559 | OnPlatformViewDispatchPlatformMessage(fontsChangeMessage); |
1560 | return true; |
1561 | } |
1562 | |
1563 | std::shared_ptr<fml::SyncSwitch> Shell::GetIsGpuDisabledSyncSwitch() const { |
1564 | return is_gpu_disabled_sync_switch_; |
1565 | } |
1566 | |
1567 | } // namespace flutter |
1568 | |