mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 12:42:16 +01:00
feat(chromium): add emulated media type
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/chromedp/cdproto/emulation"
|
||||||
"github.com/chromedp/cdproto/fetch"
|
"github.com/chromedp/cdproto/fetch"
|
||||||
"github.com/chromedp/cdproto/network"
|
"github.com/chromedp/cdproto/network"
|
||||||
"github.com/chromedp/cdproto/page"
|
"github.com/chromedp/cdproto/page"
|
||||||
@@ -30,6 +31,10 @@ var (
|
|||||||
// allowed/denied lists.
|
// allowed/denied lists.
|
||||||
ErrURLNotAuthorized = errors.New("URL not authorized")
|
ErrURLNotAuthorized = errors.New("URL not authorized")
|
||||||
|
|
||||||
|
// ErrInvalidEmulatedMediaType happens if the emulated media type is not
|
||||||
|
// "screen" nor "print". Empty value are allowed though.
|
||||||
|
ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type")
|
||||||
|
|
||||||
// ErrInvalidEvaluationExpression happens if an evaluation expression
|
// ErrInvalidEvaluationExpression happens if an evaluation expression
|
||||||
// returns an exception or undefined.
|
// returns an exception or undefined.
|
||||||
ErrInvalidEvaluationExpression = errors.New("invalid evaluation expression")
|
ErrInvalidEvaluationExpression = errors.New("invalid evaluation expression")
|
||||||
@@ -89,6 +94,11 @@ type Options struct {
|
|||||||
// Optional.
|
// Optional.
|
||||||
ExtraHTTPHeaders map[string]string
|
ExtraHTTPHeaders map[string]string
|
||||||
|
|
||||||
|
// EmulatedMediaType is the media type to emulate, either "screen" or
|
||||||
|
// "print".
|
||||||
|
// Optional.
|
||||||
|
EmulatedMediaType string
|
||||||
|
|
||||||
// Landscape sets the paper orientation.
|
// Landscape sets the paper orientation.
|
||||||
// Optional.
|
// Optional.
|
||||||
Landscape bool
|
Landscape bool
|
||||||
@@ -161,6 +171,7 @@ func DefaultOptions() Options {
|
|||||||
WaitForExpression: "",
|
WaitForExpression: "",
|
||||||
UserAgent: "",
|
UserAgent: "",
|
||||||
ExtraHTTPHeaders: nil,
|
ExtraHTTPHeaders: nil,
|
||||||
|
EmulatedMediaType: "",
|
||||||
Landscape: false,
|
Landscape: false,
|
||||||
PrintBackground: false,
|
PrintBackground: false,
|
||||||
Scale: 1.0,
|
Scale: 1.0,
|
||||||
@@ -446,6 +457,28 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
|
|||||||
|
|
||||||
return fmt.Errorf("add CSS for exact colors: %w", err)
|
return fmt.Errorf("add CSS for exact colors: %w", err)
|
||||||
}),
|
}),
|
||||||
|
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||||
|
if options.EmulatedMediaType == "" {
|
||||||
|
logger.Debug("no emulated media type")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.EmulatedMediaType != "screen" && options.EmulatedMediaType != "print" {
|
||||||
|
return fmt.Errorf("validate emulated media type '%s': %w", options.EmulatedMediaType, ErrInvalidEmulatedMediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(fmt.Sprintf("emulate media type '%s'", options.EmulatedMediaType))
|
||||||
|
|
||||||
|
emulatedMedia := emulation.SetEmulatedMedia()
|
||||||
|
|
||||||
|
err := emulatedMedia.WithMedia(options.EmulatedMediaType).Do(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("emulate media type '%s': %w", options.EmulatedMediaType, err)
|
||||||
|
}),
|
||||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||||
if options.WaitDelay > 0 {
|
if options.WaitDelay > 0 {
|
||||||
// We wait for a given amount of time so that JavaScript
|
// We wait for a given amount of time so that JavaScript
|
||||||
|
|||||||
@@ -264,6 +264,25 @@ func TestChromium_PDF(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
URL: "file:///tests/test/testdata/chromium/html/sample8/index.html",
|
||||||
|
options: Options{
|
||||||
|
EmulatedMediaType: "foo",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "file:///tests/test/testdata/chromium/html/sample8/index.html",
|
||||||
|
options: Options{
|
||||||
|
EmulatedMediaType: "screen",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "file:///tests/test/testdata/chromium/html/sample8/index.html",
|
||||||
|
options: Options{
|
||||||
|
EmulatedMediaType: "print",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
|
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
|
||||||
options: Options{
|
options: Options{
|
||||||
@@ -300,6 +319,13 @@ func TestChromium_PDF(t *testing.T) {
|
|||||||
WaitForExpression: "window.status === 'ready'",
|
WaitForExpression: "window.status === 'ready'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
|
||||||
|
options: Options{
|
||||||
|
WaitForExpression: "return undefined",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
|
URL: "file:///tests/test/testdata/chromium/html/sample4/index.html",
|
||||||
options: Options{
|
options: Options{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func FormDataChromiumPDFOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
waitForExpression string
|
waitForExpression string
|
||||||
userAgent string
|
userAgent string
|
||||||
extraHTTPHeaders map[string]string
|
extraHTTPHeaders map[string]string
|
||||||
|
emulatedMediaType string
|
||||||
landscape, printBackground bool
|
landscape, printBackground bool
|
||||||
scale, paperWidth, paperHeight float64
|
scale, paperWidth, paperHeight float64
|
||||||
marginTop, marginBottom, marginLeft, marginRight float64
|
marginTop, marginBottom, marginLeft, marginRight float64
|
||||||
@@ -58,6 +59,7 @@ func FormDataChromiumPDFOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}).
|
}).
|
||||||
|
String("emulatedMediaType", &emulatedMediaType, defaultOptions.EmulatedMediaType).
|
||||||
Bool("landscape", &landscape, defaultOptions.Landscape).
|
Bool("landscape", &landscape, defaultOptions.Landscape).
|
||||||
Bool("printBackground", &printBackground, defaultOptions.PrintBackground).
|
Bool("printBackground", &printBackground, defaultOptions.PrintBackground).
|
||||||
Float64("scale", &scale, defaultOptions.Scale).
|
Float64("scale", &scale, defaultOptions.Scale).
|
||||||
@@ -78,6 +80,7 @@ func FormDataChromiumPDFOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
WaitForExpression: waitForExpression,
|
WaitForExpression: waitForExpression,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
ExtraHTTPHeaders: extraHTTPHeaders,
|
ExtraHTTPHeaders: extraHTTPHeaders,
|
||||||
|
EmulatedMediaType: emulatedMediaType,
|
||||||
Landscape: landscape,
|
Landscape: landscape,
|
||||||
PrintBackground: printBackground,
|
PrintBackground: printBackground,
|
||||||
Scale: scale,
|
Scale: scale,
|
||||||
@@ -294,6 +297,16 @@ func convertURL(ctx *api.Context, chromium API, engine gotenberg.PDFEngine, URL,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrInvalidEmulatedMediaType) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
|
api.NewSentinelHTTPError(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
fmt.Sprintf("The media type '%s' (emulatedMediaType) is invalid: allowed values are 'screen' or 'print'", options.EmulatedMediaType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
||||||
if options.WaitForExpression == "" {
|
if options.WaitForExpression == "" {
|
||||||
// We do not expect the 'waitWindowStatus' form field to return
|
// We do not expect the 'waitWindowStatus' form field to return
|
||||||
|
|||||||
@@ -544,6 +544,21 @@ func TestConvertURL(t *testing.T) {
|
|||||||
expectHTTPErr: true,
|
expectHTTPErr: true,
|
||||||
expectHTTPStatus: http.StatusForbidden,
|
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 ErrInvalidEmulatedMediaType
|
||||||
|
}
|
||||||
|
|
||||||
|
return chromiumAPI
|
||||||
|
}(),
|
||||||
|
options: DefaultOptions(),
|
||||||
|
expectErr: true,
|
||||||
|
expectHTTPErr: true,
|
||||||
|
expectHTTPStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ctx: &api.MockContext{Context: &api.Context{}},
|
ctx: &api.MockContext{Context: &api.Context{}},
|
||||||
api: func() API {
|
api: func() API {
|
||||||
|
|||||||
19
test/testdata/chromium/html/sample8/index.html
vendored
Normal file
19
test/testdata/chromium/html/sample8/index.html
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Gutenberg</title>
|
||||||
|
<style>
|
||||||
|
@media print {
|
||||||
|
#screen { display: none }
|
||||||
|
}
|
||||||
|
@media screen {
|
||||||
|
#print { display: none }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="print">Print media type</p>
|
||||||
|
<p id="screen">Screen media type</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user