[skottie/ck] Expose logs in JS API
Plumb a JS logger object, and forward errors/warnings to its methods:
onError(err_str, json_node_str)
onWarning(wrn_str, json_node_str)
Change-Id: I796aeb313c4a693accafe04edf80178b227ab118
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/370937
Commit-Queue: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Jorge Betancourt <jmbetancourt@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index fbf3402..6d44ec8 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -6,6 +6,9 @@
## [Unreleased]
+### Added
+ - The Skottie factory (MakeManagedAnimation) now accepts an optional logger object.
+
### Breaking
- `CanvasKit.getDataBytes` has been removed, as has the Data type. The 2 APIS that returned
Data now return Uint8Array containing the bytes directly. These are `Image.encodeToData`
diff --git a/modules/canvaskit/skottie.js b/modules/canvaskit/skottie.js
index acd70ba..ac448db 100644
--- a/modules/canvaskit/skottie.js
+++ b/modules/canvaskit/skottie.js
@@ -10,7 +10,11 @@
// soundMap is an optional object that maps string names to AudioPlayers
// AudioPlayers manage a single audio layer with a seek function
-CanvasKit.MakeManagedAnimation = function(json, assets, prop_filter_prefix, soundMap) {
+
+// logger is an optional logging object, expected to provide two functions:
+// - onError(err_str, json_node_str)
+// - onWarning(wrn_str, json_node_str)
+CanvasKit.MakeManagedAnimation = function(json, assets, prop_filter_prefix, soundMap, logger) {
if (!CanvasKit._MakeManagedAnimation) {
throw 'Not compiled with MakeManagedAnimation';
}
@@ -18,7 +22,8 @@
prop_filter_prefix = '';
}
if (!assets) {
- return CanvasKit._MakeManagedAnimation(json, 0, nullptr, nullptr, nullptr, prop_filter_prefix, soundMap);
+ return CanvasKit._MakeManagedAnimation(json, 0, nullptr, nullptr, nullptr, prop_filter_prefix,
+ soundMap, logger);
}
var assetNamePtrs = [];
var assetDataPtrs = [];
@@ -52,7 +57,8 @@
var assetSizesPtr = copy1dArray(assetSizes, "HEAPU32");
var anim = CanvasKit._MakeManagedAnimation(json, assetKeys.length, namesPtr,
- assetsPtr, assetSizesPtr, prop_filter_prefix, soundMap);
+ assetsPtr, assetSizesPtr, prop_filter_prefix,
+ soundMap, logger);
// The C++ code has made copies of the asset and string data, so free our copies.
CanvasKit._free(namesPtr);
diff --git a/modules/canvaskit/skottie_bindings.cpp b/modules/canvaskit/skottie_bindings.cpp
index 980d6ab..48f0e61 100644
--- a/modules/canvaskit/skottie_bindings.cpp
+++ b/modules/canvaskit/skottie_bindings.cpp
@@ -55,7 +55,8 @@
using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>;
static sk_sp<SkottieAssetProvider> Make(AssetVec assets, emscripten::val soundMap) {
- return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets), std::move(soundMap)));
+ return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets),
+ std::move(soundMap)));
}
sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */,
@@ -120,11 +121,37 @@
const emscripten::val fSoundMap;
};
+// Wraps a JS object with 'onError' and 'onWarning' methods.
+class JSLogger final : public skottie::Logger {
+public:
+ static sk_sp<JSLogger> Make(emscripten::val logger) {
+ return logger.as<bool>()
+ && logger.hasOwnProperty(kWrnFunc)
+ && logger.hasOwnProperty(kErrFunc)
+ ? sk_sp<JSLogger>(new JSLogger(std::move(logger)))
+ : nullptr;
+ }
+
+private:
+ explicit JSLogger(emscripten::val logger) : fLogger(std::move(logger)) {}
+
+ void log(Level lvl, const char msg[], const char* json) override {
+ const auto* func = lvl == Level::kError ? kErrFunc : kWrnFunc;
+ fLogger.call<void>(func, std::string(msg), std::string(json));
+ }
+
+ static constexpr char kWrnFunc[] = "onWarning",
+ kErrFunc[] = "onError";
+
+ const emscripten::val fLogger;
+};
+
class ManagedAnimation final : public SkRefCnt {
public:
static sk_sp<ManagedAnimation> Make(const std::string& json,
sk_sp<skottie::ResourceProvider> rp,
- std::string prop_prefix) {
+ std::string prop_prefix,
+ emscripten::val logger) {
auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>(
skottie_utils::CustomPropertyManager::Mode::kCollapseProperties,
prop_prefix.empty() ? nullptr : prop_prefix.c_str());
@@ -136,6 +163,7 @@
.setPropertyObserver(mgr->getPropertyObserver())
.setResourceProvider(std::move(rp))
.setPrecompInterceptor(std::move(pinterceptor))
+ .setLogger(JSLogger::Make(std::move(logger)))
.make(json.c_str(), json.size());
return animation
@@ -215,10 +243,11 @@
ManagedAnimation(sk_sp<skottie::Animation> animation,
std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
: fAnimation(std::move(animation))
- , fPropMgr(std::move(propMgr)) {}
+ , fPropMgr(std::move(propMgr))
+ {}
- sk_sp<skottie::Animation> fAnimation;
- std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
+ const sk_sp<skottie::Animation> fAnimation;
+ const std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
};
} // anonymous ns
@@ -298,7 +327,8 @@
uintptr_t /* uint8_t** */ dptr,
uintptr_t /* size_t* */ sptr,
std::string prop_prefix,
- emscripten::val soundMap)
+ emscripten::val soundMap,
+ emscripten::val logger)
->sk_sp<ManagedAnimation> {
// See the comment in canvaskit_bindings.cpp about the use of uintptr_t
const auto assetNames = reinterpret_cast<char** >(nptr);
@@ -316,8 +346,9 @@
return ManagedAnimation::Make(json,
skresources::DataURIResourceProviderProxy::Make(
- SkottieAssetProvider::Make(std::move(assets), std::move(soundMap))),
- prop_prefix);
+ SkottieAssetProvider::Make(std::move(assets),
+ std::move(soundMap))),
+ prop_prefix, std::move(logger));
}));
constant("managed_skottie", true);
#endif // SK_INCLUDE_MANAGED_SKOTTIE
diff --git a/modules/canvaskit/tests/skottie.spec.js b/modules/canvaskit/tests/skottie.spec.js
index 247198a..a1d6f2d 100644
--- a/modules/canvaskit/tests/skottie.spec.js
+++ b/modules/canvaskit/tests/skottie.spec.js
@@ -116,4 +116,101 @@
done();
});
});
+
+ it('can get logs', (done) => {
+ if (!CanvasKit.skottie || !CanvasKit.managed_skottie) {
+ console.warn('Skipping test because not compiled with skottie');
+ return;
+ }
+
+ const logger = {
+ errors: [],
+ warnings: [],
+
+ reset: function() { this.errors = []; this.warnings = []; },
+
+ // Logger API
+ onError: function(err) { this.errors.push(err) },
+ onWarning: function(wrn) { this.warnings.push(wrn) }
+ };
+
+ {
+ const json = `{
+ "v": "5.2.1",
+ "w": 100,
+ "h": 100,
+ "fr": 10,
+ "ip": 0,
+ "op": 100,
+ "layers": [{
+ "ty": 3,
+ "nm": "null",
+ "ind": 0,
+ "ip": 0
+ }]
+ }`;
+ const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger);
+ expect(animation).toBeTruthy();
+ expect(logger.errors.length).toEqual(0);
+ expect(logger.warnings.length).toEqual(0);
+ }
+
+ {
+ const json = `{
+ "v": "5.2.1",
+ "w": 100,
+ "h": 100,
+ "fr": 10,
+ "ip": 0,
+ "op": 100,
+ "layers": [{
+ "ty": 2,
+ "nm": "image",
+ "ind": 0,
+ "ip": 0
+ }]
+ }`;
+ const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger);
+ expect(animation).toBeTruthy();
+ expect(logger.errors.length).toEqual(1);
+ expect(logger.warnings.length).toEqual(0);
+
+ // Image layer missing refID
+ expect(logger.errors[0].includes('missing ref'));
+ logger.reset();
+ }
+
+ {
+ const json = `{
+ "v": "5.2.1",
+ "w": 100,
+ "h": 100,
+ "fr": 10,
+ "ip": 0,
+ "op": 100,
+ "layers": [{
+ "ty": 1,
+ "nm": "solid",
+ "sw": 100,
+ "sh": 100,
+ "sc": "#aabbcc",
+ "ind": 0,
+ "ip": 0,
+ "ef": [{
+ "mn": "FOO"
+ }]
+ }]
+ }`;
+ const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger);
+ expect(animation).toBeTruthy();
+ expect(logger.errors.length).toEqual(0);
+ expect(logger.warnings.length).toEqual(1);
+
+ // Unsupported effect FOO
+ expect(logger.warnings[0].includes('FOO'));
+ logger.reset();
+ }
+
+ done();
+ });
});