From 3cbb5e2067ef9611e55a66585a65830b45cc1bb5 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 7 Aug 2018 14:37:19 -0700 Subject: [PATCH] Persist DartCallbackCache contents across launches (#5947) * Updated DartCallbackCache to write callback cache to disk which is restored on engine startup * Ensure cache isn't moved off disk in iOS --- ci/licenses_golden/licenses_flutter | 1 + lib/ui/plugins/callback_cache.cc | 101 +++++++++++++++++- lib/ui/plugins/callback_cache.h | 10 +- shell/platform/android/flutter_main.cc | 11 +- shell/platform/android/flutter_main.h | 3 +- .../android/io/flutter/util/PathUtils.java | 4 + .../android/io/flutter/view/FlutterMain.java | 5 +- shell/platform/darwin/ios/BUILD.gn | 1 + .../ios/framework/Headers/FlutterPlugin.h | 13 ++- .../FlutterPluginAppLifeCycleDelegate.h | 35 +++--- .../framework/Source/FlutterAppDelegate.mm | 5 + .../framework/Source/FlutterCallbackCache.mm | 24 ++++- .../Source/FlutterCallbackCache_Internal.h | 16 +++ .../FlutterPluginAppLifeCycleDelegate.mm | 23 ++++ sky/packages/sky_engine/LICENSE | 31 ------ 15 files changed, 227 insertions(+), 56 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7aeb58b5a..ed0e4e1e7 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -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 diff --git a/lib/ui/plugins/callback_cache.cc b/lib/ui/plugins/callback_cache.cc index 0e46493e2..a80b956a0 100644 --- a/lib/ui/plugins/callback_cache.cc +++ b/lib/ui/plugins/callback_cache.cc @@ -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 +#include + +#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 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 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 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 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(input), + std::istreambuf_iterator()}; + 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, diff --git a/lib/ui/plugins/callback_cache.h b/lib/ui/plugins/callback_cache.h index 04e919a47..b13002d33 100644 --- a/lib/ui/plugins/callback_cache.h +++ b/lib/ui/plugins/callback_cache.h @@ -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 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 cache_ FML_GUARDED_BY(mutex_); diff --git a/shell/platform/android/flutter_main.cc b/shell/platform/android/flutter_main.cc index 2a9a46a97..fbc0789fa 100644 --- a/shell/platform/android/flutter_main.cc +++ b/shell/platform/android/flutter_main.cc @@ -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 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(&Init), }, { diff --git a/shell/platform/android/flutter_main.h b/shell/platform/android/flutter_main.h index acd4a4636..6ad122f99 100644 --- a/shell/platform/android/flutter_main.h +++ b/shell/platform/android/flutter_main.h @@ -31,7 +31,8 @@ class FlutterMain { jclass clazz, jobject context, jobjectArray jargs, - jstring bundlePath); + jstring bundlePath, + jstring appRootPath); FML_DISALLOW_COPY_AND_ASSIGN(FlutterMain); }; diff --git a/shell/platform/android/io/flutter/util/PathUtils.java b/shell/platform/android/io/flutter/util/PathUtils.java index 55d3fcc80..df187cbc0 100644 --- a/shell/platform/android/io/flutter/util/PathUtils.java +++ b/shell/platform/android/io/flutter/util/PathUtils.java @@ -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(); } diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index 66b2fff70..ccdfb6f4a 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -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); /** diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index ca640ed13..3de4e258a 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -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", diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index 42fa50840..799aa90fa 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -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*)delegate; diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h b/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h index bce71db7d..8c9c03586 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h @@ -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*)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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index a544d516c..763374b96 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -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]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm b/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm index 61ff0b165..eb353e019 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm @@ -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 \ No newline at end of file ++ (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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h new file mode 100644 index 000000000..553c485dc --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h @@ -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_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm index 81a05493b..e4d9c03cf 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm @@ -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 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 { diff --git a/sky/packages/sky_engine/LICENSE b/sky/packages/sky_engine/LICENSE index 823c1fa3d..149dbfb7f 100644 --- a/sky/packages/sky_engine/LICENSE +++ b/sky/packages/sky_engine/LICENSE @@ -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 -- GitLab