mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-11 18:02:14 +01:00
feat(chromium): add 'waitForExpression' form field, deprecate 'waitWindowStatus' form field
This commit is contained in:
@@ -30,6 +30,10 @@ var (
|
||||
// allowed/denied lists.
|
||||
ErrURLNotAuthorized = errors.New("URL not authorized")
|
||||
|
||||
// ErrInvalidEvaluationExpression happens if an evaluation expression
|
||||
// returns an exception or undefined.
|
||||
ErrInvalidEvaluationExpression = errors.New("invalid evaluation expression")
|
||||
|
||||
// ErrInvalidPrinterSettings happens if the Options have one or more
|
||||
// aberrant values.
|
||||
ErrInvalidPrinterSettings = errors.New("invalid printer settings")
|
||||
@@ -71,6 +75,11 @@ type Options struct {
|
||||
// Optional.
|
||||
WaitWindowStatus string
|
||||
|
||||
// WaitForExpression is the custom JavaScript expression to wait before
|
||||
// converting an HTML document to PDF until it returns true
|
||||
// Optional.
|
||||
WaitForExpression string
|
||||
|
||||
// UserAgent overrides the default User-Agent header.
|
||||
// Optional.
|
||||
UserAgent string
|
||||
@@ -149,6 +158,7 @@ func DefaultOptions() Options {
|
||||
return Options{
|
||||
WaitDelay: 0,
|
||||
WaitWindowStatus: "",
|
||||
WaitForExpression: "",
|
||||
UserAgent: "",
|
||||
ExtraHTTPHeaders: nil,
|
||||
Landscape: false,
|
||||
@@ -454,42 +464,61 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
|
||||
return nil
|
||||
}),
|
||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
if options.WaitWindowStatus == "" {
|
||||
if options.WaitWindowStatus == "" && options.WaitForExpression == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We wait until the evaluation of
|
||||
// "window.status === options.WaitWindowStatus" is true or
|
||||
// until the context is done.
|
||||
logger.Debug(fmt.Sprintf("wait for window.status === '%s' before print", options.WaitWindowStatus))
|
||||
evaluate := func(expression string) error {
|
||||
// We wait until the evaluation of the expression is true or
|
||||
// until the context is done.
|
||||
logger.Debug(fmt.Sprintf("wait until '%s' is true before print", expression))
|
||||
|
||||
ticker := time.NewTicker(time.Duration(100) * time.Millisecond)
|
||||
ticker := time.NewTicker(time.Duration(100) * time.Millisecond)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
|
||||
return fmt.Errorf("wait for window.status === '%s': %w", options.WaitWindowStatus, ctx.Err())
|
||||
case <-ticker.C:
|
||||
var ok bool
|
||||
|
||||
evaluate := chromedp.Evaluate(fmt.Sprintf("window.status === '%s'", options.WaitWindowStatus), &ok)
|
||||
err := evaluate.Do(ctx)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("evaluate: %w", err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("context done while evaluating '%s': %w", expression, ctx.Err())
|
||||
case <-ticker.C:
|
||||
var ok bool
|
||||
|
||||
continue
|
||||
evaluate := chromedp.Evaluate(expression, &ok)
|
||||
err := evaluate.Do(ctx)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("evaluate: %v: %w", err, ErrInvalidEvaluationExpression)
|
||||
}
|
||||
|
||||
if ok {
|
||||
ticker.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.WaitWindowStatus != "" {
|
||||
logger.Warn("option 'WaitWindowStatus' is deprecated; prefer 'WaitForExpression' instead")
|
||||
|
||||
err := evaluate(fmt.Sprintf("window.status === '%s'", options.WaitWindowStatus))
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait for window.status === '%s': %w", options.WaitWindowStatus, err)
|
||||
}
|
||||
}
|
||||
|
||||
if options.WaitForExpression != "" {
|
||||
err := evaluate(options.WaitForExpression)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait for expression '%s': %w", options.WaitForExpression, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
printToPDF := page.PrintToPDF().
|
||||
|
||||
@@ -285,6 +285,21 @@ func TestChromium_PDF(t *testing.T) {
|
||||
WaitWindowStatus: "ready",
|
||||
},
|
||||
},
|
||||
{
|
||||
timeout: time.Duration(3) * time.Second,
|
||||
URL: "file:///tests/test/testdata/chromium/html/sample2/index.html",
|
||||
options: Options{
|
||||
WaitForExpression: "window.status === 'foo'",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
timeout: time.Duration(3) * time.Second,
|
||||
URL: "file:///tests/test/testdata/chromium/html/sample2/index.html",
|
||||
options: Options{
|
||||
WaitForExpression: "window.status === 'ready'",
|
||||
},
|
||||
},
|
||||
{
|
||||
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
|
||||
options: Options{
|
||||
|
||||
@@ -28,6 +28,7 @@ func FormDataChromiumPDFOptions(ctx *api.Context) (*api.FormData, Options) {
|
||||
var (
|
||||
waitDelay time.Duration
|
||||
waitWindowStatus string
|
||||
waitForExpression string
|
||||
userAgent string
|
||||
extraHTTPHeaders map[string]string
|
||||
landscape, printBackground bool
|
||||
@@ -41,6 +42,7 @@ func FormDataChromiumPDFOptions(ctx *api.Context) (*api.FormData, Options) {
|
||||
form := ctx.FormData().
|
||||
Duration("waitDelay", &waitDelay, defaultOptions.WaitDelay).
|
||||
String("waitWindowStatus", &waitWindowStatus, defaultOptions.WaitWindowStatus).
|
||||
String("waitForExpression", &waitForExpression, defaultOptions.WaitForExpression).
|
||||
String("userAgent", &userAgent, defaultOptions.UserAgent).
|
||||
Custom("extraHttpHeaders", func(value string) error {
|
||||
if value == "" {
|
||||
@@ -73,6 +75,7 @@ func FormDataChromiumPDFOptions(ctx *api.Context) (*api.FormData, Options) {
|
||||
options := Options{
|
||||
WaitDelay: waitDelay,
|
||||
WaitWindowStatus: waitWindowStatus,
|
||||
WaitForExpression: waitForExpression,
|
||||
UserAgent: userAgent,
|
||||
ExtraHTTPHeaders: extraHTTPHeaders,
|
||||
Landscape: landscape,
|
||||
@@ -291,6 +294,23 @@ func convertURL(ctx *api.Context, chromium API, engine gotenberg.PDFEngine, URL,
|
||||
)
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
||||
if options.WaitForExpression == "" {
|
||||
// We do not expect the 'waitWindowStatus' form field to return
|
||||
// an ErrInvalidEvaluationExpression error. In such a scenario,
|
||||
// we return a 500.
|
||||
return fmt.Errorf("convert to PDF: %w", err)
|
||||
}
|
||||
|
||||
return api.WrapError(
|
||||
fmt.Errorf("convert to PDF: %w", err),
|
||||
api.NewSentinelHTTPError(
|
||||
http.StatusBadRequest,
|
||||
fmt.Sprintf("The expression '%s' (waitForExpression) returned an exception or undefined", options.WaitForExpression),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrInvalidPrinterSettings) {
|
||||
return api.WrapError(
|
||||
fmt.Errorf("convert to PDF: %w", err),
|
||||
|
||||
@@ -523,6 +523,7 @@ func TestConvertURL(t *testing.T) {
|
||||
api API
|
||||
engine gotenberg.PDFEngine
|
||||
PDFformat string
|
||||
options Options
|
||||
expectErr bool
|
||||
expectHTTPErr bool
|
||||
expectHTTPStatus int
|
||||
@@ -538,10 +539,44 @@ func TestConvertURL(t *testing.T) {
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
expectHTTPErr: true,
|
||||
expectHTTPStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
ctx: &api.MockContext{Context: &api.Context{}},
|
||||
api: func() API {
|
||||
chromiumAPI := struct{ ProtoAPI }{}
|
||||
chromiumAPI.pdf = func(_ context.Context, _ *zap.Logger, _, _ string, _ Options) error {
|
||||
return ErrInvalidEvaluationExpression
|
||||
}
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
ctx: &api.MockContext{Context: &api.Context{}},
|
||||
api: func() API {
|
||||
chromiumAPI := struct{ ProtoAPI }{}
|
||||
chromiumAPI.pdf = func(_ context.Context, _ *zap.Logger, _, _ string, _ Options) error {
|
||||
return ErrInvalidEvaluationExpression
|
||||
}
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: func() Options {
|
||||
options := DefaultOptions()
|
||||
options.WaitForExpression = "foo"
|
||||
|
||||
return options
|
||||
}(),
|
||||
expectErr: true,
|
||||
expectHTTPErr: true,
|
||||
expectHTTPStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
ctx: &api.MockContext{Context: &api.Context{}},
|
||||
api: func() API {
|
||||
@@ -552,6 +587,7 @@ func TestConvertURL(t *testing.T) {
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
expectHTTPErr: true,
|
||||
expectHTTPStatus: http.StatusBadRequest,
|
||||
@@ -566,6 +602,7 @@ func TestConvertURL(t *testing.T) {
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
expectHTTPErr: true,
|
||||
expectHTTPStatus: http.StatusBadRequest,
|
||||
@@ -580,6 +617,7 @@ func TestConvertURL(t *testing.T) {
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
@@ -600,6 +638,7 @@ func TestConvertURL(t *testing.T) {
|
||||
}
|
||||
}(),
|
||||
PDFformat: "foo",
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
expectHTTPErr: true,
|
||||
expectHTTPStatus: http.StatusBadRequest,
|
||||
@@ -622,6 +661,7 @@ func TestConvertURL(t *testing.T) {
|
||||
}
|
||||
}(),
|
||||
PDFformat: "foo",
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
@@ -642,6 +682,7 @@ func TestConvertURL(t *testing.T) {
|
||||
}
|
||||
}(),
|
||||
PDFformat: "foo",
|
||||
options: DefaultOptions(),
|
||||
expectOutputPathsCount: 1,
|
||||
},
|
||||
{
|
||||
@@ -659,6 +700,7 @@ func TestConvertURL(t *testing.T) {
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
@@ -671,10 +713,11 @@ func TestConvertURL(t *testing.T) {
|
||||
|
||||
return chromiumAPI
|
||||
}(),
|
||||
options: DefaultOptions(),
|
||||
expectOutputPathsCount: 1,
|
||||
},
|
||||
} {
|
||||
err := convertURL(tc.ctx.Context, tc.api, tc.engine, "", tc.PDFformat, DefaultOptions())
|
||||
err := convertURL(tc.ctx.Context, tc.api, tc.engine, "", tc.PDFformat, tc.options)
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("test %d: expected error but got: %v", i, err)
|
||||
|
||||
Reference in New Issue
Block a user