Commit 98450e6f authored by Nina Satragno's avatar Nina Satragno Committed by Commit Bot

[webauthn] Port fingerprint enrollment assets to lottie

Replace the fingerprint enrollment APNGs on the webauthn fingerprint
enrollment page by lottie versions. This shaves ~200kb and adds a
transparent background to the fingerprint animation that partially fixes
issue 1041877.

Also, add "hidden" and "singleLoop" attributes to lottie to match the
APNG img tag behaviour; and update the wrapper to allow dynamically
loading another animation into the same element.

Finally, this updates the settings UI worker CSP to allow the lottie
worker.

Fixed: 1082312
Change-Id: I0c92663ec6e1bfd76c03f995b6e96d210f26173f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2116557
Commit-Queue: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: default avatarMalay Keshav <malaykeshav@chromium.org>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#786933}
parent 27891f21
...@@ -28,8 +28,8 @@ const test::UIPath kFingerprintPositionPage = {"fingerprint-setup", ...@@ -28,8 +28,8 @@ const test::UIPath kFingerprintPositionPage = {"fingerprint-setup",
const test::UIPath kProgressPage = {"fingerprint-setup", const test::UIPath kProgressPage = {"fingerprint-setup",
"startFingerprintEnroll"}; "startFingerprintEnroll"};
const test::UIPath kFingerprintArc = {"fingerprint-setup", "arc"}; const test::UIPath kFingerprintArc = {"fingerprint-setup", "arc"};
const test::UIPath kCheckmarkAnimation = {"fingerprint-setup", "arc", const test::UIPath kScanningAnimation = {"fingerprint-setup", "arc",
"checkmarkAnimation"}; "scanningAnimation"};
const test::UIPath kDoneButton = {"fingerprint-setup", "done"}; const test::UIPath kDoneButton = {"fingerprint-setup", "done"};
const test::UIPath kDoItLaterButton = {"fingerprint-setup", "setupLater"}; const test::UIPath kDoItLaterButton = {"fingerprint-setup", "setupLater"};
const test::UIPath kNextButtonOnStart = {"fingerprint-setup", "next"}; const test::UIPath kNextButtonOnStart = {"fingerprint-setup", "next"};
...@@ -40,6 +40,9 @@ const test::UIPath kAddAnotherFingerButton = {"fingerprint-setup", ...@@ -40,6 +40,9 @@ const test::UIPath kAddAnotherFingerButton = {"fingerprint-setup",
"addAnotherFinger"}; "addAnotherFinger"};
constexpr char kTestFingerprintDataString[] = "testFinger"; constexpr char kTestFingerprintDataString[] = "testFinger";
constexpr char kAnimationUrlAttribute[] = "animationUrl";
constexpr char kCheckmarkAnimationUrl[] =
"chrome://theme/IDR_FINGERPRINT_COMPLETE_TICK";
int kMaxAllowedFingerprints = 3; int kMaxAllowedFingerprints = 3;
...@@ -104,7 +107,9 @@ class FingerprintSetupTest : public OobeBaseTest { ...@@ -104,7 +107,9 @@ class FingerprintSetupTest : public OobeBaseTest {
test::OobeJS().ExpectVisiblePath(kFingerprintArc); test::OobeJS().ExpectVisiblePath(kFingerprintArc);
test::OobeJS().CreateVisibilityWaiter(true, kDoneButton)->Wait(); test::OobeJS().CreateVisibilityWaiter(true, kDoneButton)->Wait();
test::OobeJS().ExpectHiddenPath(kSkipButtonOnProgress); test::OobeJS().ExpectHiddenPath(kSkipButtonOnProgress);
test::OobeJS().ExpectVisiblePath(kCheckmarkAnimation); test::OobeJS().ExpectVisiblePath(kScanningAnimation);
test::OobeJS().ExpectAttributeEQ(kAnimationUrlAttribute, kScanningAnimation,
std::string(kCheckmarkAnimationUrl));
test::OobeJS().ExpectVisiblePath(kAddAnotherFingerButton); test::OobeJS().ExpectVisiblePath(kAddAnotherFingerButton);
} }
......
...@@ -161,6 +161,8 @@ SettingsUI::SettingsUI(content::WebUI* web_ui) ...@@ -161,6 +161,8 @@ SettingsUI::SettingsUI(content::WebUI* web_ui)
Profile* profile = Profile::FromWebUI(web_ui); Profile* profile = Profile::FromWebUI(web_ui);
content::WebUIDataSource* html_source = content::WebUIDataSource* html_source =
content::WebUIDataSource::Create(chrome::kChromeUISettingsHost); content::WebUIDataSource::Create(chrome::kChromeUISettingsHost);
html_source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
AddSettingsPageUIHandler(std::make_unique<AppearanceHandler>(web_ui)); AddSettingsPageUIHandler(std::make_unique<AppearanceHandler>(web_ui));
......
...@@ -12226,11 +12226,12 @@ sendInitializedEvent = function() { ...@@ -12226,11 +12226,12 @@ sendInitializedEvent = function() {
* animation. * animation.
*/ */
initAnimation = function(animationData, initParams, canvas) { initAnimation = function(animationData, initParams, canvas) {
if (currentAnimation || !animationData || !initParams) { if (!animationData || !initParams) {
return; return;
} }
var ctx = canvas.getContext("2d"); var ctx = canvas.getContext("2d");
currentAnimation = lottiejs.loadAnimation({ currentAnimation = lottiejs.loadAnimation({
renderer: 'canvas', renderer: 'canvas',
loop: initParams.loop, loop: initParams.loop,
...@@ -12309,6 +12310,13 @@ onmessage = function(evt) { ...@@ -12309,6 +12310,13 @@ onmessage = function(evt) {
return; return;
} }
// Stop and clear the current animation to initialize a new one with the
// provided animation data.
if (currentAnimation && evt.data.animationData) {
currentAnimation.stop();
currentAnimation = null;
}
updateCanvasSize(canvas, evt.data.drawSize); updateCanvasSize(canvas, evt.data.drawSize);
initAnimation(evt.data.animationData, evt.data.params, canvas); initAnimation(evt.data.animationData, evt.data.params, canvas);
updateAnimationState(evt.data.control); updateAnimationState(evt.data.control);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -114,8 +114,8 @@ ...@@ -114,8 +114,8 @@
<structure type="chrome_scaled_image" name="IDR_EASY_UNLOCK_UNLOCKED_HOVER" file="common/easy_unlock_unlocked_hover.png" /> <structure type="chrome_scaled_image" name="IDR_EASY_UNLOCK_UNLOCKED_HOVER" file="common/easy_unlock_unlocked_hover.png" />
<structure type="chrome_scaled_image" name="IDR_EASY_UNLOCK_UNLOCKED_PRESSED" file="common/easy_unlock_unlocked_pressed.png" /> <structure type="chrome_scaled_image" name="IDR_EASY_UNLOCK_UNLOCKED_PRESSED" file="common/easy_unlock_unlocked_pressed.png" />
<if expr="not is_android"> <if expr="not is_android">
<structure type="chrome_scaled_image" name="IDR_FINGERPRINT_COMPLETE_TICK" file="common/tick.png" /> <structure type="chrome_html" name="IDR_FINGERPRINT_COMPLETE_TICK" file="vector/common/tick.json" compress="gzip" />
<structure type="chrome_scaled_image" name="IDR_FINGERPRINT_ICON_ANIMATION" file="common/fingerprint.png" /> <structure type="chrome_html" name="IDR_FINGERPRINT_ICON_ANIMATION" file="vector/common/fingerprint_enrollment.json" compress="gzip" />
</if> </if>
<if expr="is_macosx or is_ios"> <if expr="is_macosx or is_ios">
<structure type="chrome_scaled_image" name="IDR_FOLDER_CLOSED" file="mac/folder.png" /> <structure type="chrome_scaled_image" name="IDR_FOLDER_CLOSED" file="mac/folder.png" />
......
This diff is collapsed.
{"v":"5.5.3","fr":25,"ip":0,"op":100,"w":60,"h":60,"nm":"TICK 1X","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"tick","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[63.25,77.438],[74.562,88.25],[93.875,69.062]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[0]},{"t":22,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.196078431373,0.654901960784,0.325490196078,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":8,"op":100,"st":-24,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"tick circle ","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[30.5,30.75,0],"ix":2},"a":{"a":0,"k":[79.25,79.75,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-0.425,0],[0,0.403],[0.362,0],[0,-0.444]],"o":[[0.451,0],[0,-0.433],[-0.362,0],[0,0.418]],"v":[[78.743,79.75],[79.496,78.985],[78.754,78.25],[78.004,78.985]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":9,"s":[{"i":[[15.03,0],[0,-14.239],[-12.789,0],[0,15.689]],"o":[[-15.953,0],[0,15.294],[12.789,0],[0,-14.766]],"v":[[79.014,52.5],[52.382,79.527],[78.618,105.5],[105.118,79.527]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[{"i":[[14.179,0],[0,-13.433],[-12.065,0],[0,14.801]],"o":[[-15.05,0],[0,14.428],[12.065,0],[0,-13.93]],"v":[[78.999,54],[53.874,79.498],[78.626,104],[103.626,79.498]],"c":true}]},{"t":23,"s":[{"i":[[14.25,0],[0,-13.5],[-12.125,0],[0,14.875]],"o":[[-15.125,0],[0,14.5],[12.125,0],[0,-14]],"v":[[79,53.875],[53.75,79.5],[78.625,104.125],[103.75,79.5]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.196078431373,0.654901960784,0.325490196078,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4.2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":100,"st":-22,"bm":0}],"markers":[]}
\ No newline at end of file
<link rel="import" href="../../html/polymer.html"> <link rel="import" href="../../html/polymer.html">
<link rel="import" href="cr_fingerprint_icon.html"> <link rel="import" href="cr_fingerprint_icon.html">
<link rel="import" href="../cr_lottie/cr_lottie.html">
<dom-module id="cr-fingerprint-progress-arc"> <dom-module id="cr-fingerprint-progress-arc">
<template> <template>
...@@ -8,51 +9,36 @@ ...@@ -8,51 +9,36 @@
user-select: none; user-select: none;
} }
#canvasDiv { .translucent {
height: 240px;
width: 460px;
}
/* Put the image in a separate div with 0 height, otherwise the div will
take the height of the image, leaving us with a row of whitespace when
we position the #image to be inside #arc. */
#imageDiv {
height: 0;
}
#scanningAnimation {
opacity: 0.3; opacity: 0.3;
position: relative;
} }
#enrollmentDone { #canvasDiv {
height: 240px;
overflow: hidden;
position: relative; position: relative;
width: 460px;
} }
#checkmarkDiv { cr-lottie {
height: 0; display: inline-block;
position: absolute;
} }
#checkmarkAnimation { #enrollmentDone {
position: relative; position: absolute;
} }
</style> </style>
<div id="canvasDiv"> <div id="canvasDiv">
<canvas id="canvas" height="240" width="460"></canvas> <canvas id="canvas" height="240" width="460"></canvas>
</div> <cr-lottie id="scanningAnimation"
<div id="imageDiv"> animation-url="chrome://theme/IDR_FINGERPRINT_ICON_ANIMATION"
<img id="scanningAnimation" aria-hidden="true" autoplay>
src="chrome://theme/IDR_FINGERPRINT_ICON_ANIMATION" </cr-lottie>
aria-hidden="true">
<iron-icon id="enrollmentDone" icon="cr-fingerprint-icon:enrollment-done" hidden> <iron-icon id="enrollmentDone" icon="cr-fingerprint-icon:enrollment-done" hidden>
</iron-icon> </iron-icon>
</div> </div>
<div id="checkmarkDiv" hidden>
<img id="checkmarkAnimation"
src="chrome://theme/IDR_FINGERPRINT_COMPLETE_TICK"
aria-hidden="true">
</div>
</template> </template>
<script src="cr_fingerprint_progress_arc.js"></script> <script src="cr_fingerprint_progress_arc.js"></script>
</dom-module> </dom-module>
...@@ -247,10 +247,14 @@ Polymer({ ...@@ -247,10 +247,14 @@ Polymer({
* @private * @private
*/ */
animateScanComplete_() { animateScanComplete_() {
this.$.checkmarkDiv.hidden = false; this.$.scanningAnimation.singleLoop = true;
this.$.scanningAnimation.classList.remove('translucent');
this.$.scanningAnimation.animationUrl =
'chrome://theme/IDR_FINGERPRINT_COMPLETE_TICK';
this.resizeCheckMark_(
/** @type {!HTMLElement} */ (this.$.scanningAnimation));
this.$.enrollmentDone.hidden = false; this.$.enrollmentDone.hidden = false;
this.$.scanningAnimation.hidden = true;
this.$.enrollmentDone.style.opacity = 1;
}, },
/** /**
...@@ -259,12 +263,8 @@ Polymer({ ...@@ -259,12 +263,8 @@ Polymer({
*/ */
animateScanProgress_() { animateScanProgress_() {
this.$.enrollmentDone.hidden = false; this.$.enrollmentDone.hidden = false;
this.$.enrollmentDone.style.opacity = 0.3;
this.$.scanningAnimation.hidden = true;
this.updateTimerId_ = window.setTimeout(() => { this.updateTimerId_ = window.setTimeout(() => {
this.$.enrollmentDone.hidden = true; this.$.enrollmentDone.hidden = true;
this.$.enrollmentDone.style.opacity = 1;
this.$.scanningAnimation.hidden = false;
}, FINGERPRINT_SCAN_SUCCESS_MS); }, FINGERPRINT_SCAN_SUCCESS_MS);
}, },
...@@ -287,8 +287,14 @@ Polymer({ ...@@ -287,8 +287,14 @@ Polymer({
this.isComplete_ = false; this.isComplete_ = false;
this.drawBackgroundCircle(); this.drawBackgroundCircle();
this.$.enrollmentDone.hidden = true; this.$.enrollmentDone.hidden = true;
this.$.scanningAnimation.singleLoop = false;
this.$.scanningAnimation.classList.add('translucent');
this.$.scanningAnimation.animationUrl =
'chrome://theme/IDR_FINGERPRINT_ICON_ANIMATION';
this.resizeAndCenterIcon_(
/** @type {!HTMLElement} */ (this.$.scanningAnimation));
this.$.scanningAnimation.hidden = false; this.$.scanningAnimation.hidden = false;
this.$.checkmarkDiv.hidden = true;
}, },
/** /**
...@@ -300,8 +306,6 @@ Polymer({ ...@@ -300,8 +306,6 @@ Polymer({
/** @type {!HTMLElement} */ (this.$.scanningAnimation)); /** @type {!HTMLElement} */ (this.$.scanningAnimation));
this.resizeAndCenterIcon_( this.resizeAndCenterIcon_(
/** @type {!HTMLElement} */ (this.$.enrollmentDone)); /** @type {!HTMLElement} */ (this.$.enrollmentDone));
this.resizeCheckMark_(
/** @type {!HTMLElement} */ (this.$.checkmarkAnimation));
}, },
/** /**
...@@ -317,7 +321,7 @@ Polymer({ ...@@ -317,7 +321,7 @@ Polymer({
// Place in the center of the canvas. // Place in the center of the canvas.
const left = this.$.canvas.width / 2 - ICON_WIDTH * this.scale_ / 2; const left = this.$.canvas.width / 2 - ICON_WIDTH * this.scale_ / 2;
const top = 0 - ICON_HEIGHT * this.scale_ / 2 - this.$.canvas.height / 2; const top = this.$.canvas.height / 2 - ICON_HEIGHT * this.scale_ / 2;
target.style.left = left + 'px'; target.style.left = left + 'px';
target.style.top = top + 'px'; target.style.top = top + 'px';
}, },
...@@ -334,9 +338,8 @@ Polymer({ ...@@ -334,9 +338,8 @@ Polymer({
target.style.height = CHECK_MARK_SIZE * this.scale_ + 'px'; target.style.height = CHECK_MARK_SIZE * this.scale_ + 'px';
// Place it in the left bottom corner of fingerprint progress circle. // Place it in the left bottom corner of fingerprint progress circle.
const top = 0 - const top = this.$.canvas.height / 2 + this.circleRadius -
(CHECK_MARK_SIZE * this.scale_ + this.$.canvas.height / 2 - CHECK_MARK_SIZE * this.scale_;
this.circleRadius);
const left = this.$.canvas.width / 2 + this.circleRadius - const left = this.$.canvas.width / 2 + this.circleRadius -
CHECK_MARK_SIZE * this.scale_; CHECK_MARK_SIZE * this.scale_;
target.style.left = left + 'px'; target.style.left = left + 'px';
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
width: 100%; width: 100%;
} }
</style> </style>
<canvas id="canvas"></canvas> <canvas id="canvas" hidden="[[hidden]]"></canvas>
</template> </template>
<script src="cr_lottie.js"></script> <script src="cr_lottie.js"></script>
</dom-module> </dom-module>
...@@ -27,22 +27,40 @@ Polymer({ ...@@ -27,22 +27,40 @@ Polymer({
animationUrl: { animationUrl: {
type: String, type: String,
value: '', value: '',
observer: 'animationUrlChanged_',
}, },
autoplay: { autoplay: {
type: Boolean, type: Boolean,
value: false, value: false,
}, },
hidden: {
type: Boolean,
value: false,
},
singleLoop: {
type: Boolean,
value: false,
},
}, },
/** @private {?HTMLCanvasElement} */ /** @private {?HTMLCanvasElement} */
canvasElement_: null, canvasElement_: null,
/** @private {boolean} True if the animation has loaded successfully */ /** @private {boolean} Whether the animation has loaded successfully */
isAnimationLoaded_: false, isAnimationLoaded_: false,
/** @private {?OffscreenCanvas} */ /** @private {?OffscreenCanvas} */
offscreenCanvas_: null, offscreenCanvas_: null,
/**
* @private {boolean} Whether the canvas has been transferred to the worker
* thread.
*/
hasTransferredCanvas_: false,
/** @private {?ResizeObserver} */ /** @private {?ResizeObserver} */
resizeObserver_: null, resizeObserver_: null,
...@@ -109,6 +127,23 @@ Polymer({ ...@@ -109,6 +127,23 @@ Polymer({
this.animationUrl, 'json', this.initAnimation_.bind(this)); this.animationUrl, 'json', this.initAnimation_.bind(this));
}, },
/**
* Updates the animation that is being displayed.
* @param {string} animationUrl the new animation URL.
* @param {string} oldAnimationUrl the previous animation URL.
* @private
*/
animationUrlChanged_(animationUrl, oldAnimationUrl) {
// Animation changes should be made after the previous animation loaded to
// avoid a race condition.
if (!oldAnimationUrl || !this.worker_) {
return;
}
this.isAnimationLoaded_ = false;
this.sendXmlHttpRequest_(
this.animationUrl, 'json', this.initAnimation_.bind(this));
},
/** /**
* Computes the draw buffer size for the canvas. This ensures that the * Computes the draw buffer size for the canvas. This ensures that the
* rasterization is crisp and sharp rather than blurry. * rasterization is crisp and sharp rather than blurry.
...@@ -181,14 +216,17 @@ Polymer({ ...@@ -181,14 +216,17 @@ Polymer({
* @private * @private
*/ */
initAnimation_(animationData) { initAnimation_(animationData) {
this.worker_.postMessage( const message = [{
{ animationData,
canvas: this.offscreenCanvas_, drawSize: this.getCanvasDrawBufferSize_(),
animationData: animationData, params: {loop: !this.singleLoop, autoplay: this.autoplay}
drawSize: this.getCanvasDrawBufferSize_(), }];
params: {loop: true, autoplay: this.autoplay} if (!this.hasTransferredCanvas_) {
}, message[0].canvas = this.offscreenCanvas_;
[this.offscreenCanvas_]); message.push([this.offscreenCanvas_]);
this.hasTransferredCanvas_ = true;
}
this.worker_.postMessage(...message);
}, },
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment