Compare commits

..

1 Commits

Author SHA1 Message Date
Niklas von Hertzen
eca5033705 feat: drop support for IE9 2021-08-09 18:31:10 +08:00
32 changed files with 2068 additions and 1096 deletions

View File

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

View File

@@ -2,76 +2,6 @@
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. 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.3.2](https://github.com/niklasvh/html2canvas/compare/v1.3.1...v1.3.2) (2021-08-15)
### docs
* add warning for webgl cloning with preserveDrawingBuffer=false (#2661) ([01ed879](https://github.com/niklasvh/html2canvas/commit/01ed87907ad9c7688880e2c5cb8ebc22ef73a4d8)), closes [#2661](https://github.com/niklasvh/html2canvas/issues/2661)
* include src files on www (#2660) ([58ff000](https://github.com/niklasvh/html2canvas/commit/58ff0003f77d825ac027eeec95fa80c0123eaf8f)), closes [#2660](https://github.com/niklasvh/html2canvas/issues/2660)
### feat
* add support for data-html2canvas-debug property for debugging (#2658) ([cd0d725](https://github.com/niklasvh/html2canvas/commit/cd0d7258c3a93f2989d5d9ec0244ba2763ea2d23)), closes [#2658](https://github.com/niklasvh/html2canvas/issues/2658)
### fix
* disable transition properties (#2659) ([f143166](https://github.com/niklasvh/html2canvas/commit/f1431665513e0a4636fb167a241f4a0571ba728a)), closes [#2659](https://github.com/niklasvh/html2canvas/issues/2659)
* overflows with absolutely positioned content (#2663) ([38c6829](https://github.com/niklasvh/html2canvas/commit/38c682955a9299ca7785af71d8f251df799405b0)), closes [#2663](https://github.com/niklasvh/html2canvas/issues/2663)
## [1.3.1](https://github.com/niklasvh/html2canvas/compare/v1.3.0...v1.3.1) (2021-08-14)
### fix
* multi arg transition/animation duration (#2657) ([1b55ed5](https://github.com/niklasvh/html2canvas/commit/1b55ed5668dcbbe1c6d8d7e94736d8f2da2d31c5)), closes [#2657](https://github.com/niklasvh/html2canvas/issues/2657)
# [1.3.0](https://github.com/niklasvh/html2canvas/compare/v1.2.2...v1.3.0) (2021-08-13)
### feat
* add rtl render support (#2653) ([6947982](https://github.com/niklasvh/html2canvas/commit/694798284838b16882e648914da0905818aa366c)), closes [#2653](https://github.com/niklasvh/html2canvas/issues/2653)
* correctly break graphemes (#2652) ([437b367](https://github.com/niklasvh/html2canvas/commit/437b367d3ba9dfd7f9a4c8042ac8d00208c09770)), closes [#2652](https://github.com/niklasvh/html2canvas/issues/2652)
### fix
* correctly handle allowTaint canvas cloning (#2649) ([c378e22](https://github.com/niklasvh/html2canvas/commit/c378e220694c14cb7b3b4b8650a7757f8fc23c7a)), closes [#2649](https://github.com/niklasvh/html2canvas/issues/2649)
* finish animation/transitions for elements (#2632) ([969638f](https://github.com/niklasvh/html2canvas/commit/969638fb94a0a14c64a667fa6e5689f79d9f1044)), closes [#2632](https://github.com/niklasvh/html2canvas/issues/2632)
### test
* add letter-spacing test for zwj emoji (#2650) ([f919204](https://github.com/niklasvh/html2canvas/commit/f919204efa06af219f155ca279f96124bb92862b)), closes [#2650](https://github.com/niklasvh/html2canvas/issues/2650)
## [1.2.2](https://github.com/niklasvh/html2canvas/compare/v1.2.1...v1.2.2) (2021-08-10)
### ci
* add ios15 target (#2564) ([e429e04](https://github.com/niklasvh/html2canvas/commit/e429e0443adf5c7ca3041b97a8157b8911302206)), closes [#2564](https://github.com/niklasvh/html2canvas/issues/2564)
### docs
* update test previewer (#2637) ([7a06d0c](https://github.com/niklasvh/html2canvas/commit/7a06d0c2c2f3b8a1d1a8a85c540f8288b782e8c6)), closes [#2637](https://github.com/niklasvh/html2canvas/issues/2637)
### fix
* parsing counter content in pseudo element (#2640) ([1941b9e](https://github.com/niklasvh/html2canvas/commit/1941b9e0acfd9243da0beaf70e1643cab1b4a963)), closes [#2640](https://github.com/niklasvh/html2canvas/issues/2640)
* radial gradient ry check (#2631) ([a0dd38a](https://github.com/niklasvh/html2canvas/commit/a0dd38a8be4e540ae1c1f4b4e41f6c386f3e454f)), closes [#2631](https://github.com/niklasvh/html2canvas/issues/2631)
* test for ios range line break error (#2635) ([f43f942](https://github.com/niklasvh/html2canvas/commit/f43f942fcd793dde9cdc6c0438f379ec3c05c405)), closes [#2635](https://github.com/niklasvh/html2canvas/issues/2635)
### test
* large base64 encoded background (#2636) ([e36408a](https://github.com/niklasvh/html2canvas/commit/e36408ad030fe31acd9969a37fe24c1621c0bd04)), closes [#2636](https://github.com/niklasvh/html2canvas/issues/2636)
## [1.2.1](https://github.com/niklasvh/html2canvas/compare/v1.2.0...v1.2.1) (2021-08-05) ## [1.2.1](https://github.com/niklasvh/html2canvas/compare/v1.2.0...v1.2.1) (2021-08-05)

View File

@@ -3,7 +3,7 @@ html2canvas
[Homepage](https://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](https://github.com/niklasvh/html2canvas/discussions/categories/q-a) [Homepage](https://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](https://github.com/niklasvh/html2canvas/discussions/categories/q-a)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![CI](https://github.com/niklasvh/html2canvas/workflows/CI/badge.svg?branch=master) ![CI](https://github.com/niklasvh/html2canvas/workflows/CI/badge.svg?branch=master)
[![NPM Downloads](https://img.shields.io/npm/dm/html2canvas.svg)](https://www.npmjs.org/package/html2canvas) [![NPM Downloads](https://img.shields.io/npm/dm/html2canvas.svg)](https://www.npmjs.org/package/html2canvas)
[![NPM Version](https://img.shields.io/npm/v/html2canvas.svg)](https://www.npmjs.org/package/html2canvas) [![NPM Version](https://img.shields.io/npm/v/html2canvas.svg)](https://www.npmjs.org/package/html2canvas)
@@ -28,7 +28,8 @@ The library should work fine on the following browsers (with `Promise` polyfill)
* Firefox 3.5+ * Firefox 3.5+
* Google Chrome * Google Chrome
* Opera 12+ * Opera 12+
* IE9+ * IE10+
* Edge
* Safari 6+ * Safari 6+
As each CSS property needs to be manually built to be supported, there are a number of properties that are not yet supported. As each CSS property needs to be manually built to be supported, there are a number of properties that are not yet supported.

View File

@@ -5,28 +5,28 @@ nextUrl: "/getting-started"
nextTitle: "Getting Started" nextTitle: "Getting Started"
--- ---
Before you get started with the script, there are a few things that are good to know regarding the Before you get started with the script, there are a few things that are good to know regarding the
script and some of its limitations. script and some of its limitations.
## Introduction ## Introduction
The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser.
The screenshot is based on the DOM and as such may not be 100% accurate to the real representation The screenshot is based on the DOM and as such may not be 100% accurate to the real representation
as it does not make an actual screenshot, but builds the screenshot based on the information as it does not make an actual screenshot, but builds the screenshot based on the information
available on the page. available on the page.
## How it works ## How it works
The script traverses through the DOM of the page it is loaded on. It gathers information on all the elements The script traverses through the DOM of the page it is loaded on. It gathers information on all the elements
there, which it then uses to build a representation of the page. In other words, it does not actually take a there, which it then uses to build a representation of the page. In other words, it does not actually take a
screenshot of the page, but builds a representation of it based on the properties it reads from the DOM. screenshot of the page, but builds a representation of it based on the properties it reads from the DOM.
As a result, it is only able to render correctly properties that it understands, meaning there are many As a result, it is only able to render correctly properties that it understands, meaning there are many
CSS properties which do not work. For a full list of supported CSS properties, check out the CSS properties which do not work. For a full list of supported CSS properties, check out the
[supported features](/features/) page. [supported features](/features/) page.
## Limitations ## Limitations
All the images that the script uses need to reside under the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy) All the images that the script uses need to reside under the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy)
for it to be able to read them without the assistance of a [proxy](/proxy/). Similarly, if you have other `canvas` for it to be able to read them without the assistance of a [proxy](/proxy/). Similarly, if you have other `canvas`
elements on the page, which have been tainted with cross-origin content, they will become dirty and no longer readable by html2canvas. elements on the page, which have been tainted with cross-origin content, they will become dirty and no longer readable by html2canvas.
The script doesn't render plugin content such as Flash or Java applets. The script doesn't render plugin content such as Flash or Java applets.
@@ -37,6 +37,6 @@ The library should work fine on the following browsers (with `Promise` polyfill)
- Firefox 3.5+ - Firefox 3.5+
- Google Chrome - Google Chrome
- Opera 12+ - Opera 12+
- IE9+ - IE10+
- Edge - Edge
- Safari 6+ - Safari 6+

View File

@@ -48,12 +48,6 @@ module.exports = function(config) {
platform: 'iOS', platform: 'iOS',
sdk: '15.0' sdk: '15.0'
}, },
SauceLabs_IE9: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '9.0',
platform: 'Windows 7'
},
SauceLabs_IE10: { SauceLabs_IE10: {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'internet explorer', browserName: 'internet explorer',
@@ -93,11 +87,6 @@ module.exports = function(config) {
version: '9.3', version: '9.3',
device: 'iPhone 6 Plus Simulator' device: 'iPhone 6 Plus Simulator'
}, },
IE_9: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE9',
flags: ['-extoff']
},
IE_10: { IE_10: {
base: 'IE', base: 'IE',
'x-ua-compatible': 'IE=EmulateIE10', 'x-ua-compatible': 'IE=EmulateIE10',

2553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -12,7 +12,6 @@ export class CacheStorage {
} }
link.href = url; link.href = url;
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
return link.protocol + link.hostname + link.port; return link.protocol + link.hostname + link.port;
} }

View File

@@ -1,29 +0,0 @@
const elementDebuggerAttribute = 'data-html2canvas-debug';
export const enum DebuggerType {
NONE,
ALL,
CLONE,
PARSE,
RENDER
}
const getElementDebugType = (element: Element): DebuggerType => {
const attribute = element.getAttribute(elementDebuggerAttribute);
switch (attribute) {
case 'all':
return DebuggerType.ALL;
case 'clone':
return DebuggerType.CLONE;
case 'parse':
return DebuggerType.PARSE;
case 'render':
return DebuggerType.RENDER;
default:
return DebuggerType.NONE;
}
};
export const isDebugging = (element: Element, type: Omit<DebuggerType, DebuggerType.NONE>): boolean => {
const elementType = getElementDebugType(element);
return elementType === DebuggerType.ALL || type === elementType;
};

View File

@@ -1,8 +1,8 @@
import {CSSValue} from './syntax/parser'; import {CSSValue} from './syntax/parser';
import {CSSTypes} from './types'; import {CSSTypes} from './types/index';
import {Context} from '../core/context'; import {Context} from '../core/context';
export const enum PropertyDescriptorParsingType { export enum PropertyDescriptorParsingType {
VALUE, VALUE,
LIST, LIST,
IDENT_VALUE, IDENT_VALUE,

View File

@@ -31,7 +31,6 @@ import {
borderTopWidth borderTopWidth
} from './property-descriptors/border-width'; } from './property-descriptors/border-width';
import {color} from './property-descriptors/color'; import {color} from './property-descriptors/color';
import {direction} from './property-descriptors/direction';
import {display, DISPLAY} from './property-descriptors/display'; import {display, DISPLAY} from './property-descriptors/display';
import {float, FLOAT} from './property-descriptors/float'; import {float, FLOAT} from './property-descriptors/float';
import {letterSpacing} from './property-descriptors/letter-spacing'; import {letterSpacing} from './property-descriptors/letter-spacing';
@@ -58,7 +57,6 @@ import {Tokenizer} from './syntax/tokenizer';
import {Color, color as colorType, isTransparent} from './types/color'; import {Color, color as colorType, isTransparent} from './types/color';
import {angle} from './types/angle'; import {angle} from './types/angle';
import {image} from './types/image'; import {image} from './types/image';
import {time} from './types/time';
import {opacity} from './property-descriptors/opacity'; import {opacity} from './property-descriptors/opacity';
import {textDecorationColor} from './property-descriptors/text-decoration-color'; import {textDecorationColor} from './property-descriptors/text-decoration-color';
import {textDecorationLine} from './property-descriptors/text-decoration-line'; import {textDecorationLine} from './property-descriptors/text-decoration-line';
@@ -73,7 +71,6 @@ import {contains} from '../core/bitwise';
import {content} from './property-descriptors/content'; import {content} from './property-descriptors/content';
import {counterIncrement} from './property-descriptors/counter-increment'; import {counterIncrement} from './property-descriptors/counter-increment';
import {counterReset} from './property-descriptors/counter-reset'; import {counterReset} from './property-descriptors/counter-reset';
import {duration} from './property-descriptors/duration';
import {quotes} from './property-descriptors/quotes'; import {quotes} from './property-descriptors/quotes';
import {boxShadow} from './property-descriptors/box-shadow'; import {boxShadow} from './property-descriptors/box-shadow';
import {paintOrder} from './property-descriptors/paint-order'; import {paintOrder} from './property-descriptors/paint-order';
@@ -82,7 +79,6 @@ import {webkitTextStrokeWidth} from './property-descriptors/webkit-text-stroke-w
import {Context} from '../core/context'; import {Context} from '../core/context';
export class CSSParsedDeclaration { export class CSSParsedDeclaration {
animationDuration: ReturnType<typeof duration.parse>;
backgroundClip: ReturnType<typeof backgroundClip.parse>; backgroundClip: ReturnType<typeof backgroundClip.parse>;
backgroundColor: Color; backgroundColor: Color;
backgroundImage: ReturnType<typeof backgroundImage.parse>; backgroundImage: ReturnType<typeof backgroundImage.parse>;
@@ -108,7 +104,6 @@ export class CSSParsedDeclaration {
borderLeftWidth: ReturnType<typeof borderLeftWidth.parse>; borderLeftWidth: ReturnType<typeof borderLeftWidth.parse>;
boxShadow: ReturnType<typeof boxShadow.parse>; boxShadow: ReturnType<typeof boxShadow.parse>;
color: Color; color: Color;
direction: ReturnType<typeof direction.parse>;
display: ReturnType<typeof display.parse>; display: ReturnType<typeof display.parse>;
float: ReturnType<typeof float.parse>; float: ReturnType<typeof float.parse>;
fontFamily: ReturnType<typeof fontFamily.parse>; fontFamily: ReturnType<typeof fontFamily.parse>;
@@ -150,7 +145,6 @@ export class CSSParsedDeclaration {
zIndex: ReturnType<typeof zIndex.parse>; zIndex: ReturnType<typeof zIndex.parse>;
constructor(context: Context, declaration: CSSStyleDeclaration) { constructor(context: Context, declaration: CSSStyleDeclaration) {
this.animationDuration = parse(context, duration, declaration.animationDuration);
this.backgroundClip = parse(context, backgroundClip, declaration.backgroundClip); this.backgroundClip = parse(context, backgroundClip, declaration.backgroundClip);
this.backgroundColor = parse(context, backgroundColor, declaration.backgroundColor); this.backgroundColor = parse(context, backgroundColor, declaration.backgroundColor);
this.backgroundImage = parse(context, backgroundImage, declaration.backgroundImage); this.backgroundImage = parse(context, backgroundImage, declaration.backgroundImage);
@@ -176,7 +170,6 @@ export class CSSParsedDeclaration {
this.borderLeftWidth = parse(context, borderLeftWidth, declaration.borderLeftWidth); this.borderLeftWidth = parse(context, borderLeftWidth, declaration.borderLeftWidth);
this.boxShadow = parse(context, boxShadow, declaration.boxShadow); this.boxShadow = parse(context, boxShadow, declaration.boxShadow);
this.color = parse(context, color, declaration.color); this.color = parse(context, color, declaration.color);
this.direction = parse(context, direction, declaration.direction);
this.display = parse(context, display, declaration.display); this.display = parse(context, display, declaration.display);
this.float = parse(context, float, declaration.cssFloat); this.float = parse(context, float, declaration.cssFloat);
this.fontFamily = parse(context, fontFamily, declaration.fontFamily); this.fontFamily = parse(context, fontFamily, declaration.fontFamily);
@@ -313,8 +306,6 @@ const parse = (context: Context, descriptor: CSSPropertyDescriptor<any>, style?:
case 'length-percentage': case 'length-percentage':
const value = parser.parseComponentValue(); const value = parser.parseComponentValue();
return isLengthPercentage(value) ? value : ZERO_LENGTH; return isLengthPercentage(value) ? value : ZERO_LENGTH;
case 'time':
return time.parse(context, parser.parseComponentValue());
} }
break; break;
} }

View File

@@ -1,7 +1,6 @@
import {OVERFLOW_WRAP} from '../property-descriptors/overflow-wrap'; import {OVERFLOW_WRAP} from '../property-descriptors/overflow-wrap';
import {CSSParsedDeclaration} from '../index'; import {CSSParsedDeclaration} from '../index';
import {fromCodePoint, LineBreaker, toCodePoints} from 'css-line-break'; import {fromCodePoint, LineBreaker, toCodePoints} from 'css-line-break';
import {splitGraphemes} from 'text-segmentation';
import {Bounds, parseBounds} from './bounds'; import {Bounds, parseBounds} from './bounds';
import {FEATURES} from '../../core/features'; import {FEATURES} from '../../core/features';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
@@ -29,24 +28,12 @@ export const parseTextBounds = (
if (styles.textDecorationLine.length || text.trim().length > 0) { if (styles.textDecorationLine.length || text.trim().length > 0) {
if (FEATURES.SUPPORT_RANGE_BOUNDS) { if (FEATURES.SUPPORT_RANGE_BOUNDS) {
if (!FEATURES.SUPPORT_WORD_BREAKING) { if (!FEATURES.SUPPORT_WORD_BREAKING) {
const rects = createRange(node, offset, text.length).getClientRects(); textBounds.push(
if (rects.length > 1) { new TextBounds(
let graphemeOffset = 0; text,
splitGraphemes(text).forEach((grapheme) => { Bounds.fromDOMRectList(context, createRange(node, offset, text.length).getClientRects())
textBounds.push( )
new TextBounds( );
grapheme,
Bounds.fromDOMRectList(
context,
createRange(node, graphemeOffset + offset, grapheme.length).getClientRects()
)
)
);
graphemeOffset += grapheme.length;
});
} else {
textBounds.push(new TextBounds(text, Bounds.fromDOMRectList(context, rects)));
}
} else { } else {
textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length))); textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length)));
} }
@@ -99,7 +86,7 @@ const getRangeBounds = (context: Context, node: Text, offset: number, length: nu
}; };
const breakText = (value: string, styles: CSSParsedDeclaration): string[] => { const breakText = (value: string, styles: CSSParsedDeclaration): string[] => {
return styles.letterSpacing !== 0 ? splitGraphemes(value) : breakWords(value, styles); return styles.letterSpacing !== 0 ? toCodePoints(value).map((i) => fromCodePoint(i)) : breakWords(value, styles);
}; };
// https://drafts.csswg.org/css-text/#word-separator // https://drafts.csswg.org/css-text/#word-separator

View File

@@ -1,23 +0,0 @@
import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {Context} from '../../core/context';
export const enum DIRECTION {
LTR = 0,
RTL = 1
}
export const direction: IPropertyIdentValueDescriptor<DIRECTION> = {
name: 'direction',
initialValue: 'ltr',
prefix: false,
type: PropertyDescriptorParsingType.IDENT_VALUE,
parse: (_context: Context, direction: string) => {
switch (direction) {
case 'rtl':
return DIRECTION.RTL;
case 'ltr':
default:
return DIRECTION.LTR;
}
}
};

View File

@@ -1,14 +0,0 @@
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {Context} from '../../core/context';
import {CSSValue, isDimensionToken} from '../syntax/parser';
import {time} from '../types/time';
export const duration: IPropertyListDescriptor<number[]> = {
name: 'duration',
initialValue: '0s',
prefix: false,
type: PropertyDescriptorParsingType.LIST,
parse: (context: Context, tokens: CSSValue[]) => {
return tokens.filter(isDimensionToken).map((token) => time.parse(context, token));
}
};

View File

@@ -1,6 +1,6 @@
import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor'; import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
export const enum OVERFLOW_WRAP { export enum OVERFLOW_WRAP {
NORMAL = 'normal', NORMAL = 'normal',
BREAK_WORD = 'break-word' BREAK_WORD = 'break-word'
} }

View File

@@ -5,8 +5,7 @@ export enum OVERFLOW {
VISIBLE = 0, VISIBLE = 0,
HIDDEN = 1, HIDDEN = 1,
SCROLL = 2, SCROLL = 2,
CLIP = 3, AUTO = 3
AUTO = 4
} }
export const overflow: IPropertyListDescriptor<OVERFLOW[]> = { export const overflow: IPropertyListDescriptor<OVERFLOW[]> = {
@@ -21,8 +20,6 @@ export const overflow: IPropertyListDescriptor<OVERFLOW[]> = {
return OVERFLOW.HIDDEN; return OVERFLOW.HIDDEN;
case 'scroll': case 'scroll':
return OVERFLOW.SCROLL; return OVERFLOW.SCROLL;
case 'clip':
return OVERFLOW.CLIP;
case 'auto': case 'auto':
return OVERFLOW.AUTO; return OVERFLOW.AUTO;
case 'visible': case 'visible':

View File

@@ -2,7 +2,7 @@
import {fromCodePoint, toCodePoints} from 'css-line-break'; import {fromCodePoint, toCodePoints} from 'css-line-break';
export const enum TokenType { export enum TokenType {
STRING_TOKEN, STRING_TOKEN,
BAD_STRING_TOKEN, BAD_STRING_TOKEN,
LEFT_PARENTHESIS_TOKEN, LEFT_PARENTHESIS_TOKEN,

View File

@@ -4,7 +4,10 @@ import {contains} from '../../../core/bitwise';
import {CSSParsedCounterDeclaration} from '../../index'; import {CSSParsedCounterDeclaration} from '../../index';
export class CounterState { export class CounterState {
private readonly counters: {[key: string]: number[]} = {}; readonly counters: {[key: string]: number[]};
constructor() {
this.counters = {};
}
getCounterValue(name: string): number { getCounterValue(name: string): number {
const counter = this.counters[name]; const counter = this.counters[name];
@@ -15,7 +18,7 @@ export class CounterState {
return 1; return 1;
} }
getCounterValues(name: string): readonly number[] { getCounterValues(name: string): number[] {
const counter = this.counters[name]; const counter = this.counters[name];
return counter ? counter : []; return counter ? counter : [];
} }
@@ -34,9 +37,6 @@ export class CounterState {
const counter = this.counters[entry.counter]; const counter = this.counters[entry.counter];
if (counter && entry.increment !== 0) { if (counter && entry.increment !== 0) {
canReset = false; canReset = false;
if (!counter.length) {
counter.push(1);
}
counter[Math.max(0, counter.length - 1)] += entry.increment; counter[Math.max(0, counter.length - 1)] += entry.increment;
} }
}); });

View File

@@ -1 +1 @@
export type CSSTypes = 'angle' | 'color' | 'image' | 'length' | 'length-percentage' | 'time'; export type CSSTypes = 'angle' | 'color' | 'image' | 'length' | 'length-percentage';

View File

@@ -1,20 +0,0 @@
import {CSSValue} from '../syntax/parser';
import {TokenType} from '../syntax/tokenizer';
import {ITypeDescriptor} from '../ITypeDescriptor';
import {Context} from '../../core/context';
export const time: ITypeDescriptor<number> = {
name: 'time',
parse: (_context: Context, value: CSSValue): number => {
if (value.type === TokenType.DIMENSION_TOKEN) {
switch (value.unit.toLowerCase()) {
case 's':
return 1000 * value.number;
case 'ms':
return value.number;
}
}
throw new Error(`Unsupported time type`);
}
};

View File

@@ -20,12 +20,10 @@ import {LIST_STYLE_TYPE, listStyleType} from '../css/property-descriptors/list-s
import {CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration} from '../css/index'; import {CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration} from '../css/index';
import {getQuote} from '../css/property-descriptors/quotes'; import {getQuote} from '../css/property-descriptors/quotes';
import {Context} from '../core/context'; import {Context} from '../core/context';
import {DebuggerType, isDebugging} from '../core/debugger';
export interface CloneOptions { export interface CloneOptions {
ignoreElements?: (element: Element) => boolean; ignoreElements?: (element: Element) => boolean;
onclone?: (document: Document, element: HTMLElement) => void; onclone?: (document: Document, element: HTMLElement) => void;
allowTaint?: boolean;
} }
export interface WindowOptions { export interface WindowOptions {
@@ -137,9 +135,6 @@ export class DocumentCloner {
} }
createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement { createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement {
if (isDebugging(node, DebuggerType.CLONE)) {
debugger;
}
if (isCanvasElement(node)) { if (isCanvasElement(node)) {
return this.createCanvasClone(node); return this.createCanvasClone(node);
} }
@@ -194,7 +189,7 @@ export class DocumentCloner {
img.src = canvas.toDataURL(); img.src = canvas.toDataURL();
return img; return img;
} catch (e) { } catch (e) {
this.context.logger.info(`Unable to inline canvas contents, canvas is tainted`, canvas); this.context.logger.info(`Unable to clone canvas contents, canvas is tainted`);
} }
} }
@@ -206,27 +201,14 @@ export class DocumentCloner {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const clonedCtx = clonedCanvas.getContext('2d'); const clonedCtx = clonedCanvas.getContext('2d');
if (clonedCtx) { if (clonedCtx) {
if (!this.options.allowTaint && ctx) { if (ctx) {
clonedCtx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0); clonedCtx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
} else { } else {
const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
if (gl) {
const attribs = gl.getContextAttributes();
if (attribs?.preserveDrawingBuffer === false) {
this.context.logger.warn(
'Unable to clone WebGL context as it has preserveDrawingBuffer=false',
canvas
);
}
}
clonedCtx.drawImage(canvas, 0, 0); clonedCtx.drawImage(canvas, 0, 0);
} }
} }
return clonedCanvas; return clonedCanvas;
} catch (e) { } catch (e) {}
this.context.logger.info(`Unable to clone canvas as it is tainted`, canvas);
}
return clonedCanvas; return clonedCanvas;
} }
@@ -244,7 +226,6 @@ export class DocumentCloner {
if (window && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) { if (window && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) {
const clone = this.createElementClone(node); const clone = this.createElementClone(node);
clone.style.transitionProperty = 'none';
const style = window.getComputedStyle(node); const style = window.getComputedStyle(node);
const styleBefore = window.getComputedStyle(node, ':before'); const styleBefore = window.getComputedStyle(node, ':before');

View File

@@ -3,44 +3,29 @@ import {TextContainer} from './text-container';
import {Bounds, parseBounds} from '../css/layout/bounds'; import {Bounds, parseBounds} from '../css/layout/bounds';
import {isHTMLElementNode} from './node-parser'; import {isHTMLElementNode} from './node-parser';
import {Context} from '../core/context'; import {Context} from '../core/context';
import {DebuggerType, isDebugging} from '../core/debugger';
export const enum FLAGS { export const enum FLAGS {
CREATES_STACKING_CONTEXT = 1 << 1, CREATES_STACKING_CONTEXT = 1 << 1,
CREATES_REAL_STACKING_CONTEXT = 1 << 2, CREATES_REAL_STACKING_CONTEXT = 1 << 2,
IS_LIST_OWNER = 1 << 3, IS_LIST_OWNER = 1 << 3
DEBUG_RENDER = 1 << 4
} }
export class ElementContainer { export class ElementContainer {
readonly styles: CSSParsedDeclaration; readonly styles: CSSParsedDeclaration;
readonly textNodes: TextContainer[] = []; readonly textNodes: TextContainer[];
readonly elements: ElementContainer[] = []; readonly elements: ElementContainer[];
bounds: Bounds; bounds: Bounds;
flags = 0; flags: number;
constructor(protected readonly context: Context, element: Element) { constructor(protected readonly context: Context, element: Element) {
if (isDebugging(element, DebuggerType.PARSE)) {
debugger;
}
this.styles = new CSSParsedDeclaration(context, window.getComputedStyle(element, null)); this.styles = new CSSParsedDeclaration(context, window.getComputedStyle(element, null));
this.textNodes = [];
if (isHTMLElementNode(element)) { this.elements = [];
if (this.styles.animationDuration.some((duration) => duration > 0)) { if (this.styles.transform !== null && isHTMLElementNode(element)) {
element.style.animationDuration = '0s'; // getBoundingClientRect takes transforms into account
} element.style.transform = 'none';
if (this.styles.transform !== null) {
// getBoundingClientRect takes transforms into account
element.style.transform = 'none';
}
} }
this.bounds = parseBounds(this.context, element); this.bounds = parseBounds(this.context, element);
this.flags = 0;
if (isDebugging(element, DebuggerType.RENDER)) {
this.flags |= FLAGS.DEBUG_RENDER;
}
} }
} }

View File

@@ -74,7 +74,6 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): Prom
const foreignObjectRendering = opts.foreignObjectRendering ?? false; const foreignObjectRendering = opts.foreignObjectRendering ?? false;
const cloneOptions: CloneConfigurations = { const cloneOptions: CloneConfigurations = {
allowTaint: opts.allowTaint ?? false,
onclone: opts.onclone, onclone: opts.onclone,
ignoreElements: opts.ignoreElements, ignoreElements: opts.ignoreElements,
inlineImages: foreignObjectRendering, inlineImages: foreignObjectRendering,

View File

@@ -1,6 +1,6 @@
import {ElementPaint, parseStackingContexts, StackingContext} from '../stacking-context'; import {ElementPaint, parseStackingContexts, StackingContext} from '../stacking-context';
import {asString, Color, isTransparent} from '../../css/types/color'; import {asString, Color, isTransparent} from '../../css/types/color';
import {ElementContainer, FLAGS} from '../../dom/element-container'; import {ElementContainer} from '../../dom/element-container';
import {BORDER_STYLE} from '../../css/property-descriptors/border-style'; import {BORDER_STYLE} from '../../css/property-descriptors/border-style';
import {CSSParsedDeclaration} from '../../css/index'; import {CSSParsedDeclaration} from '../../css/index';
import {TextContainer} from '../../dom/text-container'; import {TextContainer} from '../../dom/text-container';
@@ -19,6 +19,7 @@ import {
import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background'; import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background';
import {isDimensionToken} from '../../css/syntax/parser'; import {isDimensionToken} from '../../css/syntax/parser';
import {TextBounds} from '../../css/layout/text'; import {TextBounds} from '../../css/layout/text';
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {ImageElementContainer} from '../../dom/replaced-elements/image-element-container'; import {ImageElementContainer} from '../../dom/replaced-elements/image-element-container';
import {contentBox} from '../box-sizing'; import {contentBox} from '../box-sizing';
import {CanvasElementContainer} from '../../dom/replaced-elements/canvas-element-container'; import {CanvasElementContainer} from '../../dom/replaced-elements/canvas-element-container';
@@ -43,8 +44,6 @@ import {TextShadow} from '../../css/property-descriptors/text-shadow';
import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order'; import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order';
import {Renderer} from '../renderer'; import {Renderer} from '../renderer';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
import {DIRECTION} from '../../css/property-descriptors/direction';
import {splitGraphemes} from 'text-segmentation';
export type RenderConfigurations = RenderOptions & { export type RenderConfigurations = RenderOptions & {
backgroundColor: Color | null; backgroundColor: Color | null;
@@ -87,12 +86,12 @@ export class CanvasRenderer extends Renderer {
); );
} }
applyEffects(effects: IElementEffect[]): void { applyEffects(effects: IElementEffect[], target: EffectTarget): void {
while (this._activeEffects.length) { while (this._activeEffects.length) {
this.popEffect(); this.popEffect();
} }
effects.forEach((effect) => this.applyEffect(effect)); effects.filter((effect) => contains(effect.target, target)).forEach((effect) => this.applyEffect(effect));
} }
applyEffect(effect: IElementEffect): void { applyEffect(effect: IElementEffect): void {
@@ -135,10 +134,6 @@ export class CanvasRenderer extends Renderer {
} }
async renderNode(paint: ElementPaint): Promise<void> { async renderNode(paint: ElementPaint): Promise<void> {
if (contains(paint.container.flags, FLAGS.DEBUG_RENDER)) {
debugger;
}
if (paint.container.styles.isVisible()) { if (paint.container.styles.isVisible()) {
await this.renderNodeBackgroundAndBorders(paint); await this.renderNodeBackgroundAndBorders(paint);
await this.renderNodeContent(paint); await this.renderNodeContent(paint);
@@ -149,7 +144,7 @@ export class CanvasRenderer extends Renderer {
if (letterSpacing === 0) { if (letterSpacing === 0) {
this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + baseline); this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + baseline);
} else { } else {
const letters = splitGraphemes(text.text); const letters = toCodePoints(text.text).map((i) => fromCodePoint(i));
letters.reduce((left, letter) => { letters.reduce((left, letter) => {
this.ctx.fillText(letter, left, text.bounds.top + baseline); this.ctx.fillText(letter, left, text.bounds.top + baseline);
@@ -179,8 +174,6 @@ export class CanvasRenderer extends Renderer {
this.ctx.font = font; this.ctx.font = font;
this.ctx.direction = styles.direction === DIRECTION.RTL ? 'rtl' : 'ltr';
this.ctx.textAlign = 'left';
this.ctx.textBaseline = 'alphabetic'; this.ctx.textBaseline = 'alphabetic';
const {baseline, middle} = this.fontMetrics.getMetrics(fontFamily, fontSize); const {baseline, middle} = this.fontMetrics.getMetrics(fontFamily, fontSize);
const paintOrder = styles.paintOrder; const paintOrder = styles.paintOrder;
@@ -293,7 +286,7 @@ export class CanvasRenderer extends Renderer {
} }
async renderNodeContent(paint: ElementPaint): Promise<void> { async renderNodeContent(paint: ElementPaint): Promise<void> {
this.applyEffects(paint.getEffects(EffectTarget.CONTENT)); this.applyEffects(paint.effects, EffectTarget.CONTENT);
const container = paint.container; const container = paint.container;
const curves = paint.curves; const curves = paint.curves;
const styles = container.styles; const styles = container.styles;
@@ -472,9 +465,6 @@ export class CanvasRenderer extends Renderer {
} }
async renderStackContent(stack: StackingContext): Promise<void> { async renderStackContent(stack: StackingContext): Promise<void> {
if (contains(stack.element.container.flags, FLAGS.DEBUG_RENDER)) {
debugger;
}
// https://www.w3.org/TR/css-position-3/#painting-order // https://www.w3.org/TR/css-position-3/#painting-order
// 1. the background and borders of the element forming the stacking context. // 1. the background and borders of the element forming the stacking context.
await this.renderNodeBackgroundAndBorders(stack.element); await this.renderNodeBackgroundAndBorders(stack.element);
@@ -692,7 +682,7 @@ export class CanvasRenderer extends Renderer {
} }
async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise<void> { async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise<void> {
this.applyEffects(paint.getEffects(EffectTarget.BACKGROUND_BORDERS)); this.applyEffects(paint.effects, EffectTarget.BACKGROUND_BORDERS);
const styles = paint.container.styles; const styles = paint.container.styles;
const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length; const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
@@ -906,7 +896,7 @@ export class CanvasRenderer extends Renderer {
const stack = parseStackingContexts(element); const stack = parseStackingContexts(element);
await this.renderStack(stack); await this.renderStack(stack);
this.applyEffects([]); this.applyEffects([], EffectTarget.BACKGROUND_BORDERS);
return this.canvas; return this.canvas;
} }
} }

View File

@@ -20,21 +20,36 @@ export interface IElementEffect {
export class TransformEffect implements IElementEffect { export class TransformEffect implements IElementEffect {
readonly type: EffectType = EffectType.TRANSFORM; readonly type: EffectType = EffectType.TRANSFORM;
readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT;
readonly offsetX: number;
readonly offsetY: number;
readonly matrix: Matrix;
constructor(readonly offsetX: number, readonly offsetY: number, readonly matrix: Matrix) {} constructor(offsetX: number, offsetY: number, matrix: Matrix) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.matrix = matrix;
}
} }
export class ClipEffect implements IElementEffect { export class ClipEffect implements IElementEffect {
readonly type: EffectType = EffectType.CLIP; readonly type: EffectType = EffectType.CLIP;
readonly target: number;
readonly path: Path[];
constructor(readonly path: Path[], readonly target: EffectTarget) {} constructor(path: Path[], target: EffectTarget) {
this.target = target;
this.path = path;
}
} }
export class OpacityEffect implements IElementEffect { export class OpacityEffect implements IElementEffect {
readonly type: EffectType = EffectType.OPACITY; readonly type: EffectType = EffectType.OPACITY;
readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT;
readonly opacity: number;
constructor(readonly opacity: number) {} constructor(opacity: number) {
this.opacity = opacity;
}
} }
export const isTransformEffect = (effect: IElementEffect): effect is TransformEffect => export const isTransformEffect = (effect: IElementEffect): effect is TransformEffect =>

View File

@@ -1,14 +1,13 @@
import {ElementContainer, FLAGS} from '../dom/element-container'; import {ElementContainer, FLAGS} from '../dom/element-container';
import {contains} from '../core/bitwise'; import {contains} from '../core/bitwise';
import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves'; import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves';
import {ClipEffect, EffectTarget, IElementEffect, isClipEffect, OpacityEffect, TransformEffect} from './effects'; import {ClipEffect, EffectTarget, IElementEffect, OpacityEffect, TransformEffect} from './effects';
import {OVERFLOW} from '../css/property-descriptors/overflow'; import {OVERFLOW} from '../css/property-descriptors/overflow';
import {equalPath} from './path'; import {equalPath} from './path';
import {DISPLAY} from '../css/property-descriptors/display'; import {DISPLAY} from '../css/property-descriptors/display';
import {OLElementContainer} from '../dom/elements/ol-element-container'; import {OLElementContainer} from '../dom/elements/ol-element-container';
import {LIElementContainer} from '../dom/elements/li-element-container'; import {LIElementContainer} from '../dom/elements/li-element-container';
import {createCounterText} from '../css/types/functions/counter'; import {createCounterText} from '../css/types/functions/counter';
import {POSITION} from '../css/property-descriptors/position';
export class StackingContext { export class StackingContext {
element: ElementPaint; element: ElementPaint;
@@ -33,24 +32,27 @@ export class StackingContext {
} }
export class ElementPaint { export class ElementPaint {
readonly effects: IElementEffect[] = []; container: ElementContainer;
readonly curves: BoundCurves; effects: IElementEffect[];
curves: BoundCurves;
listValue?: string; listValue?: string;
constructor(readonly container: ElementContainer, readonly parent: ElementPaint | null) { constructor(element: ElementContainer, parentStack: IElementEffect[]) {
this.curves = new BoundCurves(this.container); this.container = element;
if (this.container.styles.opacity < 1) { this.effects = parentStack.slice(0);
this.effects.push(new OpacityEffect(this.container.styles.opacity)); this.curves = new BoundCurves(element);
if (element.styles.opacity < 1) {
this.effects.push(new OpacityEffect(element.styles.opacity));
} }
if (this.container.styles.transform !== null) { if (element.styles.transform !== null) {
const offsetX = this.container.bounds.left + this.container.styles.transformOrigin[0].number; const offsetX = element.bounds.left + element.styles.transformOrigin[0].number;
const offsetY = this.container.bounds.top + this.container.styles.transformOrigin[1].number; const offsetY = element.bounds.top + element.styles.transformOrigin[1].number;
const matrix = this.container.styles.transform; const matrix = element.styles.transform;
this.effects.push(new TransformEffect(offsetX, offsetY, matrix)); this.effects.push(new TransformEffect(offsetX, offsetY, matrix));
} }
if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) { if (element.styles.overflowX !== OVERFLOW.VISIBLE) {
const borderBox = calculateBorderBoxPath(this.curves); const borderBox = calculateBorderBoxPath(this.curves);
const paddingBox = calculatePaddingBoxPath(this.curves); const paddingBox = calculatePaddingBoxPath(this.curves);
@@ -63,32 +65,16 @@ export class ElementPaint {
} }
} }
getEffects(target: EffectTarget): IElementEffect[] { getParentEffects(): IElementEffect[] {
let inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) === -1;
let parent = this.parent;
const effects = this.effects.slice(0); const effects = this.effects.slice(0);
while (parent) { if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) {
const croplessEffects = parent.effects.filter((effect) => !isClipEffect(effect)); const borderBox = calculateBorderBoxPath(this.curves);
if (inFlow || parent.container.styles.position !== POSITION.STATIC || !parent.parent) { const paddingBox = calculatePaddingBoxPath(this.curves);
effects.unshift(...croplessEffects); if (!equalPath(borderBox, paddingBox)) {
inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(parent.container.styles.position) === -1; effects.push(new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT));
if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) {
const borderBox = calculateBorderBoxPath(parent.curves);
const paddingBox = calculatePaddingBoxPath(parent.curves);
if (!equalPath(borderBox, paddingBox)) {
effects.unshift(
new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)
);
}
}
} else {
effects.unshift(...croplessEffects);
} }
parent = parent.parent;
} }
return effects;
return effects.filter((effect) => contains(effect.target, target));
} }
} }
@@ -101,7 +87,7 @@ const parseStackTree = (
parent.container.elements.forEach((child) => { parent.container.elements.forEach((child) => {
const treatAsRealStackingContext = contains(child.flags, FLAGS.CREATES_REAL_STACKING_CONTEXT); const treatAsRealStackingContext = contains(child.flags, FLAGS.CREATES_REAL_STACKING_CONTEXT);
const createsStackingContext = contains(child.flags, FLAGS.CREATES_STACKING_CONTEXT); const createsStackingContext = contains(child.flags, FLAGS.CREATES_STACKING_CONTEXT);
const paintContainer = new ElementPaint(child, parent); const paintContainer = new ElementPaint(child, parent.getParentEffects());
if (contains(child.styles.display, DISPLAY.LIST_ITEM)) { if (contains(child.styles.display, DISPLAY.LIST_ITEM)) {
listItems.push(paintContainer); listItems.push(paintContainer);
} }
@@ -196,7 +182,7 @@ const processListItems = (owner: ElementContainer, elements: ElementPaint[]) =>
}; };
export const parseStackingContexts = (container: ElementContainer): StackingContext => { export const parseStackingContexts = (container: ElementContainer): StackingContext => {
const paintContainer = new ElementPaint(container, null); const paintContainer = new ElementPaint(container, []);
const root = new StackingContext(paintContainer); const root = new StackingContext(paintContainer);
const listItems: ElementPaint[] = []; const listItems: ElementPaint[] = [];
parseStackTree(paintContainer, root, root, listItems); parseStackTree(paintContainer, root, root, listItems);

View File

@@ -5,93 +5,46 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../test.js"></script> <script type="text/javascript" src="../test.js"></script>
<style> <style>
span {
color:blue;
}
p {
background-color: green;
}
div {
background: red;
border: 5px solid blue;
animation: spin 3s linear 1s infinite;
}
body { body {
font-family: Arial; font-family: Arial;
} }
@keyframes rotate0 { @-webkit-keyframes spin {
0% { 0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); transform: rotate(0deg);
} /* Firefox 16+, IE 10+, Opera */ }
} 100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */ } }
@keyframes rotate45 { @keyframes spin {
0% { 0% {
transform: rotate(45deg); -webkit-transform: rotate(0deg);
} transform: rotate(0deg);
} /* Firefox 16+, IE 10+, Opera */ }
100% {
p { -webkit-transform: rotate(360deg);
font: 22px/1 Arial, sans-serif; transform: rotate(360deg);
position: absolute; /* Firefox 16+, IE 10+, Opera */ } }
top: 25%;
left: 25%;
width: 50%;
height: 50%;
color: #fff;
background-color: #666;
line-height: 90px;
text-align: center;
}
.transformed.working p {
transform: rotate(45deg);
}
.animated.working p {
animation-name: rotate0;
animation-duration: 1ms, 1ms;
animation-play-state: paused;
}
.animated.broken p {
animation-name: rotate45;
animation-duration: 1ms;
animation-play-state: paused;
}
.transitioned p {
transition: 1ms, 1ms;
transform: rotate(45deg)
}
.transition-delay {
transition: 1ms;
transition-delay: 50ms;
transform: rotate(45deg)
}
div {
float: left;
clear: left;
margin-right: 10px;
background-color: #ccc;
width: 180px;
height: 180px;
position: relative;
}
</style> </style>
</head> </head>
<body> <body>
<div class="transformed working"> <div style="clip: rect(0px, 400px, 50px, 200px); ">Some inline text <span> followed by text in span </span> followed by more inline text.
<p>Hello</p> <p>Then a block level element.</p>
</div> Then more inline text.</div>
<div class="animated working">
<p>Hello</p>
</div>
<div class="animated broken">
<p>Hello</p>
</div>
<div class="transitioned broken">
<p>Hello</p>
</div>
<div class="transition-delay">
<p>Hello</p>
</div>
</body> </body>
</html> </html>

View File

@@ -51,9 +51,6 @@
.scroll { .scroll {
overflow: scroll; overflow: scroll;
} }
.clip {
overflow: clip;
}
.auto { .auto {
overflow: auto; overflow: auto;
} }
@@ -73,10 +70,6 @@
scroll scroll
<p class="scroll">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p> <p class="scroll">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div> </div>
<div class="cell">
clip
<p class="clip">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div>
<div class="cell"> <div class="cell">
auto auto
<p class="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p> <p class="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
@@ -136,17 +129,5 @@
</script> </script>
</div> </div>
<div class="hidden">Hidden<div style="opacity: 0.5">With opacity</div></div> <div class="hidden">Hidden<div style="opacity: 0.5">With opacity</div></div>
<div class="hidden" style="height: 0px; width: 50px;">
<div style="position: absolute; width: 50px; height: 50px; left:400px;">absolute on static parent</div>
</div>
<div class="hidden" style="height: 0px; width: 50px;">
<div style="position: relative; width: 10px; height: 10px; left:400px;">relative on static parent</div>
</div>
<div class="hidden" style="height: 0px; width: 50px;">
<div style="position: fixed; width: 50px; height: 50px; left:400px; top:0;">fixed on static parent</div>
</div>
<div class="hidden" style="height: 0px; width: 50px;position: relative;">
<div style="position: absolute; width: 10px; height: 10px; left:400px;">absolute on relative parent</div>
</div>
</body> </body>
</html> </html>

View File

@@ -86,20 +86,6 @@
of the section counter, separated of the section counter, separated
by a period */ by a period */
} }
.issue-2639 {
display: flex;
}
.issue-2639::before {
content: counter(ol0) '. ';
counter-increment: ol0;
}
.issue-2639:first-child {
counter-reset: ol0;
}
</style> </style>
</head> </head>
<body> <body>
@@ -177,10 +163,5 @@
<li>item</li> <!-- 2 --> <li>item</li> <!-- 2 -->
</ol> </ol>
<ol>
<li class="issue-2639">one</li>
<li class="issue-2639">two</li>
</ol>
</body> </body>
</html> </html>

View File

@@ -149,6 +149,5 @@
<span>[AB / CD]</span> <span>[AB / CD]</span>
</div> </div>
<div>Emojis 🤷🏾‍♂️👨‍👩‍👧‍👦 :)</div> <div>Emojis 🤷🏾‍♂️👨‍👩‍👧‍👦 :)</div>
<div style="letter-spacing: 2px">Emojis with letter-spacing 🤷🏾‍♂️👨‍👩‍👧‍👦 :)</div>
</body> </body>
</html> </html>

View File

@@ -8,15 +8,14 @@ import {ScreenshotRequest} from './types';
// @ts-ignore // @ts-ignore
window.Promise = Promise; window.Promise = Promise;
const testRunnerUrl = location.href; const testRunnerUrl = location.href;
const hasHistoryApi = typeof window.history !== 'undefined' && typeof window.history.replaceState !== 'undefined';
const uploadResults = (canvas: HTMLCanvasElement, url: string) => { const uploadResults = (canvas: HTMLCanvasElement, url: string) => {
return new Promise((resolve: () => void, reject: (error: string) => void) => { // @ts-ignore
// @ts-ignore return new Promise((resolve: () => void, reject: (error: any) => void) => {
const xhr = 'withCredentials' in new XMLHttpRequest() ? new XMLHttpRequest() : new XDomainRequest(); const xhr = new XMLHttpRequest();
xhr.onload = () => { xhr.onload = () => {
if (typeof xhr.status !== 'number' || xhr.status === 200) { if (xhr.status === 200) {
resolve(); resolve();
} else { } else {
reject(`Failed to send screenshot with status ${xhr.status}`); reject(`Failed to send screenshot with status ${xhr.status}`);
@@ -62,21 +61,17 @@ testList
testContainer.onload = () => done(); testContainer.onload = () => done();
testContainer.src = url + '?selenium&run=false&reftest&' + Math.random(); testContainer.src = url + '?selenium&run=false&reftest&' + Math.random();
if (hasHistoryApi) { // Chrome does not resolve relative background urls correctly inside of a nested iframe
// Chrome does not resolve relative background urls correctly inside of a nested iframe try {
try { history.replaceState(null, '', url);
history.replaceState(null, '', url); } catch (e) {}
} catch (e) {}
}
document.body.appendChild(testContainer); document.body.appendChild(testContainer);
}); });
after(() => { after(() => {
if (hasHistoryApi) { try {
try { history.replaceState(null, '', testRunnerUrl);
history.replaceState(null, '', testRunnerUrl); } catch (e) {}
} catch (e) {}
}
document.body.removeChild(testContainer); document.body.removeChild(testContainer);
}); });

View File

@@ -32,10 +32,8 @@
"license": "MIT", "license": "MIT",
"main": "n/a", "main": "n/a",
"scripts": { "scripts": {
"copy:build": "mkdirp public/dist && cpy ../dist/*.js public/dist", "copybuild": "mkdirp public/dist && cpy ../dist/*.js public/dist",
"copy:src": "mkdirp public/src && cpy ../src/**/*.ts public/src --parents", "build": "npm run copybuild && gatsby build",
"copy": "npm run copy:build && npm run copy:src",
"build": "npm run copy && gatsby build",
"start": "gatsby develop", "start": "gatsby develop",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
} }