Compare commits

..

1 Commits

Author SHA1 Message Date
eca5033705 feat: drop support for IE9 2021-08-09 18:31:10 +08:00
58 changed files with 2150 additions and 1397 deletions

View File

@ -101,29 +101,22 @@ jobs:
- os: macos-latest - os: macos-latest
name: OSX Safari Stable name: OSX Safari Stable
targetBrowser: Safari_Stable targetBrowser: Safari_Stable
- os: macos-10.15 - os: macos-latest
name: iOS Simulator Safari 12 name: iOS Simulator Safari 12
targetBrowser: Safari_IOS_12 targetBrowser: Safari_IOS_12
xcode: /Applications/Xcode_10.3.app xcode: /Applications/Xcode_10.3.app
- os: macos-10.15 - os: macos-latest
name: iOS Simulator Safari 13 name: iOS Simulator Safari 13
targetBrowser: Safari_IOS_13 targetBrowser: Safari_IOS_13
xcode: /Applications/Xcode_11.7.app xcode: /Applications/Xcode_11.6_beta.app
- os: macos-10.15 - os: macos-latest
name: iOS Simulator Safari 14 name: iOS Simulator Safari 14
targetBrowser: Safari_IOS_14 targetBrowser: Safari_IOS_14
xcode: /Applications/Xcode_12.4.app xcode: /Applications/Xcode_12_beta.app
- os: macos-11
name: iOS Simulator Safari 15.0
targetBrowser: Safari_IOS_15_0
xcode: /Applications/Xcode_13.0.app
- os: macos-11 - os: macos-11
name: iOS Simulator Safari 15 name: iOS Simulator Safari 15
targetBrowser: Safari_IOS_15 targetBrowser: Safari_IOS_15
xcode: /Applications/Xcode_13.2.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,136 +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.4.1](https://github.com/niklasvh/html2canvas/compare/v1.4.0...v1.4.1) (2022-01-22)
### deps
* fix source maps (#2812) ([67c5e8d](https://github.com/niklasvh/html2canvas/commit/67c5e8dec4b2af9260a2b5b75b3399495fd1fee9)), closes [#2812](https://github.com/niklasvh/html2canvas/issues/2812)
### feat
* add support for <video> elements (#2788) ([181d1b1](https://github.com/niklasvh/html2canvas/commit/181d1b1103910d6e1b5277d5c007fc5e3006c6bf)), closes [#2788](https://github.com/niklasvh/html2canvas/issues/2788)
### fix
* Properties x and y of BoundingRect is undefined in old browser (#2797) ([e587a82](https://github.com/niklasvh/html2canvas/commit/e587a82dca01d9ada78cae34fd1bdb934e547f9b)), closes [#2797](https://github.com/niklasvh/html2canvas/issues/2797)
* source maps (#2787) ([46db867](https://github.com/niklasvh/html2canvas/commit/46db86755f064828559a4b0b37310f3ae94f5494)), closes [#2787](https://github.com/niklasvh/html2canvas/issues/2787)
# [1.4.0](https://github.com/niklasvh/html2canvas/compare/v1.3.4...v1.4.0) (2022-01-01)
### feat
* use native text segmenter where available (#2782) ([6521a48](https://github.com/niklasvh/html2canvas/commit/6521a487d78172f7179f7c973c1a3af40eb92009)), closes [#2782](https://github.com/niklasvh/html2canvas/issues/2782)
### fix
* adopted stylesheets (#2785) ([74696fa](https://github.com/niklasvh/html2canvas/commit/74696faf47c07b48b9c9587db0b999da1c08a8be)), closes [#2785](https://github.com/niklasvh/html2canvas/issues/2785)
* ios text wrapping with 0 width rect (#2786) ([0476d06](https://github.com/niklasvh/html2canvas/commit/0476d065158c33d2020a9f602b3043e5e2f90c75)), closes [#2786](https://github.com/niklasvh/html2canvas/issues/2786)
* reduce SLICE_STACK_SIZE to 50k (#2784) ([1cc853a](https://github.com/niklasvh/html2canvas/commit/1cc853a3186853eaca00af060f651697dc3497a9)), closes [#2784](https://github.com/niklasvh/html2canvas/issues/2784)
## [1.3.4](https://github.com/niklasvh/html2canvas/compare/v1.3.3...v1.3.4) (2021-12-29)
### ci
* add ios 15.0 testing (#2780) ([d922207](https://github.com/niklasvh/html2canvas/commit/d9222075daaed08884491b0563fc899ee0ced731)), closes [#2780](https://github.com/niklasvh/html2canvas/issues/2780)
### fix
* ios 15 font rendering crash (#2645) ([ba2b1cd](https://github.com/niklasvh/html2canvas/commit/ba2b1cd8e9a9d7932675d7abffce1526a609e769)), closes [#2645](https://github.com/niklasvh/html2canvas/issues/2645)
## [1.3.3](https://github.com/niklasvh/html2canvas/compare/v1.3.2...v1.3.3) (2021-11-23)
### ci
* fix macos action runners (#2757) ([ed57781](https://github.com/niklasvh/html2canvas/commit/ed577815949b6a565df54f2101cf6f0fb731c290)), closes [#2757](https://github.com/niklasvh/html2canvas/issues/2757)
### fix
* "offsets" text when font is big ([fd22a01](https://github.com/niklasvh/html2canvas/commit/fd22a01a3c9e39293f47caaed0c0e13d2dc8170c))
* const enums (#2651) ([eeda86b](https://github.com/niklasvh/html2canvas/commit/eeda86bd5e81fb4e97675fe9bee3d4d15899997f)), closes [#2651](https://github.com/niklasvh/html2canvas/issues/2651)
## [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)
@ -271,14 +141,9 @@ All notable changes to this project will be documented in this file. See [standa
* update www deps (#2525) ([2a013e2](https://github.com/niklasvh/html2canvas/commit/2a013e20c814b7dbaea98f54f0bde8f553eb79a2)), closes [#2525](https://github.com/niklasvh/html2canvas/issues/2525) * update www deps (#2525) ([2a013e2](https://github.com/niklasvh/html2canvas/commit/2a013e20c814b7dbaea98f54f0bde8f553eb79a2)), closes [#2525](https://github.com/niklasvh/html2canvas/issues/2525)
### feat
* add support for border-style dashed, dotted, double (#2531) ([72cd528](https://github.com/niklasvh/html2canvas/commit/72cd5284296e4cdb3fe88f2982ec7528604b6618))
### fix ### fix
* opacity with overflow hidden (#2450) ([82b7da5](https://github.com/niklasvh/html2canvas/commit/82b7da558c342e7f53d298bb1d843a5db86c3b21)), closes [#2450](https://github.com/niklasvh/html2canvas/issues/2450) * opacity with overflow hidden (#2450) ([82b7da5](https://github.com/niklasvh/html2canvas/commit/82b7da558c342e7f53d298bb1d843a5db86c3b21)), closes [#2450](https://github.com/niklasvh/html2canvas/issues/2450)
* top right border radius (#2522) ([ba17267](https://github.com/niklasvh/html2canvas/commit/ba172678f07f962e9f54b398df087e86217d7a13))
### test ### test
@ -426,16 +291,16 @@ All notable changes to this project will be documented in this file. See [standa
* Fix white space appearing on element rendering (Fix #1438) * Fix white space appearing on element rendering (Fix #1438)
* Reset canvas transform on finish (Fix #1494) * Reset canvas transform on finish (Fix #1494)
# v1.0.0-alpha.11 - 1.4.2018 # v1.0.0-alpha.11 - 1.4.2018
* Fix IE11 member not found error * Fix IE11 member not found error
* Support blob image resources in non-foreignObjectRendering mode * Support blob image resources in non-foreignObjectRendering mode
# v1.0.0-alpha.10 - 15.2.2018 # v1.0.0-alpha.10 - 15.2.2018
* Re-introduce `onclone` option (Fix #1434) * Re-introduce `onclone` option (Fix #1434)
* Add `ignoreElements` predicate function option * Add `ignoreElements` predicate function option
* Fix version console logging * Fix version console logging
# v1.0.0-alpha.9 - 7.1.2018 # v1.0.0-alpha.9 - 7.1.2018
* Fix dynamic style sheets * Fix dynamic style sheets
* Fix > 50% border-radius values * Fix > 50% border-radius values
@ -447,7 +312,7 @@ All notable changes to this project will be documented in this file. See [standa
* Fix form input rendering (#1338) * Fix form input rendering (#1338)
* Improve word line breaking algorithm * Improve word line breaking algorithm
# v1.0.0-alpha.6 - 28.12.2017 # v1.0.0-alpha.6 - 28.12.2017
* Fix list-style: none (#1340) * Fix list-style: none (#1340)
* Extend supported values for pseudo element content * Extend supported values for pseudo element content
@ -457,7 +322,7 @@ All notable changes to this project will be documented in this file. See [standa
* Fix overflow: auto * Fix overflow: auto
* Added support for rendering list-style * Added support for rendering list-style
v1.0.0-alpha.4 - 12.12.2017 v1.0.0-alpha.4 - 12.12.2017
* Fix rendering with multiple fonts defined (Fix #796) * Fix rendering with multiple fonts defined (Fix #796)
* Add support for radial-gradients * Add support for radial-gradients
* Fix logging option (#1302) * Fix logging option (#1302)
@ -479,8 +344,8 @@ All notable changes to this project will be documented in this file. See [standa
##### Breaking Changes ##### ##### Breaking Changes #####
* Remove deprecated onrendered callback, calling `html2canvas` returns a `Promise<HTMLCanvasElement>` * Remove deprecated onrendered callback, calling `html2canvas` returns a `Promise<HTMLCanvasElement>`
* Removed option `type`, same results can be achieved by assigning `x`, `y`, `scrollX`, `scrollY`, `width` and `height` properties. * Removed option `type`, same results can be achieved by assigning `x`, `y`, `scrollX`, `scrollY`, `width` and `height` properties.
## New featues / fixes ## New featues / fixes
* Add support for scaling canvas (defaults to device pixel ratio) * Add support for scaling canvas (defaults to device pixel ratio)
* Add support for multiple text-shadows * Add support for multiple text-shadows
* Add support for multiple text-decorations * Add support for multiple text-decorations
@ -489,7 +354,7 @@ All notable changes to this project will be documented in this file. See [standa
* Correctly handle px and percentage values in linear-gradients * Correctly handle px and percentage values in linear-gradients
* Correctly support all angle types for linear-gradients * Correctly support all angle types for linear-gradients
* Add support for multiple values for background-repeat, background-position and background-size * Add support for multiple values for background-repeat, background-position and background-size
# v0.5.0-beta4 - 23.1.2016 # v0.5.0-beta4 - 23.1.2016
* Fix logger requiring access to window object * Fix logger requiring access to window object
* Derequire browserify build * Derequire browserify build
@ -509,11 +374,11 @@ All notable changes to this project will be documented in this file. See [standa
* Fix transparent colors breaking gradients * Fix transparent colors breaking gradients
* Preserve scrolling positions on render * Preserve scrolling positions on render
# v0.5.0-alpha2 - 3.2.2015 # v0.5.0-alpha2 - 3.2.2015
* Switch to using browserify for building * Switch to using browserify for building
* Fix (#517) Chrome stretches background images with 'auto' or single attributes * Fix (#517) Chrome stretches background images with 'auto' or single attributes
# v0.5.0-alpha - 19.1.2015 # v0.5.0-alpha - 19.1.2015
* Complete rewrite of library * Complete rewrite of library
* Switched interface to return Promise * Switched interface to return Promise
* Uses hidden iframe window to perform rendering, allowing async rendering and doesn't force the viewport to be scrolled to the top anymore. * Uses hidden iframe window to perform rendering, allowing async rendering and doesn't force the viewport to be scrolled to the top anymore.
@ -524,14 +389,14 @@ All notable changes to this project will be documented in this file. See [standa
* Changed format for proxy requests, permitting binary responses with CORS headers as well * Changed format for proxy requests, permitting binary responses with CORS headers as well
* Fixed many layering issues (see z-index tests) * Fixed many layering issues (see z-index tests)
# v0.4.1 - 7.9.2013 # v0.4.1 - 7.9.2013
* Added support for bower * Added support for bower
* Improved z-index ordering * Improved z-index ordering
* Basic implementation for CSS transformations * Basic implementation for CSS transformations
* Fixed inline text in top element * Fixed inline text in top element
* Basic implementation for text-shadow * Basic implementation for text-shadow
# v0.4.0 - 30.1.2013 # v0.4.0 - 30.1.2013
* Added rendering tests with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a> * Added rendering tests with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a>
* Switched to using grunt for building * Switched to using grunt for building
* Removed support for IE<9, including any FlashCanvas bits * Removed support for IE<9, including any FlashCanvas bits
@ -541,7 +406,7 @@ All notable changes to this project will be documented in this file. See [standa
* Support for placeholder rendering * Support for placeholder rendering
* Reformatted all tests to small units to test specific features * Reformatted all tests to small units to test specific features
# v0.3.4 - 26.6.2012 # v0.3.4 - 26.6.2012
* Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>) * Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>)
* SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>) * SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>)

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

@ -34,31 +34,19 @@ module.exports = function(config) {
base: 'MobileSafari', base: 'MobileSafari',
name: 'iPhone 8', name: 'iPhone 8',
platform: 'iOS', platform: 'iOS',
sdk: '13.7' sdk: '13.6'
}, },
Safari_IOS_14: { Safari_IOS_14: {
base: 'MobileSafari', base: 'MobileSafari',
name: 'iPhone 8', name: 'iPhone 8',
platform: 'iOS', platform: 'iOS',
sdk: '14.4' sdk: '14.0'
},
Safari_IOS_15_0: {
base: 'MobileSafari',
name: 'iPhone 13',
platform: 'iOS',
sdk: '15.0'
}, },
Safari_IOS_15: { Safari_IOS_15: {
base: 'MobileSafari', base: 'MobileSafari',
name: 'iPhone 13', name: 'iPhone 8',
platform: 'iOS', platform: 'iOS',
sdk: '15.2' sdk: '15.0'
},
SauceLabs_IE9: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '9.0',
platform: 'Windows 7'
}, },
SauceLabs_IE10: { SauceLabs_IE10: {
base: 'SauceLabs', base: 'SauceLabs',
@ -99,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',

2571
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.4.1", "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.1.0", "css-line-break": "2.0.1"
"text-segmentation": "^1.0.3"
} }
} }

View File

@ -30,7 +30,7 @@ export default {
// Allow json resolution // Allow json resolution
json(), json(),
// Compile TypeScript files // Compile TypeScript files
typescript({ sourceMap: true, inlineSources: true }), typescript(),
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
commonjs({ commonjs({
include: 'node_modules/**' include: 'node_modules/**'

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

@ -211,12 +211,5 @@ export const FEATURES = {
const value = 'withCredentials' in new XMLHttpRequest(); const value = 'withCredentials' in new XMLHttpRequest();
Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', {value}); Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', {value});
return value; return value;
},
get SUPPORT_NATIVE_TEXT_SEGMENTATION(): boolean {
'use strict';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value = !!(typeof Intl !== 'undefined' && (Intl as any).Segmenter);
Object.defineProperty(FEATURES, 'SUPPORT_NATIVE_TEXT_SEGMENTATION', {value});
return value;
} }
}; };

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

@ -17,11 +17,11 @@ export class Bounds {
} }
static fromDOMRectList(context: Context, domRectList: DOMRectList): Bounds { static fromDOMRectList(context: Context, domRectList: DOMRectList): Bounds {
const domRect = Array.from(domRectList).find((rect) => rect.width !== 0); const domRect = domRectList[0];
return domRect return domRect
? new Bounds( ? new Bounds(
domRect.left + context.windowBounds.left, domRect.x + context.windowBounds.left,
domRect.top + context.windowBounds.top, domRect.y + context.windowBounds.top,
domRect.width, domRect.width,
domRect.height domRect.height
) )

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';
@ -28,24 +27,15 @@ export const parseTextBounds = (
textList.forEach((text) => { textList.forEach((text) => {
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) {
const clientRects = createRange(node, offset, text.length).getClientRects(); if (!FEATURES.SUPPORT_WORD_BREAKING) {
if (clientRects.length > 1) { textBounds.push(
const subSegments = segmentGraphemes(text); new TextBounds(
let subOffset = 0; text,
subSegments.forEach((subSegment) => { Bounds.fromDOMRectList(context, createRange(node, offset, text.length).getClientRects())
textBounds.push( )
new TextBounds( );
subSegment,
Bounds.fromDOMRectList(
context,
createRange(node, subOffset + offset, subSegment.length).getClientRects()
)
)
);
subOffset += subSegment.length;
});
} else { } else {
textBounds.push(new TextBounds(text, Bounds.fromDOMRectList(context, clientRects))); textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length)));
} }
} else { } else {
const replacementNode = node.splitText(text.length); const replacementNode = node.splitText(text.length);
@ -91,32 +81,12 @@ const createRange = (node: Text, offset: number, length: number): Range => {
return range; return range;
}; };
export const segmentGraphemes = (value: string): string[] => { const getRangeBounds = (context: Context, node: Text, offset: number, length: number): Bounds => {
if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) { return Bounds.fromClientRect(context, createRange(node, offset, length).getBoundingClientRect());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const segmenter = new (Intl as any).Segmenter(void 0, {granularity: 'grapheme'});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Array.from(segmenter.segment(value)).map((segment: any) => segment.segment);
}
return splitGraphemes(value);
};
const segmentWords = (value: string, styles: CSSParsedDeclaration): string[] => {
if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const segmenter = new (Intl as any).Segmenter(void 0, {
granularity: 'word'
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Array.from(segmenter.segment(value)).map((segment: any) => segment.segment);
}
return breakWords(value, styles);
}; };
const breakText = (value: string, styles: CSSParsedDeclaration): string[] => { const breakText = (value: string, styles: CSSParsedDeclaration): string[] => {
return styles.letterSpacing !== 0 ? segmentGraphemes(value) : segmentWords(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,7 +1,7 @@
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor'; import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {CSSValue, isIdentToken} from '../syntax/parser'; import {CSSValue, isIdentToken} from '../syntax/parser';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
export const enum BACKGROUND_CLIP { export enum BACKGROUND_CLIP {
BORDER_BOX = 0, BORDER_BOX = 0,
PADDING_BOX = 1, PADDING_BOX = 1,
CONTENT_BOX = 2 CONTENT_BOX = 2

View File

@ -3,7 +3,7 @@ import {CSSValue, isIdentToken, parseFunctionArgs} from '../syntax/parser';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
export type BackgroundRepeat = BACKGROUND_REPEAT[]; export type BackgroundRepeat = BACKGROUND_REPEAT[];
export const enum BACKGROUND_REPEAT { export enum BACKGROUND_REPEAT {
REPEAT = 0, REPEAT = 0,
NO_REPEAT = 1, NO_REPEAT = 1,
REPEAT_X = 2, REPEAT_X = 2,

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 BORDER_STYLE { export enum BORDER_STYLE {
NONE = 0, NONE = 0,
SOLID = 1, SOLID = 1,
DASHED = 2, DASHED = 2,

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 FLOAT { export enum FLOAT {
NONE = 0, NONE = 0,
LEFT = 1, LEFT = 1,
RIGHT = 2, RIGHT = 2,

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 FONT_STYLE { export enum FONT_STYLE {
NORMAL = 'normal', NORMAL = 'normal',
ITALIC = 'italic', ITALIC = 'italic',
OBLIQUE = 'oblique' OBLIQUE = 'oblique'

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 LIST_STYLE_POSITION { export enum LIST_STYLE_POSITION {
INSIDE = 0, INSIDE = 0,
OUTSIDE = 1 OUTSIDE = 1
} }

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 LIST_STYLE_TYPE { export enum LIST_STYLE_TYPE {
NONE = -1, NONE = -1,
DISC = 0, DISC = 0,
CIRCLE = 1, CIRCLE = 1,

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

@ -1,12 +1,11 @@
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor'; import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {CSSValue, isIdentToken} from '../syntax/parser'; import {CSSValue, isIdentToken} from '../syntax/parser';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
export const enum OVERFLOW { 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

@ -1,7 +1,7 @@
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor'; import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {CSSValue, isIdentToken} from '../syntax/parser'; import {CSSValue, isIdentToken} from '../syntax/parser';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
export const enum PAINT_ORDER_LAYER { export enum PAINT_ORDER_LAYER {
FILL, FILL,
STROKE, STROKE,
MARKERS MARKERS

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 POSITION { export enum POSITION {
STATIC = 0, STATIC = 0,
RELATIVE = 1, RELATIVE = 1,
ABSOLUTE = 2, ABSOLUTE = 2,

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 TEXT_ALIGN { export enum TEXT_ALIGN {
LEFT = 0, LEFT = 0,
CENTER = 1, CENTER = 1,
RIGHT = 2 RIGHT = 2

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 TEXT_TRANSFORM { export enum TEXT_TRANSFORM {
NONE = 0, NONE = 0,
LOWERCASE = 1, LOWERCASE = 1,
UPPERCASE = 2, UPPERCASE = 2,

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 VISIBILITY { export enum VISIBILITY {
VISIBLE = 0, VISIBLE = 0,
HIDDEN = 1, HIDDEN = 1,
COLLAPSE = 2 COLLAPSE = 2

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,
@ -657,7 +657,7 @@ export class Tokenizer {
} }
private consumeStringSlice(count: number): string { private consumeStringSlice(count: number): string {
const SLICE_STACK_SIZE = 50000; const SLICE_STACK_SIZE = 60000;
let value = ''; let value = '';
while (count > 0) { while (count > 0) {
const amount = Math.min(SLICE_STACK_SIZE, count); const amount = Math.min(SLICE_STACK_SIZE, count);

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

@ -10,7 +10,7 @@ import {radialGradient} from './functions/radial-gradient';
import {prefixRadialGradient} from './functions/-prefix-radial-gradient'; import {prefixRadialGradient} from './functions/-prefix-radial-gradient';
import {Context} from '../../core/context'; import {Context} from '../../core/context';
export const enum CSSImageType { export enum CSSImageType {
URL, URL,
LINEAR_GRADIENT, LINEAR_GRADIENT,
RADIAL_GRADIENT RADIAL_GRADIENT
@ -56,12 +56,12 @@ export interface CSSLinearGradientImage extends ICSSGradientImage {
type: CSSImageType.LINEAR_GRADIENT; type: CSSImageType.LINEAR_GRADIENT;
} }
export const enum CSSRadialShape { export enum CSSRadialShape {
CIRCLE, CIRCLE,
ELLIPSE ELLIPSE
} }
export const enum CSSRadialExtent { export enum CSSRadialExtent {
CLOSEST_SIDE, CLOSEST_SIDE,
FARTHEST_SIDE, FARTHEST_SIDE,
CLOSEST_CORNER, CLOSEST_CORNER,

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

@ -2,19 +2,16 @@ import {Bounds} from '../css/layout/bounds';
import { import {
isBodyElement, isBodyElement,
isCanvasElement, isCanvasElement,
isCustomElement,
isElementNode, isElementNode,
isHTMLElementNode, isHTMLElementNode,
isIFrameElement, isIFrameElement,
isImageElement, isImageElement,
isScriptElement, isScriptElement,
isSelectElement, isSelectElement,
isSlotElement,
isStyleElement, isStyleElement,
isSVGElementNode, isSVGElementNode,
isTextareaElement, isTextareaElement,
isTextNode, isTextNode
isVideoElement
} from './node-parser'; } from './node-parser';
import {isIdentToken, nonFunctionArgSeparator} from '../css/syntax/parser'; import {isIdentToken, nonFunctionArgSeparator} from '../css/syntax/parser';
import {TokenType} from '../css/syntax/tokenizer'; import {TokenType} from '../css/syntax/tokenizer';
@ -23,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 {
@ -66,7 +61,7 @@ export class DocumentCloner {
throw new Error('Cloned element does not have an owner document'); throw new Error('Cloned element does not have an owner document');
} }
this.documentElement = this.cloneNode(element.ownerDocument.documentElement, false) as HTMLElement; this.documentElement = this.cloneNode(element.ownerDocument.documentElement) as HTMLElement;
} }
toIFrame(ownerDocument: Document, windowSize: Bounds): Promise<HTMLIFrameElement> { toIFrame(ownerDocument: Document, windowSize: Bounds): Promise<HTMLIFrameElement> {
@ -140,15 +135,10 @@ 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);
} }
if (isVideoElement(node)) {
return this.createVideoClone(node);
}
if (isStyleElement(node)) { if (isStyleElement(node)) {
return this.createStyleClone(node); return this.createStyleClone(node);
} }
@ -165,17 +155,6 @@ export class DocumentCloner {
} }
} }
if (isCustomElement(clone)) {
return this.createCustomElementClone(clone);
}
return clone;
}
createCustomElementClone(node: HTMLElement): HTMLElement {
const clone = document.createElement('html2canvascustomelement');
copyCSSStyles(node.style, clone);
return clone; return clone;
} }
@ -210,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`);
} }
} }
@ -222,88 +201,19 @@ 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;
} }
createVideoClone(video: HTMLVideoElement): HTMLCanvasElement { cloneNode(node: Node): Node {
const canvas = video.ownerDocument.createElement('canvas');
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
const ctx = canvas.getContext('2d');
try {
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
if (!this.options.allowTaint) {
ctx.getImageData(0, 0, canvas.width, canvas.height);
}
}
return canvas;
} catch (e) {
this.context.logger.info(`Unable to clone video as it is tainted`, video);
}
const blankCanvas = video.ownerDocument.createElement('canvas');
blankCanvas.width = video.offsetWidth;
blankCanvas.height = video.offsetHeight;
return blankCanvas;
}
appendChildNode(clone: HTMLElement | SVGElement, child: Node, copyStyles: boolean): void {
if (
!isElementNode(child) ||
(!isScriptElement(child) &&
!child.hasAttribute(IGNORE_ATTRIBUTE) &&
(typeof this.options.ignoreElements !== 'function' || !this.options.ignoreElements(child)))
) {
if (!this.options.copyStyles || !isElementNode(child) || !isStyleElement(child)) {
clone.appendChild(this.cloneNode(child, copyStyles));
}
}
}
cloneChildNodes(node: Element, clone: HTMLElement | SVGElement, copyStyles: boolean): void {
for (
let child = node.shadowRoot ? node.shadowRoot.firstChild : node.firstChild;
child;
child = child.nextSibling
) {
if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === 'function') {
const assignedNodes = child.assignedNodes() as ChildNode[];
if (assignedNodes.length) {
assignedNodes.forEach((assignedNode) => this.appendChildNode(clone, assignedNode, copyStyles));
}
} else {
this.appendChildNode(clone, child, copyStyles);
}
}
}
cloneNode(node: Node, copyStyles: boolean): Node {
if (isTextNode(node)) { if (isTextNode(node)) {
return document.createTextNode(node.data); return document.createTextNode(node.data);
} }
@ -316,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');
@ -332,12 +241,17 @@ export class DocumentCloner {
const counters = this.counters.parse(new CSSParsedCounterDeclaration(this.context, style)); const counters = this.counters.parse(new CSSParsedCounterDeclaration(this.context, style));
const before = this.resolvePseudoContent(node, clone, styleBefore, PseudoElementType.BEFORE); const before = this.resolvePseudoContent(node, clone, styleBefore, PseudoElementType.BEFORE);
if (isCustomElement(node)) { for (let child = node.firstChild; child; child = child.nextSibling) {
copyStyles = true; if (
} !isElementNode(child) ||
(!isScriptElement(child) &&
if (!isVideoElement(node)) { !child.hasAttribute(IGNORE_ATTRIBUTE) &&
this.cloneChildNodes(node, clone, copyStyles); (typeof this.options.ignoreElements !== 'function' || !this.options.ignoreElements(child)))
) {
if (!this.options.copyStyles || !isElementNode(child) || !isStyleElement(child)) {
clone.appendChild(this.cloneNode(child));
}
}
} }
if (before) { if (before) {
@ -351,10 +265,7 @@ export class DocumentCloner {
this.counters.pop(counters); this.counters.pop(counters);
if ( if (style && (this.options.copyStyles || isSVGElementNode(node)) && !isIFrameElement(node)) {
(style && (this.options.copyStyles || isSVGElementNode(node)) && !isIFrameElement(node)) ||
copyStyles
) {
copyCSSStyles(style, clone); copyCSSStyles(style, clone);
} }

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

@ -124,7 +124,6 @@ export const isHTMLElement = (node: Element): node is HTMLHtmlElement => node.ta
export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg'; export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg';
export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY'; export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY';
export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS'; export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO';
export const isImageElement = (node: Element): node is HTMLImageElement => node.tagName === 'IMG'; export const isImageElement = (node: Element): node is HTMLImageElement => node.tagName === 'IMG';
export const isIFrameElement = (node: Element): node is HTMLIFrameElement => node.tagName === 'IFRAME'; export const isIFrameElement = (node: Element): node is HTMLIFrameElement => node.tagName === 'IFRAME';
export const isStyleElement = (node: Element): node is HTMLStyleElement => node.tagName === 'STYLE'; export const isStyleElement = (node: Element): node is HTMLStyleElement => node.tagName === 'STYLE';
@ -132,5 +131,3 @@ export const isScriptElement = (node: Element): node is HTMLScriptElement => nod
export const isTextareaElement = (node: Element): node is HTMLTextAreaElement => node.tagName === 'TEXTAREA'; export const isTextareaElement = (node: Element): node is HTMLTextAreaElement => node.tagName === 'TEXTAREA';
export const isSelectElement = (node: Element): node is HTMLSelectElement => node.tagName === 'SELECT'; export const isSelectElement = (node: Element): node is HTMLSelectElement => node.tagName === 'SELECT';
export const isSlotElement = (node: Element): node is HTMLSlotElement => node.tagName === 'SLOT'; export const isSlotElement = (node: Element): node is HTMLSlotElement => node.tagName === 'SLOT';
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
export const isCustomElement = (node: Element): node is HTMLElement => node.tagName.indexOf('-') > 0;

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,8 +1,8 @@
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'; import {CSSParsedDeclaration} from '../../css/index';
import {TextContainer} from '../../dom/text-container'; import {TextContainer} from '../../dom/text-container';
import {Path, transformPath} from '../path'; import {Path, transformPath} from '../path';
import {BACKGROUND_CLIP} from '../../css/property-descriptors/background-clip'; import {BACKGROUND_CLIP} from '../../css/property-descriptors/background-clip';
@ -18,12 +18,13 @@ import {
} from '../border'; } from '../border';
import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background'; import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background';
import {isDimensionToken} from '../../css/syntax/parser'; import {isDimensionToken} from '../../css/syntax/parser';
import {segmentGraphemes, 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';
import {SVGElementContainer} from '../../dom/replaced-elements/svg-element-container'; import {SVGElementContainer} from '../../dom/replaced-elements/svg-element-container';
import {ReplacedElementContainer} from '../../dom/replaced-elements'; import {ReplacedElementContainer} from '../../dom/replaced-elements/index';
import {EffectTarget, IElementEffect, isClipEffect, isOpacityEffect, isTransformEffect} from '../effects'; import {EffectTarget, IElementEffect, isClipEffect, isOpacityEffect, isTransformEffect} from '../effects';
import {contains} from '../../core/bitwise'; import {contains} from '../../core/bitwise';
import {calculateGradientDirection, calculateRadius, processColorStops} from '../../css/types/functions/gradient'; import {calculateGradientDirection, calculateRadius, processColorStops} from '../../css/types/functions/gradient';
@ -43,7 +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';
export type RenderConfigurations = RenderOptions & { export type RenderConfigurations = RenderOptions & {
backgroundColor: Color | null; backgroundColor: Color | null;
@ -86,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 {
@ -134,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);
@ -148,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 = segmentGraphemes(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);
@ -161,7 +157,7 @@ export class CanvasRenderer extends Renderer {
const fontVariant = styles.fontVariant const fontVariant = styles.fontVariant
.filter((variant) => variant === 'normal' || variant === 'small-caps') .filter((variant) => variant === 'normal' || variant === 'small-caps')
.join(''); .join('');
const fontFamily = fixIOSSystemFonts(styles.fontFamily).join(', '); const fontFamily = styles.fontFamily.join(', ');
const fontSize = isDimensionToken(styles.fontSize) const fontSize = isDimensionToken(styles.fontSize)
? `${styles.fontSize.number}${styles.fontSize.unit}` ? `${styles.fontSize.number}${styles.fontSize.unit}`
: `${styles.fontSize.number}px`; : `${styles.fontSize.number}px`;
@ -178,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;
@ -292,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;
@ -471,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);
@ -691,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;
@ -905,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;
} }
} }
@ -946,12 +937,3 @@ const canvasTextAlign = (textAlign: TEXT_ALIGN): CanvasTextAlign => {
return 'left'; return 'left';
} }
}; };
// see https://github.com/niklasvh/html2canvas/pull/2645
const iOSBrokenFonts = ['-apple-system', 'system-ui'];
const fixIOSSystemFonts = (fontFamilies: string[]): string[] => {
return /iPhone OS 15_(0|1)/.test(window.navigator.userAgent)
? fontFamilies.filter((fontFamily) => iOSBrokenFonts.indexOf(fontFamily) === -1)
: fontFamilies;
};

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

@ -27,7 +27,6 @@ export class FontMetrics {
container.style.fontSize = fontSize; container.style.fontSize = fontSize;
container.style.margin = '0'; container.style.margin = '0';
container.style.padding = '0'; container.style.padding = '0';
container.style.whiteSpace = 'nowrap';
body.appendChild(container); body.appendChild(container);

View File

@ -1,6 +1,6 @@
import {BezierCurve} from './bezier-curve'; import {BezierCurve} from './bezier-curve';
import {Vector} from './vector'; import {Vector} from './vector';
export const enum PathType { export enum PathType {
VECTOR = 0, VECTOR = 0,
BEZIER_CURVE = 1 BEZIER_CURVE = 1
} }

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);

Binary file not shown.

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

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Video tests</title>
<script type="text/javascript" src="../../test.js"></script>
</head>
<body>
<h2>Same origin</h2>
<video controls width="250">
<source src="../../assets/cc0-video.mp4" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
<h2>Cross-origin (doesn't taint)</h2>
<video controls width="250">
<source src="http://localhost:8081/cors/tests/assets/cc0-video.mp4" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
</body>
</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

@ -11,13 +11,6 @@
float: left; float: left;
} }
.apple-system {
font-family: -apple-system, Arial;
}
.system-ui {
font-family: system-ui, Arial;
}
</style> </style>
</head> </head>
<body> <body>
@ -39,7 +32,5 @@
</p><p>&nbsp;&nbsp;&nbsp;&nbsp;13 法捷耶夫(一九○一——一九五六),苏联名作家。他所作的小说《毁灭》于一九二七年出版,内容是描写苏联国内战争时期由苏联远东滨海边区工人、农民和革命知识分子所组成的一支游击队同国内反革命白卫军以及日本武装干涉军进行斗争的故事。这部小说曾由鲁迅译为汉文。 </p><p>&nbsp;&nbsp;&nbsp;&nbsp;13 法捷耶夫(一九○一——一九五六),苏联名作家。他所作的小说《毁灭》于一九二七年出版,内容是描写苏联国内战争时期由苏联远东滨海边区工人、农民和革命知识分子所组成的一支游击队同国内反革命白卫军以及日本武装干涉军进行斗争的故事。这部小说曾由鲁迅译为汉文。
</p><p>&nbsp;&nbsp;&nbsp;&nbsp;14 见鲁迅《集外集·自嘲》《鲁迅全集》第7卷人民文学出版社1981年版第147页</p> </p><p>&nbsp;&nbsp;&nbsp;&nbsp;14 见鲁迅《集外集·自嘲》《鲁迅全集》第7卷人民文学出版社1981年版第147页</p>
</div> </div>
<div class="apple-system">中文</div>
<div class="system-ui">中文</div>
</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

@ -40,10 +40,6 @@ class AutonomousCustomElement extends HTMLElement {
wrapper.appendChild(img); wrapper.appendChild(img);
wrapper.appendChild(info); wrapper.appendChild(info);
} }
connectedCallback() {
this.shadowRoot.adoptedStyleSheets = [sheet];
}
} }
customElements.define('autonomous-custom-element', AutonomousCustomElement); customElements.define('autonomous-custom-element', AutonomousCustomElement);

View File

@ -3,11 +3,10 @@
<head> <head>
<title>Web components tests</title> <title>Web components tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
const sheet = new CSSStyleSheet();
sheet.replaceSync('* { color: red !important; }')
</script>
<script type="text/javascript" src="../../test.js"></script> <script type="text/javascript" src="../../test.js"></script>
<style>
</style>
</head> </head>
<body> <body>
<div> <div>

View File

@ -12,7 +12,7 @@ const mkdirp = require('mkdirp');
export const app = express(); export const app = express();
app.use('/', serveIndex(path.resolve(__dirname, '../'), {icons: true})); app.use('/', serveIndex(path.resolve(__dirname, '../'), {icons: true}));
app.use([/^\/src($|\/)/, '/'], express.static(path.resolve(__dirname, '../'))); app.use('/', express.static(path.resolve(__dirname, '../')));
export const corsApp = express(); export const corsApp = express();
corsApp.use('/proxy', proxy()); corsApp.use('/proxy', proxy());

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);
}); });

2
www/.gitignore vendored
View File

@ -9,5 +9,3 @@ yarn-error.log
src/results.json src/results.json
static/tests/preview.js static/tests/preview.js
src/preview.js src/preview.js
.docusaurus
build/

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"
} }