Skip to content

Commit 22d48e4

Browse files
authored
feat: enforce hex color digit counts in css parser (#5176)
1 parent d8721e5 commit 22d48e4

10 files changed

Lines changed: 99 additions & 73 deletions

File tree

packages/model-viewer-effects/src/test/effect-composer-spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414
*/
1515

1616
import {ModelViewerElement} from '@google/model-viewer';
17+
import {Renderer} from '@google/model-viewer/lib/three-components/Renderer.js';
1718
import {expect} from 'chai';
1819
import {DotScreenEffect, Effect, EffectPass, GridEffect} from 'postprocessing';
1920
import {Camera} from 'three';
2021

21-
import {Renderer} from '@google/model-viewer/lib/three-components/Renderer.js';
22-
2322
import {$effectComposer, $normalPass, $renderPass, $scene} from '../effect-composer.js';
2423
import {EffectComposer} from '../model-viewer-effects.js';
2524

packages/model-viewer-effects/src/test/effects/color-grade-spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414
*/
1515

1616
import {ModelViewerElement} from '@google/model-viewer';
17-
import {expect} from 'chai';
18-
1917
import {Renderer} from '@google/model-viewer/lib/three-components/Renderer.js';
18+
import {expect} from 'chai';
2019

2120
import {ColorGradeEffect, EffectComposer} from '../../model-viewer-effects.js';
2221
import {ArraysAreEqual, assetPath, AverageHSL, CompareArrays, createModelViewerElement, rafPasses, screenshot, waitForEvent} from '../utilities.js';

packages/model-viewer/src/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export const IS_FIREFOX = /firefox/i.test(navigator.userAgent);
6767
export const IS_OCULUS = /OculusBrowser/.test(navigator.userAgent);
6868
export const IS_IOS_CHROME = IS_IOS && /CriOS\//.test(navigator.userAgent);
6969
export const IS_IOS_SAFARI = IS_IOS && IS_SAFARI;
70-
export const IS_IOS_THIRDPARTY = IS_IOS && /CriOS\/|EdgiOS\/|FxiOS\/|GSA\/|DuckDuckGo\//.test(navigator.userAgent);
70+
export const IS_IOS_THIRDPARTY = IS_IOS &&
71+
/CriOS\/|EdgiOS\/|FxiOS\/|GSA\/|DuckDuckGo\//.test(navigator.userAgent);
7172
export const IS_IOS_GSA = IS_IOS && /GSA\//.test(navigator.userAgent);
7273

7374
export const IS_SCENEVIEWER_CANDIDATE = IS_ANDROID && !IS_FIREFOX && !IS_OCULUS;

packages/model-viewer/src/features/loading.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,9 @@ export const LoadingMixin = <T extends Constructor<ModelViewerElementBase>>(
205205
*
206206
* The default value is "auto". The only supported alternative values are
207207
* "lazy" and "eager". Auto is equivalent to lazy, which loads the model
208-
* when it is near the viewport for reveal = "auto", and when dismissPoster()
209-
* is called for reveal = "manual". Eager loads the model immediately.
208+
* when it is near the viewport for reveal = "auto", and when
209+
* dismissPoster() is called for reveal = "manual". Eager loads the model
210+
* immediately.
210211
*/
211212
@property({type: String})
212213
loading: LoadingAttributeValue = LoadingStrategy.AUTO;

packages/model-viewer/src/styles/parsers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ const parseNumber = (() => {
258258
* Parses a hexadecimal-encoded color in 3, 6 or 8 digit form.
259259
*/
260260
const parseHex = (() => {
261-
// TODO(cdata): right now we don't actually enforce the number of digits
262-
const HEX_RE = /^[a-f0-9]*/i;
261+
const HEX_RE =
262+
/^([a-f0-9]{8}|[a-f0-9]{6}|[a-f0-9]{4}|[a-f0-9]{3})(?![a-f0-9])/i;
263263

264264
return (inputString: string): ParseResult<HexNode> => {
265265
inputString = inputString.slice(1).trim();

packages/model-viewer/src/test/styles/parsers-spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,25 @@ suite('parsers', () => {
5050
test('parses hex colors', () => {
5151
expect(parseExpressions('#fff')).to.be.eql([expressionNode(
5252
[hexNode('fff')])]);
53+
expect(parseExpressions('#abcd')).to.be.eql([expressionNode(
54+
[hexNode('abcd')])]);
5355
expect(parseExpressions('#abc123')).to.be.eql([expressionNode(
5456
[hexNode('abc123')])]);
5557
expect(parseExpressions('#daf012ee')).to.be.eql([expressionNode(
5658
[hexNode('daf012ee')])]);
59+
60+
// Invalid hex digit counts should fail to parse
61+
expect(parseExpressions('#')).to.be.eql([]);
62+
expect(parseExpressions('#1')).to.be.eql([]);
63+
expect(parseExpressions('#12')).to.be.eql([]);
64+
expect(parseExpressions('#12345')).to.be.eql([]);
65+
expect(parseExpressions('#1234567')).to.be.eql([]);
66+
expect(parseExpressions('#123456789')).to.be.eql([]);
67+
expect(parseExpressions('#123456fg')).to.be.eql([]);
68+
69+
// Correctly parses valid hex followed by non-hex characters
70+
expect(parseExpressions('#123456g')).to.be.eql([expressionNode(
71+
[hexNode('123456'), identNode('g')])]);
5772
});
5873

5974
test('parses functions', () => {

packages/model-viewer/src/test/three-components/ARRenderer-spec.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -241,43 +241,43 @@ suite('ARRenderer', () => {
241241
expect(scale.z).to.be.equal(1);
242242
});
243243

244-
test('prevents default WebXR select when beforexrselect is triggered on interactive elements', () => {
245-
const button = document.createElement('button');
246-
const event = new CustomEvent('beforexrselect', {
247-
bubbles: true,
248-
cancelable: true
249-
});
250-
Object.defineProperty(event, 'composedPath', {
251-
value: () => [button, (arRenderer as any).overlay]
252-
});
253-
254-
let defaultPrevented = false;
255-
event.preventDefault = () => {
256-
defaultPrevented = true;
257-
};
258-
259-
(arRenderer as any).overlay.dispatchEvent(event);
260-
expect(defaultPrevented).to.be.equal(true);
261-
});
262-
263-
test('does not prevent default WebXR select on non-interactive elements', () => {
264-
const div = document.createElement('div');
265-
const event = new CustomEvent('beforexrselect', {
266-
bubbles: true,
267-
cancelable: true
268-
});
269-
Object.defineProperty(event, 'composedPath', {
270-
value: () => [div, (arRenderer as any).overlay]
271-
});
272-
273-
let defaultPrevented = false;
274-
event.preventDefault = () => {
275-
defaultPrevented = true;
276-
};
277-
278-
(arRenderer as any).overlay.dispatchEvent(event);
279-
expect(defaultPrevented).to.be.equal(false);
280-
});
244+
test(
245+
'prevents default WebXR select when beforexrselect is triggered on interactive elements',
246+
() => {
247+
const button = document.createElement('button');
248+
const event = new CustomEvent(
249+
'beforexrselect', {bubbles: true, cancelable: true});
250+
Object.defineProperty(event, 'composedPath', {
251+
value: () => [button, (arRenderer as any).overlay]
252+
});
253+
254+
let defaultPrevented = false;
255+
event.preventDefault = () => {
256+
defaultPrevented = true;
257+
};
258+
259+
(arRenderer as any).overlay.dispatchEvent(event);
260+
expect(defaultPrevented).to.be.equal(true);
261+
});
262+
263+
test(
264+
'does not prevent default WebXR select on non-interactive elements',
265+
() => {
266+
const div = document.createElement('div');
267+
const event = new CustomEvent(
268+
'beforexrselect', {bubbles: true, cancelable: true});
269+
Object.defineProperty(event, 'composedPath', {
270+
value: () => [div, (arRenderer as any).overlay]
271+
});
272+
273+
let defaultPrevented = false;
274+
event.preventDefault = () => {
275+
defaultPrevented = true;
276+
};
277+
278+
(arRenderer as any).overlay.dispatchEvent(event);
279+
expect(defaultPrevented).to.be.equal(false);
280+
});
281281

282282
suite('presentation ends', () => {
283283
setup(async () => {

packages/model-viewer/src/test/three-components/TextureUtils-spec.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,27 @@ suite('TextureUtils', () => {
7373
expect(texture.name).to.be.eq(EQUI_URL);
7474
expect(texture.mapping).to.be.eq(EquirectangularReflectionMapping);
7575
});
76-
test('decodes a gainmap and disposes intermediate render targets', async () => {
77-
const GAINMAP_URL = assetPath('environments/spruit_sunrise_1k_HDR.jpg');
78-
const THREE = await import('three');
79-
let disposeCount = 0;
80-
const originalDispose = THREE.WebGLRenderTarget.prototype.dispose;
81-
THREE.WebGLRenderTarget.prototype.dispose = function() {
82-
disposeCount++;
83-
return originalDispose.call(this);
84-
};
85-
86-
try {
87-
const texture = await textureUtils.loadEquirect(GAINMAP_URL);
88-
texture.dispose();
89-
expect(disposeCount).to.be.greaterThan(0);
90-
} finally {
91-
THREE.WebGLRenderTarget.prototype.dispose = originalDispose;
92-
}
93-
});
76+
test(
77+
'decodes a gainmap and disposes intermediate render targets',
78+
async () => {
79+
const GAINMAP_URL =
80+
assetPath('environments/spruit_sunrise_1k_HDR.jpg');
81+
const THREE = await import('three');
82+
let disposeCount = 0;
83+
const originalDispose = THREE.WebGLRenderTarget.prototype.dispose;
84+
THREE.WebGLRenderTarget.prototype.dispose = function() {
85+
disposeCount++;
86+
return originalDispose.call(this);
87+
};
88+
89+
try {
90+
const texture = await textureUtils.loadEquirect(GAINMAP_URL);
91+
texture.dispose();
92+
expect(disposeCount).to.be.greaterThan(0);
93+
} finally {
94+
THREE.WebGLRenderTarget.prototype.dispose = originalDispose;
95+
}
96+
});
9497
test('loads a valid KTX2 texture from URL', async () => {
9598
let texture = await textureUtils.loadImage(KTX2_URL, false);
9699
texture.dispose();

packages/model-viewer/src/three-components/ARRenderer.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,8 @@ export class ARRenderer extends EventDispatcher<
459459
this.selectedXRController.userData.line.visible = false;
460460
if (scene.canScale && this.isWorldSpaceReady()) {
461461
this.isTwoHandInteraction = true;
462-
this.firstRatio = this.controllerSeparation() / scene.scenePivot.scale.x;
462+
this.firstRatio =
463+
this.controllerSeparation() / scene.scenePivot.scale.x;
463464
this.scaleLine.visible = true;
464465
}
465466
} else {
@@ -478,7 +479,8 @@ export class ARRenderer extends EventDispatcher<
478479
this.xrController2?.userData.isSelected) {
479480
if (scene.canScale && this.isWorldSpaceReady()) {
480481
this.isTwoHandInteraction = true;
481-
this.firstRatio = this.controllerSeparation() / scene.scenePivot.scale.x;
482+
this.firstRatio =
483+
this.controllerSeparation() / scene.scenePivot.scale.x;
482484
this.scaleLine.visible = true;
483485
}
484486
} else {
@@ -516,7 +518,8 @@ export class ARRenderer extends EventDispatcher<
516518
scene.attach(scene.scenePivot);
517519
this.selectedXRController = null;
518520
this.goalYaw = Math.atan2(
519-
scene.scenePivot.matrix.elements[8], scene.scenePivot.matrix.elements[10]);
521+
scene.scenePivot.matrix.elements[8],
522+
scene.scenePivot.matrix.elements[10]);
520523
this.goalPosition.x = scene.scenePivot.position.x;
521524
this.goalPosition.z = scene.scenePivot.position.z;
522525

@@ -957,8 +960,8 @@ export class ARRenderer extends EventDispatcher<
957960
if (element instanceof HTMLElement) {
958961
const tagName = element.tagName.toLowerCase();
959962
if (tagName === 'input' || tagName === 'button' ||
960-
tagName === 'select' || tagName === 'textarea' ||
961-
tagName === 'a' || element.hasAttribute('data-pointer-coalesce') ||
963+
tagName === 'select' || tagName === 'textarea' || tagName === 'a' ||
964+
element.hasAttribute('data-pointer-coalesce') ||
962965
element.classList.contains('interactive')) {
963966
event.preventDefault();
964967
break;
@@ -1093,14 +1096,16 @@ export class ARRenderer extends EventDispatcher<
10931096
}
10941097
}
10951098

1096-
private applyXRControllerRotation(controller: XRController, scenePivot: Object3D) {
1099+
private applyXRControllerRotation(
1100+
controller: XRController, scenePivot: Object3D) {
10971101
if (!controller.userData.turning) {
10981102
return;
10991103
}
11001104
const angle = (controller.position.x - controller.userData.initialX) *
11011105
ROTATION_SENSIVITY;
11021106
this.deltaRotation.setFromAxisAngle(AXIS_Y, angle);
1103-
scenePivot.quaternion.multiplyQuaternions(this.deltaRotation, scenePivot.quaternion);
1107+
scenePivot.quaternion.multiplyQuaternions(
1108+
this.deltaRotation, scenePivot.quaternion);
11041109
}
11051110

11061111
private handleScalingInXR(scene: ModelScene, delta: number) {

packages/model-viewer/src/three-components/TextureUtils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,15 @@ export default class TextureUtils {
157157
texture = dataTexture;
158158
result.dispose(true);
159159
} else {
160-
console.warn('Decoded DataTexture is completely black, falling back to render target texture.');
160+
console.warn(
161+
'Decoded DataTexture is completely black, falling back to render target texture.');
161162
texture = renderTarget.texture;
162163
result.dispose(false);
163164
}
164165
} catch (e) {
165-
console.warn('Failed to convert gainmap to DataTexture, falling back to render target texture:', e);
166+
console.warn(
167+
'Failed to convert gainmap to DataTexture, falling back to render target texture:',
168+
e);
166169
texture = renderTarget.texture;
167170
result.dispose(false);
168171
}

0 commit comments

Comments
 (0)