Commit 60bfd697 authored by Nina Satragno's avatar Nina Satragno Committed by Commit Bot

[webui] Dark mode for cr-fingerprint-progress-arc

Add a dark mode version of the fingerprint checkmark animation and use
it in cr-fingerprint-progress-arc when dark mode is enabled. The
animation looks bad with a dark background otherwise.

Since we're using MockController on a test that is closure-compiled, fix
all its compilation issues.

See the bug for screenshots.

Fixed: 1041877
Change-Id: I7d7fcdb01c372038c8580cdad1c7177fdc16ac80
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2290779
Commit-Queue: Nina Satragno <nsatragno@chromium.org>
Auto-Submit: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791421}
parent 0c0ee419
...@@ -396,6 +396,11 @@ js_type_check("closure_compile_local") { ...@@ -396,6 +396,11 @@ js_type_check("closure_compile_local") {
] ]
} }
js_library("mock_controller.m") {
sources = [ "$root_gen_dir/chrome/test/data/webui/mock_controller.m.js" ]
extra_deps = [ ":modulize_local" ]
}
js_library("mock_timer.m") { js_library("mock_timer.m") {
sources = [ "$root_gen_dir/chrome/test/data/webui/mock_timer.m.js" ] sources = [ "$root_gen_dir/chrome/test/data/webui/mock_timer.m.js" ]
extra_deps = [ ":modulize_local" ] extra_deps = [ ":modulize_local" ]
......
...@@ -209,8 +209,10 @@ js_library("cr_fingerprint_progress_arc_tests.m") { ...@@ -209,8 +209,10 @@ js_library("cr_fingerprint_progress_arc_tests.m") {
sources = [ "$root_gen_dir/chrome/test/data/webui/cr_elements/cr_fingerprint_progress_arc_tests.m.js" ] sources = [ "$root_gen_dir/chrome/test/data/webui/cr_elements/cr_fingerprint_progress_arc_tests.m.js" ]
deps = [ deps = [
"..:chai_assert", "..:chai_assert",
"..:mock_controller.m",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/cr_elements/cr_fingerprint:cr_fingerprint_progress_arc.m", "//ui/webui/resources/cr_elements/cr_fingerprint:cr_fingerprint_progress_arc.m",
"//ui/webui/resources/cr_elements/cr_lottie:cr_lottie.m",
] ]
externs_list = [ "$externs_path/mocha-2.5.js" ] externs_list = [ "$externs_path/mocha-2.5.js" ]
extra_deps = [ ":modulize" ] extra_deps = [ ":modulize" ]
......
...@@ -272,6 +272,7 @@ CrElementsFingerprintProgressArcTest.prototype = { ...@@ -272,6 +272,7 @@ CrElementsFingerprintProgressArcTest.prototype = {
/** @override */ /** @override */
extraLibraries: CrElementsBrowserTest.prototype.extraLibraries.concat([ extraLibraries: CrElementsBrowserTest.prototype.extraLibraries.concat([
'../mock_controller.js',
'cr_fingerprint_progress_arc_tests.js', 'cr_fingerprint_progress_arc_tests.js',
]), ]),
}; };
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
// found in the LICENSE file. // found in the LICENSE file.
// clang-format off // clang-format off
// #import 'chrome://resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.m.js'; // #import {FINGEPRINT_TICK_LIGHT_URL, FINGEPRINT_TICK_DARK_URL} from 'chrome://resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.m.js';
// #import 'chrome://resources/cr_elements/cr_lottie/cr_lottie.m.js';
// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// #import {assertEquals} from '../chai_assert.js'; // #import {assertEquals} from '../chai_assert.js';
// #import {MockController, MockMethod} from '../mock_controller.m.js';
// clang-format on // clang-format on
/** @fileoverview Suite of tests for cr-fingerprint-progress-arc. */ /** @fileoverview Suite of tests for cr-fingerprint-progress-arc. */
...@@ -44,7 +46,31 @@ suite('cr_fingerprint_progress_arc_test', function() { ...@@ -44,7 +46,31 @@ suite('cr_fingerprint_progress_arc_test', function() {
/** @type {!Color} */ /** @type {!Color} */
const white = {r: 255, g: 255, b: 255}; const white = {r: 255, g: 255, b: 255};
/** @type {!MockController} */
let mockController;
/** @type {!Object} */
let darkModeQuery;
setup(function() { setup(function() {
mockController = new MockController();
const matchMediaMock =
mockController.createFunctionMock(window, 'matchMedia');
matchMediaMock.addExpectation('(prefers-color-scheme: dark)');
darkModeQuery = {
listener: null,
matches: false,
addListener: function(listener) {
this.listener = listener;
},
removeListener: function(listener) {
assertEquals(listener, this.listener);
this.listener = null;
},
};
matchMediaMock.returnValue = darkModeQuery;
document.body.innerHTML = ''; document.body.innerHTML = '';
progressArc = /** @type {!CrFingerprintProgressArcElement} */ ( progressArc = /** @type {!CrFingerprintProgressArcElement} */ (
document.createElement('cr-fingerprint-progress-arc')); document.createElement('cr-fingerprint-progress-arc'));
...@@ -161,4 +187,16 @@ suite('cr_fingerprint_progress_arc_test', function() { ...@@ -161,4 +187,16 @@ suite('cr_fingerprint_progress_arc_test', function() {
progressArc.clearCanvas(); progressArc.clearCanvas();
assertListOfColorsEqual(white, expectedPointsInCircle); assertListOfColorsEqual(white, expectedPointsInCircle);
}); });
test('TestSwitchToDarkMode', function() {
const scanningAnimation =
/** @type {!CrLottieElement} */ (progressArc.$$('#scanningAnimation'));
progressArc.setProgress(0, 1, true);
assertEquals(FINGEPRINT_TICK_LIGHT_URL, scanningAnimation.animationUrl);
darkModeQuery.matches = true;
darkModeQuery.listener();
assertEquals(FINGEPRINT_TICK_DARK_URL, scanningAnimation.animationUrl);
});
}); });
...@@ -2,12 +2,16 @@ ...@@ -2,12 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// #import {assertEquals, assertDeepEquals} from '../chai_assert.js';
/** /**
* Create a mock function that records function calls and validates against * Create a mock function that records function calls and validates against
* expectations. * expectations.
* @extends Function
*/ */
/* #export */ class MockMethod { /* #export */ class MockMethod {
constructor() { constructor() {
/** @type {MockMethod|Function} */
var fn = function() { var fn = function() {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
var callbacks = args.filter(function(arg) { var callbacks = args.filter(function(arg) {
...@@ -20,12 +24,13 @@ ...@@ -20,12 +24,13 @@
return; return;
} }
fn.recordCall(args); var fnAsMethod = /** @type {!MockMethod} */ (fn);
fnAsMethod.recordCall(args);
if (callbacks.length === 1) { if (callbacks.length === 1) {
callbacks[0].apply(undefined, fn.callbackData); callbacks[0].apply(undefined, fnAsMethod.callbackData);
return; return;
} }
return fn.returnValue; return fnAsMethod.returnValue;
}; };
/** /**
...@@ -33,43 +38,50 @@ ...@@ -33,43 +38,50 @@
* @type {!Array<!Array>} * @type {!Array<!Array>}
* @private * @private
*/ */
fn.calls_ = []; this.calls_ = [];
/** /**
* List of expected call signatures. * List of expected call signatures.
* @type {!Array<!Array>} * @type {!Array<!Array>}
* @private * @private
*/ */
fn.expectations_ = []; this.expectations_ = [];
/** /**
* Value returned from call to function. * Value returned from call to function.
* @type {*} * @type {*}
*/ */
fn.returnValue = undefined; this.returnValue = undefined;
/** /**
* List of arguments for callback function. * List of arguments for callback function.
* @type {!Array<!Array>} * @type {!Array<!Array>}
*/ */
fn.callbackData = []; this.callbackData = [];
Object.setPrototypeOf(fn, MockMethod.prototype); /**
return fn; * Name of the function being replaced.
* @type {?string}
*/
this.functionName = null;
var fnAsMethod = /** @type {!MockMethod} */ (fn);
Object.assign(fnAsMethod, this);
Object.setPrototypeOf(fnAsMethod, MockMethod.prototype);
return fnAsMethod;
} }
/** /**
* Adds an expected call signature. * Adds an expected call signature.
* @param {...} var_args Expected arguments for the function call. * @param {...} args Expected arguments for the function call.
*/ */
addExpectation() { addExpectation(...args) {
var args = Array.prototype.slice.call(arguments);
this.expectations_.push(args.filter(this.notFunction_)); this.expectations_.push(args.filter(this.notFunction_));
} }
/** /**
* Adds a call signature. * Adds a call signature.
* @param {!Array} args. * @param {!Array} args
*/ */
recordCall(args) { recordCall(args) {
this.calls_.push(args.filter(this.notFunction_)); this.calls_.push(args.filter(this.notFunction_));
...@@ -97,7 +109,7 @@ ...@@ -97,7 +109,7 @@
* base implementation, but provides context that may be useful for * base implementation, but provides context that may be useful for
* overrides. * overrides.
* @param {!Array} expected The expected arguments. * @param {!Array} expected The expected arguments.
* @parma {!Array} observed The observed arguments. * @param {!Array} observed The observed arguments.
*/ */
validateCall(index, expected, observed) { validateCall(index, expected, observed) {
assertDeepEquals(expected, observed); assertDeepEquals(expected, observed);
......
...@@ -115,6 +115,7 @@ ...@@ -115,6 +115,7 @@
<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_html" name="IDR_FINGERPRINT_COMPLETE_TICK" file="vector/common/tick.json" compress="gzip" /> <structure type="chrome_html" name="IDR_FINGERPRINT_COMPLETE_TICK" file="vector/common/tick.json" compress="gzip" />
<structure type="chrome_html" name="IDR_FINGERPRINT_COMPLETE_TICK_DARK" file="vector/common/tick_dark.json" compress="gzip" />
<structure type="chrome_html" name="IDR_FINGERPRINT_ICON_ANIMATION" file="vector/common/fingerprint_enrollment.json" compress="gzip" /> <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">
......
{"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":[0.14,0.14,0.14,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":[0.14,0.14,0.14,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":[]}
...@@ -10,6 +10,7 @@ js_type_check("closure_compile") { ...@@ -10,6 +10,7 @@ js_type_check("closure_compile") {
} }
js_library("cr_fingerprint_progress_arc") { js_library("cr_fingerprint_progress_arc") {
deps = [ "../cr_lottie:cr_lottie" ]
} }
group("polymer3_elements") { group("polymer3_elements") {
...@@ -39,6 +40,7 @@ js_type_check("closure_compile_module") { ...@@ -39,6 +40,7 @@ js_type_check("closure_compile_module") {
js_library("cr_fingerprint_progress_arc.m") { js_library("cr_fingerprint_progress_arc.m") {
sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.m.js" ] sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.m.js" ]
deps = [ deps = [
"../cr_lottie:cr_lottie.m",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
] ]
extra_deps = [ ":cr_fingerprint_progress_arc_module" ] extra_deps = [ ":cr_fingerprint_progress_arc_module" ]
......
...@@ -32,9 +32,7 @@ ...@@ -32,9 +32,7 @@
<div id="canvasDiv"> <div id="canvasDiv">
<canvas id="canvas" height="240" width="460"></canvas> <canvas id="canvas" height="240" width="460"></canvas>
<cr-lottie id="scanningAnimation" <cr-lottie id="scanningAnimation" aria-hidden="true" autoplay>
animation-url="chrome://theme/IDR_FINGERPRINT_ICON_ANIMATION"
aria-hidden="true" autoplay>
</cr-lottie> </cr-lottie>
<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>
......
...@@ -2,6 +2,16 @@ ...@@ -2,6 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// #import {CrLottieElement} from '../cr_lottie/cr_lottie.m.js';
/** @type {string} */
/* #export */ const FINGEPRINT_TICK_DARK_URL =
'chrome://theme/IDR_FINGERPRINT_COMPLETE_TICK_DARK';
/** @type {string} */
/* #export */ const FINGEPRINT_TICK_LIGHT_URL =
'chrome://theme/IDR_FINGERPRINT_COMPLETE_TICK';
(function() { (function() {
/** /**
...@@ -127,10 +137,36 @@ Polymer({ ...@@ -127,10 +137,36 @@ Polymer({
*/ */
updateTimerId_: undefined, updateTimerId_: undefined,
/**
* Media query for dark mode.
* @type {MediaQueryList|undefined}
* @private
*/
darkModeQuery_: undefined,
/**
* Dark mode change listener callback.
* @type {function(MediaQueryList)|undefined}
* @private
*/
darkModeListener_: undefined,
/** @override */ /** @override */
attached() { attached() {
this.scale_ = this.circleRadius / DEFAULT_CANVAS_CIRCLE_RADIUS; this.scale_ = this.circleRadius / DEFAULT_CANVAS_CIRCLE_RADIUS;
this.updateImages_(); this.updateImages_();
this.darkModeListener_ = this.updateAnimationAsset_.bind(this);
this.darkModeQuery_ = window.matchMedia('(prefers-color-scheme: dark)');
this.darkModeQuery_.addListener(this.darkModeListener_);
this.updateAnimationAsset_();
},
/** @override */
detached() {
this.darkModeQuery_.removeListener(
/** @type {function(MediaQueryList)} */ (this.darkModeListener_));
this.darkModeListener_ = undefined;
}, },
/** /**
...@@ -227,6 +263,24 @@ Polymer({ ...@@ -227,6 +263,24 @@ Polymer({
} }
}, },
/**
* Updates the lottie animation taking into account the current state and
* whether dark mode is enabled.
* @private
*/
updateAnimationAsset_() {
const scanningAnimation =
/** @type {CrLottieElement} */ (this.$.scanningAnimation);
if (this.isComplete_) {
scanningAnimation.animationUrl = this.darkModeQuery_.matches ?
FINGEPRINT_TICK_DARK_URL :
FINGEPRINT_TICK_LIGHT_URL;
return;
}
scanningAnimation.animationUrl =
'chrome://theme/IDR_FINGERPRINT_ICON_ANIMATION';
},
/* /*
* Cleans up any pending animation update created by setInterval(). * Cleans up any pending animation update created by setInterval().
* @private * @private
...@@ -247,12 +301,12 @@ Polymer({ ...@@ -247,12 +301,12 @@ Polymer({
* @private * @private
*/ */
animateScanComplete_() { animateScanComplete_() {
this.$.scanningAnimation.singleLoop = true; const scanningAnimation =
this.$.scanningAnimation.classList.remove('translucent'); /** @type {CrLottieElement|HTMLElement} */ (this.$.scanningAnimation);
this.$.scanningAnimation.animationUrl = scanningAnimation.singleLoop = true;
'chrome://theme/IDR_FINGERPRINT_COMPLETE_TICK'; scanningAnimation.classList.remove('translucent');
this.resizeCheckMark_( this.updateAnimationAsset_();
/** @type {!HTMLElement} */ (this.$.scanningAnimation)); this.resizeCheckMark_(scanningAnimation);
this.$.enrollmentDone.hidden = false; this.$.enrollmentDone.hidden = false;
}, },
...@@ -288,13 +342,13 @@ Polymer({ ...@@ -288,13 +342,13 @@ Polymer({
this.drawBackgroundCircle(); this.drawBackgroundCircle();
this.$.enrollmentDone.hidden = true; this.$.enrollmentDone.hidden = true;
this.$.scanningAnimation.singleLoop = false; const scanningAnimation =
this.$.scanningAnimation.classList.add('translucent'); /** @type {CrLottieElement|HTMLElement} */ (this.$.scanningAnimation);
this.$.scanningAnimation.animationUrl = scanningAnimation.singleLoop = false;
'chrome://theme/IDR_FINGERPRINT_ICON_ANIMATION'; scanningAnimation.classList.add('translucent');
this.resizeAndCenterIcon_( this.updateAnimationAsset_();
/** @type {!HTMLElement} */ (this.$.scanningAnimation)); this.resizeAndCenterIcon_(scanningAnimation);
this.$.scanningAnimation.hidden = false; scanningAnimation.hidden = false;
}, },
/** /**
......
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