Persist DartCallbackCache contents across launches (flutter/engine#5947)
* Updated DartCallbackCache to write callback cache to disk which is restored on engine startup * Ensure cache isn't moved off disk in iOS
This commit is contained in:
@@ -502,6 +502,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterRunArgument
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
|
||||
|
||||
@@ -2,17 +2,39 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/lib/ui/plugins/callback_cache.h"
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
#include "flutter/fml/build_config.h"
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/fml/paths.h"
|
||||
#include "flutter/lib/ui/plugins/callback_cache.h"
|
||||
#include "third_party/rapidjson/rapidjson/document.h"
|
||||
#include "third_party/rapidjson/rapidjson/stringbuffer.h"
|
||||
#include "third_party/rapidjson/rapidjson/writer.h"
|
||||
#include "third_party/tonic/converter/dart_converter.h"
|
||||
|
||||
using rapidjson::Document;
|
||||
using rapidjson::StringBuffer;
|
||||
using rapidjson::Writer;
|
||||
using tonic::ToDart;
|
||||
|
||||
namespace blink {
|
||||
|
||||
static const char* kHandleKey = "handle";
|
||||
static const char* kRepresentationKey = "representation";
|
||||
static const char* kNameKey = "name";
|
||||
static const char* kClassNameKey = "class_name";
|
||||
static const char* kLibraryPathKey = "library_path";
|
||||
static const char* kCacheName = "flutter_callback_cache.json";
|
||||
std::mutex DartCallbackCache::mutex_;
|
||||
std::string DartCallbackCache::cache_path_;
|
||||
std::map<int64_t, DartCallbackRepresentation> DartCallbackCache::cache_;
|
||||
|
||||
void DartCallbackCache::SetCachePath(const std::string& path) {
|
||||
cache_path_ = fml::paths::JoinPaths({path, kCacheName});
|
||||
}
|
||||
|
||||
Dart_Handle DartCallbackCache::GetCallback(int64_t handle) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto iterator = cache_.find(handle);
|
||||
@@ -34,6 +56,7 @@ int64_t DartCallbackCache::GetCallbackHandle(const std::string& name,
|
||||
|
||||
if (cache_.find(hash) == cache_.end()) {
|
||||
cache_[hash] = {name, class_name, library_path};
|
||||
SaveCacheToDisk();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
@@ -48,6 +71,82 @@ DartCallbackCache::GetCallbackInformation(int64_t handle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DartCallbackCache::SaveCacheToDisk() {
|
||||
// Cache JSON format
|
||||
// [
|
||||
// {
|
||||
// "hash": 42,
|
||||
// "representation": {
|
||||
// "name": "...",
|
||||
// "class_name": "...",
|
||||
// "library_path": "..."
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// ...
|
||||
// }
|
||||
// ]
|
||||
StringBuffer s;
|
||||
Writer<StringBuffer> writer(s);
|
||||
writer.StartArray();
|
||||
for (auto iterator = cache_.begin(); iterator != cache_.end(); ++iterator) {
|
||||
int64_t hash = iterator->first;
|
||||
DartCallbackRepresentation cb = iterator->second;
|
||||
writer.StartObject();
|
||||
writer.Key(kHandleKey);
|
||||
writer.Int64(hash);
|
||||
writer.Key(kRepresentationKey);
|
||||
writer.StartObject();
|
||||
writer.Key(kNameKey);
|
||||
writer.String(cb.name.c_str());
|
||||
writer.Key(kClassNameKey);
|
||||
writer.String(cb.class_name.c_str());
|
||||
writer.Key(kLibraryPathKey);
|
||||
writer.String(cb.library_path.c_str());
|
||||
writer.EndObject();
|
||||
writer.EndObject();
|
||||
}
|
||||
writer.EndArray();
|
||||
|
||||
std::ofstream output(cache_path_);
|
||||
output << s.GetString();
|
||||
output.close();
|
||||
}
|
||||
|
||||
void DartCallbackCache::LoadCacheFromDisk() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// Don't reload the cache if it's already populated.
|
||||
if (!cache_.empty()) {
|
||||
return;
|
||||
}
|
||||
std::ifstream input(cache_path_);
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
std::string cache_contents{std::istreambuf_iterator<char>(input),
|
||||
std::istreambuf_iterator<char>()};
|
||||
Document d;
|
||||
d.Parse(cache_contents.c_str());
|
||||
if (d.HasParseError() || !d.IsArray()) {
|
||||
FML_LOG(WARNING) << "Could not parse callback cache, aborting restore";
|
||||
// TODO(bkonyi): log and bail (delete cache?)
|
||||
return;
|
||||
}
|
||||
const auto entries = d.GetArray();
|
||||
for (auto it = entries.begin(); it != entries.end(); ++it) {
|
||||
const auto root_obj = it->GetObject();
|
||||
const auto representation = root_obj[kRepresentationKey].GetObject();
|
||||
|
||||
const int64_t hash = root_obj[kHandleKey].GetInt64();
|
||||
DartCallbackRepresentation cb;
|
||||
cb.name = representation[kNameKey].GetString();
|
||||
cb.class_name = representation[kClassNameKey].GetString();
|
||||
cb.library_path = representation[kLibraryPathKey].GetString();
|
||||
cache_[hash] = cb;
|
||||
}
|
||||
}
|
||||
|
||||
Dart_Handle DartCallbackCache::LookupDartClosure(
|
||||
const std::string& name,
|
||||
const std::string& class_name,
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#include "flutter/fml/synchronization/thread_annotations.h"
|
||||
#include "third_party/dart/runtime/include/dart_api.h"
|
||||
|
||||
#define DART_CALLBACK_INVALID_HANDLE -1
|
||||
|
||||
namespace blink {
|
||||
|
||||
typedef struct {
|
||||
@@ -26,6 +24,9 @@ typedef struct {
|
||||
|
||||
class DartCallbackCache {
|
||||
public:
|
||||
static void SetCachePath(const std::string& path);
|
||||
static std::string GetCachePath() { return cache_path_; }
|
||||
|
||||
static int64_t GetCallbackHandle(const std::string& name,
|
||||
const std::string& class_name,
|
||||
const std::string& library_path)
|
||||
@@ -36,12 +37,17 @@ class DartCallbackCache {
|
||||
static std::unique_ptr<DartCallbackRepresentation> GetCallbackInformation(
|
||||
int64_t handle) FML_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
static void LoadCacheFromDisk() FML_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
static Dart_Handle LookupDartClosure(const std::string& name,
|
||||
const std::string& class_name,
|
||||
const std::string& library_path);
|
||||
|
||||
static void SaveCacheToDisk() FML_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
static std::mutex mutex_;
|
||||
static std::string cache_path_;
|
||||
|
||||
static std::map<int64_t, DartCallbackRepresentation> cache_
|
||||
FML_GUARDED_BY(mutex_);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "flutter/fml/message_loop.h"
|
||||
#include "flutter/fml/paths.h"
|
||||
#include "flutter/fml/platform/android/jni_util.h"
|
||||
#include "flutter/lib/ui/plugins/callback_cache.h"
|
||||
#include "flutter/runtime/dart_vm.h"
|
||||
#include "flutter/runtime/start_up.h"
|
||||
#include "flutter/shell/common/shell.h"
|
||||
@@ -44,7 +45,8 @@ void FlutterMain::Init(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jobject context,
|
||||
jobjectArray jargs,
|
||||
jstring bundlePath) {
|
||||
jstring bundlePath,
|
||||
jstring appStoragePath) {
|
||||
std::vector<std::string> args;
|
||||
args.push_back("flutter");
|
||||
for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) {
|
||||
@@ -56,6 +58,11 @@ void FlutterMain::Init(JNIEnv* env,
|
||||
|
||||
settings.assets_path = fml::jni::JavaStringToString(env, bundlePath);
|
||||
|
||||
// Restore the callback cache.
|
||||
blink::DartCallbackCache::SetCachePath(
|
||||
fml::jni::JavaStringToString(env, appStoragePath));
|
||||
blink::DartCallbackCache::LoadCacheFromDisk();
|
||||
|
||||
if (!blink::DartVM::IsRunningPrecompiledCode()) {
|
||||
// Check to see if the appropriate kernel files are present and configure
|
||||
// settings accordingly.
|
||||
@@ -97,7 +104,7 @@ bool FlutterMain::Register(JNIEnv* env) {
|
||||
{
|
||||
.name = "nativeInit",
|
||||
.signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/"
|
||||
"lang/String;)V",
|
||||
"lang/String;Ljava/lang/String;)V",
|
||||
.fnPtr = reinterpret_cast<void*>(&Init),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -31,7 +31,8 @@ class FlutterMain {
|
||||
jclass clazz,
|
||||
jobject context,
|
||||
jobjectArray jargs,
|
||||
jstring bundlePath);
|
||||
jstring bundlePath,
|
||||
jstring appRootPath);
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(FlutterMain);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,10 @@ package io.flutter.util;
|
||||
import android.content.Context;
|
||||
|
||||
public final class PathUtils {
|
||||
public static String getFilesDir(Context applicationContext) {
|
||||
return applicationContext.getFilesDir().getPath();
|
||||
}
|
||||
|
||||
public static String getDataDirectory(Context applicationContext) {
|
||||
return applicationContext.getDir("flutter", Context.MODE_PRIVATE).getPath();
|
||||
}
|
||||
|
||||
@@ -219,8 +219,9 @@ public class FlutterMain {
|
||||
}
|
||||
|
||||
String appBundlePath = findAppBundlePath(applicationContext);
|
||||
String appStoragePath = PathUtils.getFilesDir(applicationContext);
|
||||
nativeInit(applicationContext, shellArgs.toArray(new String[0]),
|
||||
appBundlePath);
|
||||
appBundlePath, appStoragePath);
|
||||
|
||||
sInitialized = true;
|
||||
} catch (Exception e) {
|
||||
@@ -229,7 +230,7 @@ public class FlutterMain {
|
||||
}
|
||||
}
|
||||
|
||||
private static native void nativeInit(Context context, String[] args, String bundlePath);
|
||||
private static native void nativeInit(Context context, String[] args, String bundlePath, String appStoragePath);
|
||||
private static native void nativeRecordStartTimestamp(long initTimeMillis);
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,7 @@ shared_library("create_flutter_framework_dylib") {
|
||||
"framework/Source/FlutterAppDelegate.mm",
|
||||
"framework/Source/FlutterAppDelegate_Internal.h",
|
||||
"framework/Source/FlutterCallbackCache.mm",
|
||||
"framework/Source/FlutterCallbackCache_Internal.h",
|
||||
"framework/Source/FlutterChannels.mm",
|
||||
"framework/Source/FlutterCodecs.mm",
|
||||
"framework/Source/FlutterDartProject.mm",
|
||||
|
||||
@@ -59,6 +59,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
|
||||
|
||||
/**
|
||||
Called if this plugin has been registered for `UIApplicationDelegate` callbacks.
|
||||
|
||||
- Returns: `NO` if this plugin vetoes application launch.
|
||||
*/
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
|
||||
|
||||
/**
|
||||
Called if this plugin has been registered for `UIApplicationDelegate` callbacks.
|
||||
*/
|
||||
@@ -293,8 +302,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@end
|
||||
|
||||
/**
|
||||
Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register themselves to the application
|
||||
life cycle events.
|
||||
Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register themselves
|
||||
to the application life cycle events.
|
||||
*/
|
||||
@protocol FlutterAppLifeCycleProvider
|
||||
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate;
|
||||
|
||||
@@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
FLUTTER_EXPORT
|
||||
@interface FlutterPluginAppLifeCycleDelegate : NSObject
|
||||
/**
|
||||
Registers `delegate` to receive life cycle callbacks via this FlutterPluginAppLifecycleDelegate as long as it is alive.
|
||||
Registers `delegate` to receive life cycle callbacks via this FlutterPluginAppLifecycleDelegate as
|
||||
long as it is alive.
|
||||
|
||||
`delegate` will only referenced weakly.
|
||||
*/
|
||||
@@ -29,6 +30,14 @@ FLUTTER_EXPORT
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks.
|
||||
|
||||
- Returns: `NO` if any plugin vetoes application launch.
|
||||
*/
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks.
|
||||
*/
|
||||
@@ -74,8 +83,8 @@ FLUTTER_EXPORT
|
||||
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
|
||||
the request.
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
|
||||
some plugin handles the request.
|
||||
|
||||
- Returns: `YES` if any plugin handles the request.
|
||||
*/
|
||||
@@ -84,16 +93,16 @@ FLUTTER_EXPORT
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
|
||||
the request.
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
|
||||
some plugin handles the request.
|
||||
|
||||
- Returns: `YES` if any plugin handles the request.
|
||||
*/
|
||||
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
|
||||
the request.
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
|
||||
some plugin handles the request.
|
||||
|
||||
- Returns: `YES` if any plugin handles the request.
|
||||
*/
|
||||
@@ -111,8 +120,8 @@ FLUTTER_EXPORT
|
||||
API_AVAILABLE(ios(9.0));
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
|
||||
the request.
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
|
||||
some plugin handles the request.
|
||||
|
||||
- Returns: `YES` if any plugin handles the request.
|
||||
*/
|
||||
@@ -121,8 +130,8 @@ FLUTTER_EXPORT
|
||||
completionHandler:(nonnull void (^)(void))completionHandler;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
|
||||
the request.
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
|
||||
some plugin handles the request.
|
||||
|
||||
- Returns: `YES` if any plugin handles the request.
|
||||
*/
|
||||
@@ -130,8 +139,8 @@ FLUTTER_EXPORT
|
||||
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
|
||||
|
||||
/**
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
|
||||
the request.
|
||||
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
|
||||
some plugin handles the request.
|
||||
- Returns: `YES` if any plugin handles the request.
|
||||
*/
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||
return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||
return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h"
|
||||
|
||||
#include "flutter/lib/ui/plugins/callback_cache.h"
|
||||
|
||||
@@ -23,4 +23,24 @@
|
||||
return new_info;
|
||||
}
|
||||
|
||||
@end
|
||||
+ (void)setCachePath:(NSString*)path {
|
||||
assert(path != nil);
|
||||
blink::DartCallbackCache::SetCachePath([path UTF8String]);
|
||||
NSString* cache_path =
|
||||
[NSString stringWithUTF8String:blink::DartCallbackCache::GetCachePath().c_str()];
|
||||
// Set the "Do Not Backup" flag to ensure that the cache isn't moved off disk in
|
||||
// low-memory situations.
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:cache_path]) {
|
||||
[[NSFileManager defaultManager] createFileAtPath:cache_path contents:nil attributes:nil];
|
||||
NSError* error = nil;
|
||||
NSURL* URL = [NSURL fileURLWithPath:cache_path];
|
||||
BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES]
|
||||
forKey:NSURLIsExcludedFromBackupKey
|
||||
error:&error];
|
||||
if (!success) {
|
||||
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_FLUTTERCALLBACKCACHE_INTERNAL_H_
|
||||
#define FLUTTER_FLUTTERCALLBACKCACHE_INTERNAL_H_
|
||||
|
||||
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h"
|
||||
|
||||
@interface FlutterCallbackCache ()
|
||||
|
||||
+ (void)setCachePath:(NSString*)path;
|
||||
|
||||
@end
|
||||
|
||||
#endif // FLUTTER_FLUTTERCALLBACKCACHE_INTERNAL_H_
|
||||
@@ -4,7 +4,12 @@
|
||||
|
||||
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/fml/paths.h"
|
||||
#include "flutter/lib/ui/plugins/callback_cache.h"
|
||||
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
|
||||
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h"
|
||||
|
||||
static const char* kCallbackCacheSubDir = "Library/Caches/";
|
||||
|
||||
@implementation FlutterPluginAppLifeCycleDelegate {
|
||||
UIBackgroundTaskIdentifier _debugBackgroundTask;
|
||||
@@ -15,6 +20,8 @@
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
|
||||
[FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
|
||||
_pluginDelegates = [[NSPointerArray weakObjectsPointerArray] retain];
|
||||
}
|
||||
return self;
|
||||
@@ -51,6 +58,22 @@ static BOOL isPowerOfTwo(NSUInteger x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||
blink::DartCallbackCache::LoadCacheFromDisk();
|
||||
for (id<FlutterPlugin> plugin in [_pluginDelegates allObjects]) {
|
||||
if (!plugin) {
|
||||
continue;
|
||||
}
|
||||
if ([plugin respondsToSelector:_cmd]) {
|
||||
if (![plugin application:application willFinishLaunchingWithOptions:launchOptions]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Returns the key window's rootViewController, if it's a FlutterViewController.
|
||||
// Otherwise, returns nil.
|
||||
- (FlutterViewController*)rootFlutterViewController {
|
||||
|
||||
@@ -11171,37 +11171,6 @@ distribution.
|
||||
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.
|
||||
--------------------------------------------------------------------------------
|
||||
topaz
|
||||
|
||||
Copyright 2017, the Flutter project authors. Please see the AUTHORS file
|
||||
for details. 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
|
||||
|
||||
Reference in New Issue
Block a user