niklasvh / html2canvas

Screenshots with JavaScript
https://html2canvas.hertzen.com/
MIT License
30.63k stars 4.81k forks source link

`html2canvas` significantly slower in Firefox for Jupyterlab #3191

Open nishikantparmariam opened 3 months ago

nishikantparmariam commented 3 months ago

Bug report

Context

We write custom React components for Jupyterlab, one of components allows users to download the view of that component as an image that internally uses html2canvas library. But we found that this operation is significantly slower in Firefox as compared to Chrome in Jupyterlab for the same component. This affects our user-experience a lot.

Reproducer

While our setup is bit complex, we managed to create a minimal reproducer.

Go to https://jupyter.org/try-jupyter/lab/, open a new notebook, add a cell with below code and run it. This code downloads html2canvas on the page and runs it against a div of the page:

from IPython.display import Javascript

Javascript("""

function loadScript(url, callback) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url; 
    script.onload = callback;    
    document.head.appendChild(script);
}

loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', async () => {

    const refreshIconDiv = document.getElementsByClassName('jp-CommandToolbarButton')[5];

    console.log('Styles:', window.getComputedStyle(refreshIconDiv).length);

    console.time('HTMLTOCANVAS TIMING');
    await html2canvas(refreshIconDiv);
    console.timeEnd('HTMLTOCANVAS TIMING');
});
""")

If we check the timing in console then in Chrome it takes just 500-600ms but in Firefox this takes 6-7s.

Root-Cause

Upon lots of debugging, we figured out that https://github.com/niklasvh/html2canvas/blob/6020386bbeed60ad68e675fdcaa6220e292fd35a/src/dom/document-cloner.ts#L560

is significantly slower for each call in Firefox than in Chrome.

Firefox (8ms): firefox-timing

Chrome (2.19ms): image

Upon debugging further we found out that in Jupyterlab in Firefox this line:

https://github.com/niklasvh/html2canvas/blob/6020386bbeed60ad68e675fdcaa6220e292fd35a/src/dom/document-cloner.ts#L321

always includes the custom CSS variables of Jupyterlab for any DOM node. See Styles: count in above snippet in both browsers.

image

Because html2canvas clones entire document and styles of every DOM node, these custom CSS variables are getting copied in Firefox making it very slow for Jupyterlab related use-cases.

Related Chromium Issues: https://issues.chromium.org/issues/351029427 https://issues.chromium.org/issues/41419198

Proposed Solution

While this happens only in Jupyterlab in Firefox but any webpage having custom CSS variables will get affected and html2canvas performance will be very slow.

For us, these custom variables do not matter to be copied and may be skipped, so we re-wrote the copyCSSStyle function like below that made Firefox as fast as Chrome:

var copyCSSStyles = function (style, target) {
        // Iterate from beginning
        for (var i = 0; i < style.length; i++) {
            var property = style.item(i);
            // Return early if a style like this encountered as styles are sorted
            if(property.startsWith('--')) return target;
            if (ignoredStyleProperties.indexOf(property) === -1) {
                target.style.setProperty(property, style.getPropertyValue(property));
            }
        }
        return target;
};

While this fix is not very generic, an ideal fix could be that html2canvas allows us to override our own copyCSSStyle function, may be in options like this:

html2canvas(element, {customCopyCSSStyle: // My implementation})

and then we can provide our implementation to solve the problem. I'm happy to raise a PR.

Open for other solutions / ideas.

Specifications: