From b0e15228556b3ab7ef521c4076d1f27cbce4929b Mon Sep 17 00:00:00 2001
From: Wattachai Kanawitoon <117723407+wattachai-lseg@users.noreply.github.com>
Date: Tue, 11 Aug 2026 15:37:11 +0700
Subject: [PATCH 1/3] fix(flag): resolve empty value returned by
getComputedStyle on Chromium 151
---
.../src/flag/__test__/flag.cdn-prefix.test.js | 57 +++++++++++++++++++
packages/elements/src/flag/const.ts | 4 ++
packages/elements/src/flag/index.ts | 19 +++++--
3 files changed, 74 insertions(+), 6 deletions(-)
create mode 100644 packages/elements/src/flag/__test__/flag.cdn-prefix.test.js
create mode 100644 packages/elements/src/flag/const.ts
diff --git a/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js b/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js
new file mode 100644
index 000000000..b00842cf0
--- /dev/null
+++ b/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js
@@ -0,0 +1,57 @@
+import sinon from 'sinon';
+
+// import element and theme
+import '@refinitiv-ui/elements/flag';
+
+import '@refinitiv-ui/halo-theme/light/ef-flag.js';
+import { expect } from '@refinitiv-ui/test-helpers';
+
+import {
+ checkRequestedUrl,
+ createAndWaitForLoad,
+ createFakeResponse,
+ flagName,
+ gbSvg,
+ generateUniqueName,
+ isEqualSvg,
+ responseConfigSuccess
+} from './helpers/helpers.js';
+
+// Must match DefaultStyle.CDN_PREFIX in src/flag/const.ts
+const DEFAULT_CDN_PREFIX = 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/flags/';
+
+// CDN prefix is resolved once per page by the FlagLoader singleton, so this case needs its own test file
+describe('flag/cdn-prefix', function () {
+ let fetch;
+ beforeEach(function () {
+ fetch = sinon.stub(window, 'fetch');
+ });
+ afterEach(function () {
+ window.fetch.restore(); // remove stub
+ });
+
+ it('Should fall back to the hardcoded CDN prefix when --cdn-prefix resolves to an empty value', async function () {
+ createFakeResponse(gbSvg, responseConfigSuccess);
+ const uniqueFlagName = generateUniqueName(flagName); // to avoid caching
+ // `initial` makes the custom property guaranteed-invalid, so getComputedStyle returns an empty value
+ const el = await createAndWaitForLoad(
+ ``
+ );
+ const expectedSrc = `${DEFAULT_CDN_PREFIX}${uniqueFlagName}.svg`;
+
+ expect(el.getComputedVariable('--cdn-prefix')).to.equal(
+ '',
+ 'CSS variable should resolve to an empty value'
+ );
+ expect(fetch.callCount).to.equal(1, 'Should make one request');
+ expect(checkRequestedUrl(fetch.args, expectedSrc)).to.equal(
+ true,
+ `Requested URL should use the fallback prefix: ${expectedSrc}`
+ );
+
+ const svg = el.shadowRoot.querySelector('svg');
+
+ expect(svg).to.not.equal(null, 'SVG element should exist when falling back to the default prefix');
+ expect(isEqualSvg(svg.outerHTML, gbSvg)).to.equal(true, 'Should render SVG, from the mock response');
+ });
+});
diff --git a/packages/elements/src/flag/const.ts b/packages/elements/src/flag/const.ts
new file mode 100644
index 000000000..3b6457b08
--- /dev/null
+++ b/packages/elements/src/flag/const.ts
@@ -0,0 +1,4 @@
+// Default to Halo theme as Elemental and Solar theme are deprecated.
+export enum DefaultStyle {
+ CDN_PREFIX = 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/flags/'
+}
diff --git a/packages/elements/src/flag/index.ts b/packages/elements/src/flag/index.ts
index 5c814bbbe..abbd4e645 100644
--- a/packages/elements/src/flag/index.ts
+++ b/packages/elements/src/flag/index.ts
@@ -12,6 +12,7 @@ import { property } from '@refinitiv-ui/core/decorators/property.js';
import { unsafeHTML } from '@refinitiv-ui/core/directives/unsafe-html.js';
import { VERSION } from '../version.js';
+import { DefaultStyle } from './const.js';
import { FlagLoader } from './utils/FlagLoader.js';
export { preload } from './utils/FlagLoader.js';
@@ -131,11 +132,17 @@ export class Flag extends BasicElement {
*/
protected override firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
- /**
- * We have to call this here because
- * polyfilled browsers only get variables at this point.
- */
- this.setPrefix();
+ // Chromium only issue: starting from version 151,
+ // when Flag is slotted into an unregistered custom element,
+ // getComputedStyle() will always return empty string as a style value.
+ // It needs to wait for the registration of the parent custom element first.
+ // In practice, this happens when Flag class and its style are imported before the parent's ones as following:
+ // import '@refinitiv-ui/elements/flag';
+ // import '@refinitiv-ui/elements/panel';
+
+ // import '@refinitiv-ui/elements/flag/themes/halo/dark';
+ // import '@refinitiv-ui/elements/panel/themes/halo/dark';
+ setTimeout(() => this.setPrefix());
}
/**
@@ -167,7 +174,7 @@ export class Flag extends BasicElement {
*/
private setPrefix(): void {
if (!FlagLoader.isPrefixSet) {
- const CDNPrefix = this.getComputedVariable('--cdn-prefix').replace(/^('|")|('|")$/g, '');
+ const CDNPrefix = this.getComputedVariable('--cdn-prefix', DefaultStyle.CDN_PREFIX);
FlagLoader.setCdnPrefix(CDNPrefix);
}
From 31cc20983197f83c48eb44dedf7a3b631f34104e Mon Sep 17 00:00:00 2001
From: Wattachai Kanawitoon <117723407+wattachai-lseg@users.noreply.github.com>
Date: Tue, 11 Aug 2026 15:40:10 +0700
Subject: [PATCH 2/3] fix(icon): resolve empty value returned by
getComputedStyle on Chromium 151
---
.../__test__/icon.cdn-prefix-fallback.test.js | 63 +++++++++++++++++++
packages/elements/src/icon/const.ts | 5 ++
packages/elements/src/icon/index.ts | 27 +++++---
3 files changed, 86 insertions(+), 9 deletions(-)
create mode 100644 packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js
create mode 100644 packages/elements/src/icon/const.ts
diff --git a/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js b/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js
new file mode 100644
index 000000000..177efa9e2
--- /dev/null
+++ b/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js
@@ -0,0 +1,63 @@
+import sinon from 'sinon';
+
+// import element and theme
+import '@refinitiv-ui/elements/configuration';
+import '@refinitiv-ui/elements/icon';
+
+import '@refinitiv-ui/halo-theme/light/ef-icon.js';
+import { expect } from '@refinitiv-ui/test-helpers';
+
+import {
+ checkRequestedUrl,
+ createAndWaitForLoad,
+ createFakeResponse,
+ iconName,
+ isEqualSvg,
+ responseConfigSuccess,
+ tickSvgSprite
+} from './helpers/helpers.js';
+
+// Must match DefaultStyle.CDN_SPRITE_PREFIX in src/icon/const.ts
+const DEFAULT_CDN_SPRITE_PREFIX =
+ 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/sprites/icons.svg';
+
+// CDN prefixes are resolved once per page by the loader singletons, so this case needs its own test file
+describe('icon/cdn-prefix-fallback', function () {
+ let fetch;
+ beforeEach(function () {
+ fetch = sinon.stub(window, 'fetch');
+ });
+ afterEach(function () {
+ window.fetch.restore(); // remove stub
+ });
+
+ it('Should fall back to the sprite when CDN prefixes resolve to empty values', async function () {
+ createFakeResponse(tickSvgSprite, responseConfigSuccess);
+ // `initial` makes the custom properties guaranteed-invalid, so getComputedStyle returns empty values
+ const el = await createAndWaitForLoad(
+ ``
+ );
+
+ expect(el.getComputedVariable('--cdn-prefix')).to.equal(
+ '',
+ 'CSS variable should resolve to an empty value'
+ );
+ expect(el.getComputedVariable('--cdn-sprite-prefix')).to.equal(
+ '',
+ 'CSS variable should resolve to an empty value'
+ );
+ expect(fetch.callCount).to.equal(1, 'Should make one request');
+ expect(checkRequestedUrl(fetch.args, DEFAULT_CDN_SPRITE_PREFIX)).to.equal(
+ true,
+ `Requested URL should use the fallback sprite prefix: ${DEFAULT_CDN_SPRITE_PREFIX}`
+ );
+
+ const svg = el.shadowRoot.querySelector('svg');
+
+ expect(svg).to.not.equal(null, 'SVG element should exist when falling back to the sprite');
+ expect(isEqualSvg(svg.outerHTML, tickSvgSprite)).to.equal(
+ true,
+ 'Should render SVG, from the mock response'
+ );
+ });
+});
diff --git a/packages/elements/src/icon/const.ts b/packages/elements/src/icon/const.ts
new file mode 100644
index 000000000..98b52cc02
--- /dev/null
+++ b/packages/elements/src/icon/const.ts
@@ -0,0 +1,5 @@
+// Default to Halo theme as Elemental and Solar theme are deprecated.
+export enum DefaultStyle {
+ CDN_PREFIX = '',
+ CDN_SPRITE_PREFIX = 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/sprites/icons.svg'
+}
diff --git a/packages/elements/src/icon/index.ts b/packages/elements/src/icon/index.ts
index 66f1731d5..3255e5565 100644
--- a/packages/elements/src/icon/index.ts
+++ b/packages/elements/src/icon/index.ts
@@ -20,6 +20,7 @@ import { Deferred, isBase64svg, isUrl } from '@refinitiv-ui/utils/loader.js';
import { efConfig } from '../configuration/index.js';
import type { Config } from '../configuration/index.js';
import { VERSION } from '../version.js';
+import { DefaultStyle } from './const.js';
import { IconLoader } from './utils/IconLoader.js';
import { SpriteLoader } from './utils/SpriteLoader.js';
@@ -94,7 +95,10 @@ export class Icon extends BasicElement {
if (oldValue !== value) {
this.deferIconReady();
this._icon = value;
- requestAnimationFrame(() => this.updateRenderer());
+ // Wait for setPrefix() resolving both sprite & icon CDN prefix value before updating the renderer
+ void Promise.all([SpriteLoader.getCdnPrefix(), IconLoader.getCdnPrefix()]).then(() => {
+ this.updateRenderer();
+ });
this.requestUpdate('icon', oldValue);
}
}
@@ -180,12 +184,17 @@ export class Icon extends BasicElement {
*/
protected override firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
-
- /**
- * We have to call this here because
- * polyfilled browsers only get variables at this point.
- */
- this.setPrefix();
+ // Chromium only issue: starting from version 151,
+ // when Icon is slotted into an unregistered custom element,
+ // getComputedStyle() will always return empty string as a style value.
+ // It needs to wait for the registration of the parent custom element first.
+ // In practice, this happens when Icon class and its style are imported before the parent's ones as following:
+ // import '@refinitiv-ui/elements/icon';
+ // import '@refinitiv-ui/elements/panel';
+
+ // import '@refinitiv-ui/elements/icon/themes/halo/dark';
+ // import '@refinitiv-ui/elements/panel/themes/halo/dark';
+ setTimeout(() => this.setPrefix());
}
protected override async getUpdateComplete(): Promise {
@@ -284,12 +293,12 @@ export class Icon extends BasicElement {
private setPrefix(): void {
// This prefix for individual icons allows supporting custom prefix of self-managed icons.
if (IconLoader.isPrefixPending) {
- const CDNPrefix = this.getComputedVariable('--cdn-prefix');
+ const CDNPrefix = this.getComputedVariable('--cdn-prefix', DefaultStyle.CDN_PREFIX);
IconLoader.setCdnPrefix(CDNPrefix);
}
if (SpriteLoader.isPrefixPending) {
- const CDNSpritePrefix = this.getComputedVariable('--cdn-sprite-prefix');
+ const CDNSpritePrefix = this.getComputedVariable('--cdn-sprite-prefix', DefaultStyle.CDN_SPRITE_PREFIX);
SpriteLoader.setCdnPrefix(CDNSpritePrefix);
}
}
From d1810a972b00e9ac3080818bdd3c45a05234e96f Mon Sep 17 00:00:00 2001
From: Wattachai Kanawitoon <117723407+wattachai-lseg@users.noreply.github.com>
Date: Tue, 11 Aug 2026 16:29:44 +0700
Subject: [PATCH 3/3] test(flag, icon): reset load state to support all
elements testing
---
.../src/flag/__test__/flag.cdn-prefix.test.js | 13 ++++++++++---
packages/elements/src/flag/index.ts | 2 ++
.../__test__/icon.cdn-prefix-fallback.test.js | 19 ++++++++++++++-----
3 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js b/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js
index b00842cf0..5fee40f07 100644
--- a/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js
+++ b/packages/elements/src/flag/__test__/flag.cdn-prefix.test.js
@@ -2,9 +2,10 @@ import sinon from 'sinon';
// import element and theme
import '@refinitiv-ui/elements/flag';
+import { FlagLoader } from '@refinitiv-ui/elements/flag';
import '@refinitiv-ui/halo-theme/light/ef-flag.js';
-import { expect } from '@refinitiv-ui/test-helpers';
+import { expect, nextFrame } from '@refinitiv-ui/test-helpers';
import {
checkRequestedUrl,
@@ -23,11 +24,17 @@ const DEFAULT_CDN_PREFIX = 'https://cdn.refinitiv.net/public/libs/elf/assets/elf
// CDN prefix is resolved once per page by the FlagLoader singleton, so this case needs its own test file
describe('flag/cdn-prefix', function () {
let fetch;
- beforeEach(function () {
+ const resetLoaders = async () => {
+ FlagLoader.reset();
+ await nextFrame(5);
+ };
+ beforeEach(async function () {
+ await resetLoaders();
fetch = sinon.stub(window, 'fetch');
});
- afterEach(function () {
+ afterEach(async function () {
window.fetch.restore(); // remove stub
+ await resetLoaders();
});
it('Should fall back to the hardcoded CDN prefix when --cdn-prefix resolves to an empty value', async function () {
diff --git a/packages/elements/src/flag/index.ts b/packages/elements/src/flag/index.ts
index abbd4e645..91eaaae95 100644
--- a/packages/elements/src/flag/index.ts
+++ b/packages/elements/src/flag/index.ts
@@ -17,6 +17,8 @@ import { FlagLoader } from './utils/FlagLoader.js';
export { preload } from './utils/FlagLoader.js';
+export { FlagLoader } from './utils/FlagLoader.js';
+
const EmptyTemplate = svg``;
@customElement('ef-flag')
diff --git a/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js b/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js
index 177efa9e2..17961d2b4 100644
--- a/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js
+++ b/packages/elements/src/icon/__test__/icon.cdn-prefix-fallback.test.js
@@ -2,10 +2,10 @@ import sinon from 'sinon';
// import element and theme
import '@refinitiv-ui/elements/configuration';
-import '@refinitiv-ui/elements/icon';
+import { IconLoader, SpriteLoader, iconTemplateCache } from '@refinitiv-ui/elements/icon';
import '@refinitiv-ui/halo-theme/light/ef-icon.js';
-import { expect } from '@refinitiv-ui/test-helpers';
+import { expect, nextFrame } from '@refinitiv-ui/test-helpers';
import {
checkRequestedUrl,
@@ -21,14 +21,23 @@ import {
const DEFAULT_CDN_SPRITE_PREFIX =
'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/sprites/icons.svg';
-// CDN prefixes are resolved once per page by the loader singletons, so this case needs its own test file
+// CDN prefixes and the sprite are resolved once per page by the loader singletons,
+// so they must be reset to make this case independent of other tests sharing the same page
describe('icon/cdn-prefix-fallback', function () {
let fetch;
- beforeEach(function () {
+ const resetLoaders = async () => {
+ IconLoader.reset();
+ SpriteLoader.reset();
+ iconTemplateCache.clear();
+ await nextFrame(5);
+ };
+ beforeEach(async function () {
+ await resetLoaders();
fetch = sinon.stub(window, 'fetch');
});
- afterEach(function () {
+ afterEach(async function () {
window.fetch.restore(); // remove stub
+ await resetLoaders();
});
it('Should fall back to the sprite when CDN prefixes resolve to empty values', async function () {