mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-19 05:32:16 +01:00
refactor(chromium): drop paint-callback polyfill now that chromedp is pinned
This commit is contained in:
@@ -317,7 +317,6 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *slog.Logger, url, out
|
|||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
setCookiesActionFunc(logger, options.Cookies),
|
setCookiesActionFunc(logger, options.Cookies),
|
||||||
userAgentOverride(logger, options.UserAgent),
|
userAgentOverride(logger, options.UserAgent),
|
||||||
injectPaintCallbacksPolyfillActionFunc(logger),
|
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent, options.SkipNetworkAlmostIdleEvent),
|
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent, options.SkipNetworkAlmostIdleEvent),
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground),
|
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground),
|
||||||
forceExactColorsActionFunc(logger, options.PrintBackground),
|
forceExactColorsActionFunc(logger, options.PrintBackground),
|
||||||
@@ -344,7 +343,6 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *slog.Logger, u
|
|||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
setCookiesActionFunc(logger, options.Cookies),
|
setCookiesActionFunc(logger, options.Cookies),
|
||||||
userAgentOverride(logger, options.UserAgent),
|
userAgentOverride(logger, options.UserAgent),
|
||||||
injectPaintCallbacksPolyfillActionFunc(logger),
|
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent, options.SkipNetworkAlmostIdleEvent),
|
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent, options.SkipNetworkAlmostIdleEvent),
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
||||||
forceExactColorsActionFunc(logger, true),
|
forceExactColorsActionFunc(logger, true),
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
package chromium
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/chromedp/cdproto/page"
|
|
||||||
"github.com/chromedp/chromedp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// paintCallbacksPolyfill is a JavaScript shim installed before any user
|
|
||||||
// script runs. Replaces requestAnimationFrame, cancelAnimationFrame,
|
|
||||||
// ResizeObserver, and IntersectionObserver with timer-backed
|
|
||||||
// implementations. Headless Chromium's print-emulation pipeline does
|
|
||||||
// not tick the compositor refresh driver between the load event and
|
|
||||||
// the Page.printToPDF call, which leaves the native callback queues
|
|
||||||
// stalled and breaks charting libraries (visx, ApexCharts) that rely
|
|
||||||
// on rAF-gated measurement. The polyfill exposes the same APIs with
|
|
||||||
// timer semantics so user-scheduled callbacks fire in print mode and
|
|
||||||
// the rendered output matches a live browser. See
|
|
||||||
// https://github.com/gotenberg/gotenberg/issues/1535.
|
|
||||||
const paintCallbacksPolyfill = `
|
|
||||||
(function () {
|
|
||||||
var nextHandle = 0;
|
|
||||||
var pending = new Map();
|
|
||||||
|
|
||||||
window.requestAnimationFrame = function (callback) {
|
|
||||||
var handle = ++nextHandle;
|
|
||||||
pending.set(handle, setTimeout(function () {
|
|
||||||
pending.delete(handle);
|
|
||||||
callback(performance.now());
|
|
||||||
}, 16));
|
|
||||||
return handle;
|
|
||||||
};
|
|
||||||
window.cancelAnimationFrame = function (handle) {
|
|
||||||
clearTimeout(pending.get(handle));
|
|
||||||
pending.delete(handle);
|
|
||||||
};
|
|
||||||
|
|
||||||
function PollingResizeObserver(callback) {
|
|
||||||
this._callback = callback;
|
|
||||||
this._observed = [];
|
|
||||||
this._timer = null;
|
|
||||||
}
|
|
||||||
PollingResizeObserver.prototype.observe = function (el) {
|
|
||||||
var entry = { target: el, lastW: -1, lastH: -1 };
|
|
||||||
this._observed.push(entry);
|
|
||||||
if (!this._timer) {
|
|
||||||
var self = this;
|
|
||||||
this._timer = setInterval(function () { self._tick(); }, 100);
|
|
||||||
}
|
|
||||||
this._tick();
|
|
||||||
};
|
|
||||||
PollingResizeObserver.prototype.unobserve = function (el) {
|
|
||||||
this._observed = this._observed.filter(function (e) { return e.target !== el; });
|
|
||||||
if (this._observed.length === 0 && this._timer) {
|
|
||||||
clearInterval(this._timer);
|
|
||||||
this._timer = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
PollingResizeObserver.prototype.disconnect = function () {
|
|
||||||
this._observed = [];
|
|
||||||
if (this._timer) {
|
|
||||||
clearInterval(this._timer);
|
|
||||||
this._timer = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
PollingResizeObserver.prototype._tick = function () {
|
|
||||||
var changed = [];
|
|
||||||
for (var i = 0; i < this._observed.length; i++) {
|
|
||||||
var e = this._observed[i];
|
|
||||||
var w = e.target.offsetWidth;
|
|
||||||
var h = e.target.offsetHeight;
|
|
||||||
if (w !== e.lastW || h !== e.lastH) {
|
|
||||||
e.lastW = w;
|
|
||||||
e.lastH = h;
|
|
||||||
changed.push({
|
|
||||||
target: e.target,
|
|
||||||
contentRect: { width: w, height: h, top: 0, left: 0, right: w, bottom: h, x: 0, y: 0 },
|
|
||||||
borderBoxSize: [{ inlineSize: w, blockSize: h }],
|
|
||||||
contentBoxSize: [{ inlineSize: w, blockSize: h }],
|
|
||||||
devicePixelContentBoxSize: [{ inlineSize: w, blockSize: h }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed.length > 0) {
|
|
||||||
try { this._callback(changed, this); } catch (err) { console.error(err); }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.ResizeObserver = PollingResizeObserver;
|
|
||||||
|
|
||||||
function ImmediateIntersectionObserver(callback) {
|
|
||||||
this._callback = callback;
|
|
||||||
this._observed = [];
|
|
||||||
}
|
|
||||||
ImmediateIntersectionObserver.prototype.observe = function (el) {
|
|
||||||
this._observed.push(el);
|
|
||||||
var self = this;
|
|
||||||
setTimeout(function () {
|
|
||||||
var rect = el.getBoundingClientRect();
|
|
||||||
var entry = {
|
|
||||||
target: el,
|
|
||||||
isIntersecting: true,
|
|
||||||
intersectionRatio: 1,
|
|
||||||
boundingClientRect: rect,
|
|
||||||
intersectionRect: rect,
|
|
||||||
rootBounds: null,
|
|
||||||
time: performance.now(),
|
|
||||||
};
|
|
||||||
try { self._callback([entry], self); } catch (err) { console.error(err); }
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
ImmediateIntersectionObserver.prototype.unobserve = function (el) {
|
|
||||||
this._observed = this._observed.filter(function (x) { return x !== el; });
|
|
||||||
};
|
|
||||||
ImmediateIntersectionObserver.prototype.disconnect = function () {
|
|
||||||
this._observed = [];
|
|
||||||
};
|
|
||||||
ImmediateIntersectionObserver.prototype.takeRecords = function () { return []; };
|
|
||||||
window.IntersectionObserver = ImmediateIntersectionObserver;
|
|
||||||
})();
|
|
||||||
`
|
|
||||||
|
|
||||||
// injectPaintCallbacksPolyfillActionFunc installs the
|
|
||||||
// [paintCallbacksPolyfill] via [page.AddScriptToEvaluateOnNewDocument]
|
|
||||||
// before any user script runs. Runs on every conversion: Chromium's
|
|
||||||
// headless-print pipeline does not fire the native rAF,
|
|
||||||
// ResizeObserver, and IntersectionObserver, so the timer-backed
|
|
||||||
// replacements fire the user's callbacks when the page relies on them
|
|
||||||
// and have no effect otherwise.
|
|
||||||
func injectPaintCallbacksPolyfillActionFunc(logger *slog.Logger) chromedp.ActionFunc {
|
|
||||||
return func(ctx context.Context) error {
|
|
||||||
logger.DebugContext(ctx, "inject paint-callbacks polyfill")
|
|
||||||
_, err := page.AddScriptToEvaluateOnNewDocument(paintCallbacksPolyfill).Do(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("inject paint-callbacks polyfill: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -195,7 +195,7 @@ Feature: /forms/chromium/convert/html
|
|||||||
Wait delay > 2 seconds or expression window globalVar === 'ready' returns true.
|
Wait delay > 2 seconds or expression window globalVar === 'ready' returns true.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Scenario: POST /forms/chromium/convert/html (paint-callback polyfill fires rAF / ResizeObserver / IntersectionObserver with waitForExpression)
|
Scenario: POST /forms/chromium/convert/html (rAF / ResizeObserver / IntersectionObserver fire with waitForExpression)
|
||||||
Given I have a default Gotenberg container
|
Given I have a default Gotenberg container
|
||||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
||||||
| files | testdata/paint-callbacks-html/index.html | file |
|
| files | testdata/paint-callbacks-html/index.html | file |
|
||||||
@@ -219,7 +219,7 @@ Feature: /forms/chromium/convert/html
|
|||||||
io-fired
|
io-fired
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Scenario: POST /forms/chromium/convert/html (paint-callback polyfill fires rAF / ResizeObserver / IntersectionObserver with waitDelay and emulatedMediaType=print)
|
Scenario: POST /forms/chromium/convert/html (rAF / ResizeObserver / IntersectionObserver fire with waitDelay and emulatedMediaType=print)
|
||||||
Given I have a default Gotenberg container
|
Given I have a default Gotenberg container
|
||||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
||||||
| files | testdata/paint-callbacks-html/index.html | file |
|
| files | testdata/paint-callbacks-html/index.html | file |
|
||||||
|
|||||||
Reference in New Issue
Block a user