Compare commits

...

10 Commits

17 changed files with 220 additions and 75 deletions

View File

@ -113,6 +113,10 @@ jobs:
name: iOS Simulator Safari 14
targetBrowser: Safari_IOS_14
xcode: /Applications/Xcode_12_beta.app
- os: macos-11
name: iOS Simulator Safari 15
targetBrowser: Safari_IOS_15
xcode: /Applications/Xcode_13.0.app
- os: windows-latest
name: Windows Internet Explorer 9 (Emulated)
targetBrowser: IE_9

View File

@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.2.1](https://github.com/niklasvh/html2canvas/compare/v1.2.0...v1.2.1) (2021-08-05)
### fix
* none image (#2627) ([6651fc6](https://github.com/niklasvh/html2canvas/commit/6651fc6789d5902d171dc53b4094887870433018)), closes [#2627](https://github.com/niklasvh/html2canvas/issues/2627)
* type import that is only available ts 3.8 or higher (#2629) ([c5c6fa0](https://github.com/niklasvh/html2canvas/commit/c5c6fa00d71f36ef963ba5170ebc7b668d39c407)), closes [#2629](https://github.com/niklasvh/html2canvas/issues/2629)
# [1.2.0](https://github.com/niklasvh/html2canvas/compare/v1.1.5...v1.2.0) (2021-08-04)

View File

@ -42,6 +42,12 @@ module.exports = function(config) {
platform: 'iOS',
sdk: '14.0'
},
Safari_IOS_15: {
base: 'MobileSafari',
name: 'iPhone 8',
platform: 'iOS',
sdk: '15.0'
},
SauceLabs_IE9: {
base: 'SauceLabs',
browserName: 'internet explorer',

68
package-lock.json generated
View File

@ -1,14 +1,15 @@
{
"name": "html2canvas",
"version": "1.2.0",
"version": "1.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.1.5",
"version": "1.2.1",
"license": "MIT",
"dependencies": {
"css-line-break": "2.0.1"
"css-line-break": "2.0.1",
"text-segmentation": "^1.0.2"
},
"devDependencies": {
"@babel/cli": "^7.4.3",
@ -36,7 +37,7 @@
"babel-loader": "^8.0.5",
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-dev-expression": "^0.2.1",
"base64-arraybuffer": "0.2.0",
"base64-arraybuffer": "1.0.1",
"body-parser": "^1.19.0",
"chai": "4.1.1",
"chromeless": "^1.5.2",
@ -5784,9 +5785,9 @@
}
},
"node_modules/base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==",
"engines": {
"node": ">= 0.6.0"
}
@ -8637,6 +8638,14 @@
"base64-arraybuffer": "^0.2.0"
}
},
"node_modules/css-line-break/node_modules/base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@ -23441,6 +23450,14 @@
"node": ">=0.10"
}
},
"node_modules/text-segmentation": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.2.tgz",
"integrity": "sha512-uTqvLxdBrVnx/CFQOtnf8tfzSXFm+1Qxau7Xi54j4OPTZokuDOX8qncQzrg2G8ZicAMOM8TgzFAYTb+AqNO4Cw==",
"dependencies": {
"utrie": "^1.0.1"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -24517,6 +24534,14 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.1.tgz",
"integrity": "sha512-JPaDXF3vzgZxfeEwutdGzlrNoVFL5UvZcbO6Qo9D4GoahrieUPoMU8GCpVpR7MQqcKhmShIh8VlbEN3PLM3EBg==",
"dependencies": {
"base64-arraybuffer": "^1.0.1"
}
},
"node_modules/uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
@ -30457,9 +30482,9 @@
}
},
"base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA=="
},
"base64-js": {
"version": "1.3.0",
@ -32816,6 +32841,13 @@
"integrity": "sha512-gwKYIMUn7xodIcb346wgUhE2Dt5O1Kmrc16PWi8sL4FTfyDj8P5095rzH7+O8CTZudJr+uw2GCI/hwEkDJFI2w==",
"requires": {
"base64-arraybuffer": "^0.2.0"
},
"dependencies": {
"base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
}
}
},
"cssom": {
@ -44509,6 +44541,14 @@
"integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
"dev": true
},
"text-segmentation": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.2.tgz",
"integrity": "sha512-uTqvLxdBrVnx/CFQOtnf8tfzSXFm+1Qxau7Xi54j4OPTZokuDOX8qncQzrg2G8ZicAMOM8TgzFAYTb+AqNO4Cw==",
"requires": {
"utrie": "^1.0.1"
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -45332,6 +45372,14 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"dev": true
},
"utrie": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.1.tgz",
"integrity": "sha512-JPaDXF3vzgZxfeEwutdGzlrNoVFL5UvZcbO6Qo9D4GoahrieUPoMU8GCpVpR7MQqcKhmShIh8VlbEN3PLM3EBg==",
"requires": {
"base64-arraybuffer": "^1.0.1"
}
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",

View File

@ -6,7 +6,7 @@
"module": "dist/html2canvas.esm.js",
"typings": "dist/types/index.d.ts",
"browser": "dist/html2canvas.js",
"version": "1.2.0",
"version": "1.2.1",
"author": {
"name": "Niklas von Hertzen",
"email": "niklasvh@gmail.com",
@ -48,7 +48,7 @@
"babel-loader": "^8.0.5",
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-dev-expression": "^0.2.1",
"base64-arraybuffer": "0.2.0",
"base64-arraybuffer": "1.0.1",
"body-parser": "^1.19.0",
"chai": "4.1.1",
"chromeless": "^1.5.2",
@ -118,6 +118,7 @@
"homepage": "https://html2canvas.hertzen.com",
"license": "MIT",
"dependencies": {
"css-line-break": "2.0.1"
"css-line-break": "2.0.1",
"text-segmentation": "^1.0.2"
}
}

View File

@ -1,5 +1,5 @@
import {FEATURES} from './features';
import type {Context} from './context';
import {Context} from './context';
export class CacheStorage {
private static _link?: HTMLAnchorElement;

View File

@ -1,3 +1,5 @@
import {fromCodePoint, toCodePoints} from 'text-segmentation';
const testRangeBounds = (document: Document) => {
const TEST_HEIGHT = 123;
@ -22,6 +24,45 @@ const testRangeBounds = (document: Document) => {
return false;
};
const testIOSLineBreak = (document: Document) => {
const testElement = document.createElement('boundtest');
testElement.style.width = '50px';
testElement.style.display = 'block';
testElement.style.fontSize = '12px';
testElement.style.letterSpacing = '0px';
testElement.style.wordSpacing = '0px';
document.body.appendChild(testElement);
const range = document.createRange();
testElement.innerHTML = typeof ''.repeat === 'function' ? '👨'.repeat(10) : '';
const node = testElement.firstChild as Text;
const textList = toCodePoints(node.data).map((i) => fromCodePoint(i));
let offset = 0;
let prev: DOMRect = {} as DOMRect;
// ios 13 does not handle range getBoundingClientRect line changes correctly #2177
const supports = textList.every((text, i) => {
range.setStart(node, offset);
range.setEnd(node, offset + text.length);
const rect = range.getBoundingClientRect();
offset += text.length;
const boundAhead = rect.x > prev.x || rect.y > prev.y;
prev = rect;
if (i === 0) {
return true;
}
return boundAhead;
});
document.body.removeChild(testElement);
return supports;
};
const testCORS = (): boolean => typeof new Image().crossOrigin !== 'undefined';
const testResponseType = (): boolean => typeof new XMLHttpRequest().responseType === 'string';
@ -132,6 +173,12 @@ export const FEATURES = {
Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', {value});
return value;
},
get SUPPORT_WORD_BREAKING(): boolean {
'use strict';
const value = FEATURES.SUPPORT_RANGE_BOUNDS && testIOSLineBreak(document);
Object.defineProperty(FEATURES, 'SUPPORT_WORD_BREAKING', {value});
return value;
},
get SUPPORT_SVG_DRAWING(): boolean {
'use strict';
const value = testSVG(document);

View File

@ -15,6 +15,20 @@ export class Bounds {
clientRect.height
);
}
static fromDOMRectList(context: Context, domRectList: DOMRectList): Bounds {
const domRect = domRectList[0];
return domRect
? new Bounds(
domRect.x + context.windowBounds.left,
domRect.y + context.windowBounds.top,
domRect.width,
domRect.height
)
: Bounds.EMPTY;
}
static EMPTY = new Bounds(0, 0, 0, 0);
}
export const parseBounds = (context: Context, node: Element): Bounds => {

View File

@ -1,6 +1,5 @@
import {OVERFLOW_WRAP} from '../property-descriptors/overflow-wrap';
import {CSSParsedDeclaration} from '../index';
import {fromCodePoint, LineBreaker, toCodePoints} from 'css-line-break';
import {splitGraphemes} from 'text-segmentation';
import {Bounds, parseBounds} from './bounds';
import {FEATURES} from '../../core/features';
import {Context} from '../../core/context';
@ -21,13 +20,22 @@ export const parseTextBounds = (
styles: CSSParsedDeclaration,
node: Text
): TextBounds[] => {
const textList = breakText(value, styles);
const textList = splitGraphemes(value);
const textBounds: TextBounds[] = [];
let offset = 0;
textList.forEach((text) => {
if (styles.textDecorationLine.length || text.trim().length > 0) {
if (FEATURES.SUPPORT_RANGE_BOUNDS) {
textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length)));
if (!FEATURES.SUPPORT_WORD_BREAKING) {
textBounds.push(
new TextBounds(
text,
Bounds.fromDOMRectList(context, createRange(node, offset, text.length).getClientRects())
)
);
} else {
textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length)));
}
} else {
const replacementNode = node.splitText(text.length);
textBounds.push(new TextBounds(text, getWrapperBounds(context, node)));
@ -58,10 +66,10 @@ const getWrapperBounds = (context: Context, node: Text): Bounds => {
}
}
return new Bounds(0, 0, 0, 0);
return Bounds.EMPTY;
};
const getRangeBounds = (context: Context, node: Text, offset: number, length: number): Bounds => {
const createRange = (node: Text, offset: number, length: number): Range => {
const ownerDocument = node.ownerDocument;
if (!ownerDocument) {
throw new Error('Node has no owner document');
@ -69,47 +77,9 @@ const getRangeBounds = (context: Context, node: Text, offset: number, length: nu
const range = ownerDocument.createRange();
range.setStart(node, offset);
range.setEnd(node, offset + length);
return Bounds.fromClientRect(context, range.getBoundingClientRect());
return range;
};
const breakText = (value: string, styles: CSSParsedDeclaration): string[] => {
return styles.letterSpacing !== 0 ? toCodePoints(value).map((i) => fromCodePoint(i)) : breakWords(value, styles);
};
// https://drafts.csswg.org/css-text/#word-separator
const wordSeparators = [0x0020, 0x00a0, 0x1361, 0x10100, 0x10101, 0x1039, 0x1091];
const breakWords = (str: string, styles: CSSParsedDeclaration): string[] => {
const breaker = LineBreaker(str, {
lineBreak: styles.lineBreak,
wordBreak: styles.overflowWrap === OVERFLOW_WRAP.BREAK_WORD ? 'break-word' : styles.wordBreak
});
const words = [];
let bk;
while (!(bk = breaker.next()).done) {
if (bk.value) {
const value = bk.value.slice();
const codePoints = toCodePoints(value);
let word = '';
codePoints.forEach((codePoint) => {
if (wordSeparators.indexOf(codePoint) === -1) {
word += fromCodePoint(codePoint);
} else {
if (word.length) {
words.push(word);
}
words.push(fromCodePoint(codePoint));
word = '';
}
});
if (word.length) {
words.push(word);
}
}
}
return words;
const getRangeBounds = (context: Context, node: Text, offset: number, length: number): Bounds => {
return Bounds.fromClientRect(context, createRange(node, offset, length).getBoundingClientRect());
};

View File

@ -1,6 +1,6 @@
// https://www.w3.org/TR/css-syntax-3
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {fromCodePoint, toCodePoints} from 'text-segmentation';
export enum TokenType {
STRING_TOKEN,

View File

@ -1,5 +1,5 @@
import {LIST_STYLE_TYPE} from '../../property-descriptors/list-style-type';
import {fromCodePoint} from 'css-line-break';
import {fromCodePoint} from 'text-segmentation';
import {contains} from '../../../core/bitwise';
import {CSSParsedCounterDeclaration} from '../../index';

View File

@ -94,12 +94,15 @@ export const image: ITypeDescriptor<ICSSImage> = {
return imageFunction(context, value.values);
}
throw new Error(`Unsupported image type`);
throw new Error(`Unsupported image type ${value.type}`);
}
};
export function isSupportedImage(value: CSSValue): boolean {
return value.type !== TokenType.FUNCTION || !!SUPPORTED_IMAGE_FUNCTIONS[value.name];
return (
!(value.type === TokenType.IDENT_TOKEN && value.value === 'none') &&
(value.type !== TokenType.FUNCTION || !!SUPPORTED_IMAGE_FUNCTIONS[value.name])
);
}
const SUPPORTED_IMAGE_FUNCTIONS: Record<string, (context: Context, args: CSSValue[]) => ICSSImage> = {

View File

@ -19,7 +19,7 @@ import {
import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background';
import {isDimensionToken} from '../../css/syntax/parser';
import {TextBounds} from '../../css/layout/text';
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {fromCodePoint, toCodePoints} from 'text-segmentation';
import {ImageElementContainer} from '../../dom/replaced-elements/image-element-container';
import {contentBox} from '../box-sizing';
import {CanvasElementContainer} from '../../dom/replaced-elements/canvas-element-container';
@ -628,7 +628,7 @@ export class CanvasRenderer extends Renderer {
const y = getAbsoluteValue(position[position.length - 1], height);
const [rx, ry] = calculateRadius(backgroundImage, x, y, width, height);
if (rx > 0 && rx > 0) {
if (rx > 0 && ry > 0) {
const radialGradient = this.ctx.createRadialGradient(left + x, top + y, 0, left + x, top + y, rx);
processColorStops(backgroundImage.stops, rx * 2).forEach((colorStop) =>

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@
<title>Background attribute tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<link href="base64.css" rel="stylesheet">
<style>
html {
background-color: red;
@ -39,6 +40,9 @@
background:url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCABLAEsDAREAAhEBAxEB/8QAHAAAAgMBAQEBAAAAAAAAAAAABQcEBggCAwAJ/8QANxAAAgECBQEHAQUIAwEAAAAAAQIDBBEABQYSITEHExQiQVFhcTKBkbHBCCNCUnLR4fAWYqLx/8QAGwEAAQUBAQAAAAAAAAAAAAAAAQIDBAUGAAf/xAAzEQACAgECAwQHCAMAAAAAAAAAAQIDEQQhEjFBIlFhcRMUgZGhsdEFFSNCUsHh8DKi8f/aAAwDAQACEQMRAD8AxREirbfKlv6rYxDz0R6mmj2eohpqrLKmGVTJTVsEpG65sHF8dBSakpLmn8hq5pxTXRo0DlaCn1dB7eJZfxxD0b/FiyFqVmmQ1Yo/KMaRFBIj5tCTFSG3SsgP/sf3x0t0JTJtLTlJa0FSP31+R/0TCcYG8nM0Q9sLAQJoRzgnECoiFjggB7RC5wsSZHTLaGCQpJBSq68EbQ1sUjst72bdU1dy9x1mlPQJlNQY0p+9Rd67YwDcG/HGBVK12JPOPMF1darbUVt4GgoazdmeW1YNxKaea/8AUoJxVafsTj4P9xm5cVcvJjjhljjlRZDtBPt1xqlzMu3tsNjSulMrziigkVUkUOHK9RcdD8Ys6owcStsnJMsFV2ZUrxVDRqQ0luSL9P8AH5YcdUWhtWtC91T2e1mWRPUQx740BLKt7/X6YhTqcd0S4WKWxQZhiOPA+ccYICAy3Y4UAyXS/uwFXLGFv5kX++KCWHu5m6i3y4SbI7y0k0bZbZHQqTtT2/qw3FRUk1P5/QVJtxa4X8PqNHIanxmndMzg+Z6KEc+6kqfyxG4MXuPiyFJ4qcn3F5o9RGrzPbI5aRz6np7D2GLpSbfiZ9xWMD20Fq5MlgpF72PdNIyGE8OFFvMPjn7/ALsWlE3FIrbYcTH5keaw11MtnVz063xZqWSvccMkZhRwT08m4KRY3BwibSW4uCeTJ+qKA5PnuYURBXuZmVQ/Xbe6n8CMVTWGWXQATOCCLjBAQXbzHphQkyTS5XE1HTXpttTtPfB5bruvxt+LdcUc7e08PY3dcez2luTIsm3GwjhF+OW/xhl3eLHuHwLpo1pn0lkrRh5no2qICkSGRmZJSQAo6khhxhmxS9azFc8P3kLsqiUZeQ3dPafhmrFkU7HUh3Exsyi9269LeuL2Me0ZWUsIM6p7EKjtkzTLXi1XVaWaEvfwSkmqJZdq7Qy9LdSeOeD6WlSRAm8YbNDN2f6ry6lpqXRGq6RqjLXjapgzaiM3i4woPdK6sCpYXs3NuOuJXBjkR3JNbjRy+KvkpF8bEaaV1tYi5/8AuI0k2tx1NLkYQ/aB1XmucdrGeHLNQ5jl+X07ikjiptiITGNrNfaSSWvyTf7gMUt2olGxxjyRe0Uw9GnOOWxXyVuoVLM+rc7kBvYeIC2/BcNeszew+qK1+UGTVmdGRj/yjPV+PGH+2HVqJ94PRV/pQtos+1SPs0WVi/S5kP64S9PpespfAlLUaz9Efj9QtkEmuc/roaajgycB2AaRo5NsY9WJB6DHeraVvCcm/Z9BM9Vq4rMlFL2/Dc0dp7L4dO5eKWl6k7pJtoVpGtyxA6dOgxIqpVawipuvldLMgtRzIjbDJsBYeW32sSlAiNkLtI7Y6nRkMcOWGeKYKqrMsO8Ru91Qkeo3bfKP1wpN8XDEXCtSWWGv2Le1nXk2qJn1VXjNVrHWJJpy3euIRtLcKAFAO0X6k8Di+Jzmk4xiQ5UWRUnYsGvO23thyvs60bUVIq0GcV1O4yykU3ldyNvegeiITck8XFuThOqvhRXxN7vkDSUTvsUUtlzPzdzqecS940zOzkvI7HlmuSxPyeuMfF5e/M1/CkerKFiaZQZIiN19wJ+oGE5T2Ow2QGkhc7u7fn6YWmDhYMnyDLGBanrp43JG8TQhx09CtrYs5aaPSTIEPtKf54L2PHzyXrRmVQZTlJZFQyyctIqlbj0HOHKqvRrfdkbUah3yzyXcWnK6lK4Ps+0h2snqDiXFZITeA/leTSV1ZGii537DZbgAjn64kRhljbkkhqt+zkmo6ekLx0s61ZNJJT1se5GABNn4IN/0BxIlpctSi8MajqOcZLKG32e9immtA6berly+nVqaF3ip4dyxR2UndybliR1J4Fhh6FSj2p7sZna32Y8jM+faKzjU2YNmWZ54tZmFSAZJ54mdiB0A6WUDoBYYoZ6C62XHZYm34MtofaNNceCFbS80B6jskrWRQmb0e8g3D07gLb7zfCPuufSa9zF/elfWD96Kzn2iZ8mjqFmmjd46hoN5tGsjKBu2BjubhlPAsAet8Vl+mlTlt9f7jr+xa1aiNuMLpn+9Clyaan3tt7vbfjzjEbLJWUUxqqxBtdR6e+NJgy/IZmlKiTMdPpO8RiuWsL/aAPUYUlsBvvO8glmi1LHGEURzAhh6gAXGFxW4mT2HbpOhTxSSOGWNrbghBJ+behxOgiLKRobSWo6fJKGE11YYYmO9TI/mP8J2jkkm1uP1xNTSW5F3b2CGp9UxHTGZVCkmnaMokbdTu8vP44alPIBCxkKFHJsStwfS5FsN5AfSSkpMyXEaAFiebLwCfxOCAomrshgGofFzw0UVPVoHlqJgu+NhZJG5IFhwbnnzcemKfVVQVnHJJZ69f748zQaO6cqlFNvG2Onev+chT1MEwqZfDyQdxuOwVDhZAL9CPjpjOvbkzQLxFk8TpIA6kpe7WHpjTYMrnI49KVVHmGX0cdOsnhwLI5Ujc3sB1Nr9elz64dSBkFZ+9To7PaatqwsNNFUhWk3G0YY28xP8PNyfS+OinGRzawa07E4IaySCaVVeOQdOoBHS/vixqIFnMtnb8tatHpxcpjgepjqHlKzOYwU2bSoYA26g9PTBtWcYG4y4XuU4Z9mE+TJl88cMUJ2FlDFzcG9gbDi+GEmFyT5AWSInvFFyFJHHXp/nBE5PHLA0Ek9M7EtNEYw59T8/76YMe46W+5W9dd+cm76IWMDhZbRxuAkg2tuDgggMFuBzyeuIesjmviXTwT2fmWOhklY4Pr5815Cxly+GRwxy+lclQdypMoPA5spKj7iR7YoJVtPHAv8AZfLK9xpI2Jr/ACfwItT2YTToVjCsTx5W639Mah0dxkVbgjZVojV+nqhXyeKOeON9yxzuFHwASeAOTb3OBGqaFemj1Gpn2VVuudD51kdRkcFDPWqIvE1dTG7qLLd1CX5JDDn64kcDwMuxE3sNoNWdnarltd3FZRx37mYVQOxFAsGH2r9eelrYEISiGVkZLI3M/wBRTallp5p9qLGuyNAb2FuTf5OHWskdsATRb1L2Fgf9/XCcHZIrRHxJHsdxHryPyIwMCiPLAwa9gHS4t7m/GOwcQ66natgqaSQeWqjaJSSP4hcG5vYggdQbH3wmUOOLj3jlcuCal3CIqWplqJAaWGNtxJSSdkYH1upVbG/wMZnixtlL2r+PkazhT33+P8jGy5iViYnzWBv841hjwplh3RwX9SXPyS2OQcBNpWpYZWiO096E6X8u61vwxyE9AplEjLPmLBjdQFHwOOMFdRL5BOjdmijJJJAU/wC/gMcgs6pnLIpJuTUAH6XtgndWjwy1Qc1qeP5fzwFzO6EOR28dSDcfPVhW56j93x+eB1CBdXVEtLmmWiJzGDOQbH03DCJbNBXJiU1zUyUGtc+p6dzDBFXzqkacBQJDYDGV1F9kLpxi9k38zbaaqE6ISkt2kf/Z");
}
.encoded2 {
background-size: cover;
}
</style>
</head>
@ -46,6 +50,7 @@
<div class="medium">
<div class="encoded"></div>
<div class="base64 encoded2"></div>
</div>
</body>

View File

@ -46,6 +46,7 @@
<div style="background: linear-gradient(60deg, hsla(120,80%,50%,0.8) 0%, transparent 50%, rgba(255,100,100,0.5) 100%);"></div>
<div style="background: linear-gradient(to right, red 20%, orange 20% 40%, yellow 40% 60%, green 60% 80%, blue 80%);"></div>
<div style="background: linear-gradient(-45deg, #FF0000 40%, #00FF00 50%);"></div>
<div style="background:linear-gradient(217deg, rgba(255, 0, 0, 0.8) 10%, rgba(255, 0, 0, 0) 70.71%), linear-gradient(127deg, rgba(0, 255, 0, 0.8) 20%, rgba(0, 255, 0, 0) 70.71%), linear-gradient(336deg, rgba(0, 0, 255, 0.8) 40%, rgba(0, 0, 255, 0) 70.71%), rgb(255, 255, 255);"></div>
<div class="linearGradient"></div>
</body>
</html>

View File

@ -22,18 +22,30 @@ type TestList = {[key: string]: Test[]};
function onTestChange(browserTests: Test[]) {
if (browserSelector) {
const currentSelection = browserSelector.value;
while (browserSelector.firstChild) {
browserSelector.firstChild.remove();
}
let newSelection;
browserTests.forEach((browser, i) => {
if (i === 0) {
onBrowserChange(browser);
newSelection = browser;
}
const option = document.createElement('option');
option.value = browser.id;
if (browser.id === currentSelection) {
option.selected = true;
newSelection = browser;
}
option.textContent = browser.id.replace(/_/g, ' ');
browserSelector.appendChild(option);
});
if (newSelection) {
onBrowserChange(newSelection);
}
}
}
@ -48,13 +60,20 @@ function onBrowserChange(browserTest: Test) {
previewImage.style.transformOrigin = '';
}
}
if (history) {
history.replaceState(null, document.title, `?browser=${browserSelector?.value}&test=${testSelector?.value}`);
}
}
function selectTest(testName: string) {
if (testLink) {
testLink.textContent = testLink.href = testName;
const foundTest = testList[testName];
if (foundTest) {
if (testLink) {
testLink.textContent = testLink.href = testName;
}
onTestChange(foundTest);
}
onTestChange(testList[testName]);
}
const UP_ARROW = 38;
@ -116,15 +135,29 @@ if (testSelector && browserSelector) {
false
);
const tests: string[] = Object.keys(testList);
tests.forEach((testName, i) => {
if (i === 0) {
selectTest(testName);
let testFromUrl: string | null = null;
if (URLSearchParams) {
const url = new URLSearchParams(location.search);
testFromUrl = url.get('test');
if (browserSelector) {
const option = document.createElement('option');
browserSelector.appendChild(option);
browserSelector.value = option.value = url.get('browser') ?? '';
}
}
const tests: string[] = Object.keys(testList);
tests.forEach((testName) => {
const option = document.createElement('option');
option.value = testName;
option.textContent = testName;
if (option.value === testFromUrl) {
option.selected = true;
}
testSelector.appendChild(option);
});
selectTest(testSelector.value ?? testSelector.firstChild?.textContent ?? '');
}