226 lines
9.7 KiB
TypeScript
226 lines
9.7 KiB
TypeScript
import {DEFAULT_THEME} from '../../../src/defaults';
|
|
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
|
import {resetChromeRuntimeMessageStub, stubBackgroundFetchResponse, stubChromeRuntimeMessage} from '../support/background-stub';
|
|
import {getCSSEchoURL} from '../support/echo-client';
|
|
import {multiline, timeout, waitForEvent} from '../support/test-utils';
|
|
|
|
const theme = {
|
|
...DEFAULT_THEME,
|
|
darkSchemeBackgroundColor: 'black',
|
|
darkSchemeTextColor: 'white',
|
|
};
|
|
let container: HTMLElement;
|
|
const links: HTMLLinkElement[] = [];
|
|
|
|
function createStyleLink(href: string | null) {
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.classList.add('testcase--link');
|
|
if (href) {
|
|
link.href = href;
|
|
}
|
|
document.head.append(link);
|
|
links.push(link);
|
|
return link;
|
|
}
|
|
|
|
function selectTestStyleLink() {
|
|
return document.querySelector('.testcase--link')! as HTMLLinkElement;
|
|
}
|
|
|
|
function createCorsLink(content: string) {
|
|
const url = getCSSEchoURL(content);
|
|
stubBackgroundFetchResponse(url, content);
|
|
return createStyleLink(url);
|
|
}
|
|
|
|
async function waitForLinkLoading(link: HTMLLinkElement) {
|
|
return new Promise((resolve, reject) => {
|
|
link.addEventListener('load', resolve, {once: true});
|
|
link.addEventListener('error', reject, {once: true});
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
container = document.body;
|
|
container.innerHTML = '';
|
|
stubChromeRuntimeMessage();
|
|
});
|
|
|
|
afterEach(() => {
|
|
removeDynamicTheme();
|
|
container.innerHTML = '';
|
|
links.forEach((l) => l.remove());
|
|
links.splice(0);
|
|
resetChromeRuntimeMessageStub();
|
|
});
|
|
|
|
describe('LINK STYLES', () => {
|
|
it('should override same-origin link', async () => {
|
|
createStyleLink(`data:text/css;utf8,${encodeURIComponent(multiline(
|
|
'h1 { background: gray; }',
|
|
'h1 strong { color: red; }',
|
|
))}`);
|
|
container.innerHTML = multiline(
|
|
'<h1>Link <strong>override</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(50);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should override cross-origin link', async () => {
|
|
createCorsLink(multiline(
|
|
'h1 { background: gray; }',
|
|
'h1 strong { color: red; }',
|
|
));
|
|
container.innerHTML = multiline(
|
|
'<h1><strong>Cross-origin</strong> link override</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should override cross-origin imports in linked CSS', async () => {
|
|
const importedCSS = 'h1 { background: gray; }';
|
|
const importedURL = getCSSEchoURL(importedCSS);
|
|
stubBackgroundFetchResponse(importedURL, importedCSS);
|
|
createCorsLink(multiline(
|
|
`@import "${importedURL}";`,
|
|
'h1 strong { color: red; }',
|
|
));
|
|
container.innerHTML = multiline(
|
|
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should override cross-origin imports in linked CSS with capital @import', async () => {
|
|
const importedCSS = 'h1 { background: gray; }';
|
|
const importedURL = getCSSEchoURL(importedCSS);
|
|
stubBackgroundFetchResponse(importedURL, importedCSS);
|
|
createCorsLink(multiline(
|
|
`@IMPORT "${importedURL}";`,
|
|
'h1 strong { color: red; }',
|
|
));
|
|
container.innerHTML = multiline(
|
|
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should override cross-origin link that has already been loaded', async () => {
|
|
createCorsLink(multiline(
|
|
'h1 { background: gray; }',
|
|
'h1 strong { color: red; }',
|
|
));
|
|
container.innerHTML = multiline(
|
|
'<h1>Loaded <strong>cross-origin</strong> link override</h1>',
|
|
);
|
|
|
|
await waitForLinkLoading(selectTestStyleLink());
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(128, 128, 128)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 0, 0)');
|
|
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should remove manager from disabled link', async () => {
|
|
const link = createStyleLink(`data:text/css;utf8,${encodeURIComponent(multiline(
|
|
'h1 { background: gray; }',
|
|
'h1 strong { color: red; }',
|
|
))}`);
|
|
container.innerHTML = multiline(
|
|
'<h1>Link <strong>disabled</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(50);
|
|
expect(selectTestStyleLink().nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
|
link.disabled = true;
|
|
await timeout(0);
|
|
expect(selectTestStyleLink().nextElementSibling!.classList.contains('darkreader--sync')).toBe(false);
|
|
});
|
|
it("Shouldn't wait on link that won't be loaded", async () => {
|
|
const link = createStyleLink(null);
|
|
link.setAttribute('data-href', `data:text/css;utf8,${encodeURIComponent(multiline(
|
|
'h1 { background: green !important; }',
|
|
'h1 strong { color: orange !important; }',
|
|
))}`);
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 { background: gray; }',
|
|
'</style>',
|
|
'<h1>Link <strong>loading with non-href attribute</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(50);
|
|
const h1 = document.querySelector('h1')!;
|
|
expect(getComputedStyle(h1).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(document.querySelector('.darkreader--fallback')!.textContent).toBe('');
|
|
});
|
|
|
|
it('should handle styles with @import "..." screen;', async () => {
|
|
const importedCSS = 'h1 { background: gray; }';
|
|
const importedURL = getCSSEchoURL(importedCSS);
|
|
stubBackgroundFetchResponse(importedURL, importedCSS);
|
|
createCorsLink(multiline(
|
|
`@import "${importedURL}" screen;`,
|
|
'h1 strong { color: red; }',
|
|
));
|
|
container.innerHTML = multiline(
|
|
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should handle styles with invalid url(...)', async () => {
|
|
const importedCSS = 'h1 { background-image: url("freecookies:3https://example.com"); background-color: gray; }';
|
|
const importedURL = getCSSEchoURL(importedCSS);
|
|
stubBackgroundFetchResponse(importedURL, importedCSS);
|
|
stubBackgroundFetchResponse('freecookies:3https://example.com', '');
|
|
createCorsLink(multiline(
|
|
`@import "${importedURL}" screen;`,
|
|
'h1 strong { color: red; }',
|
|
));
|
|
container.innerHTML = multiline(
|
|
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
|
await timeout(1000);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
});
|
|
|