create Playwright struct, use BrowserContext

This commit is contained in:
Bernard Xie 2022-11-17 11:25:07 -08:00
parent e5e34362fe
commit 6ef4246565
No known key found for this signature in database
GPG key ID: 3C3E0036CE0F892C
3 changed files with 92 additions and 60 deletions

View file

@ -113,15 +113,14 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
} }
ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation) ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation)
var pw *playwright.Playwright var pw png.Playwright
var browser playwright.Browser
if filepath.Ext(outputPath) == ".png" { if filepath.Ext(outputPath) == ".png" {
pw, browser, err = png.InitPlaywright() pw, err = png.InitPlaywright()
if err != nil { if err != nil {
return err return err
} }
defer func() error { defer func() error {
err = png.Cleanup(pw, browser) err = pw.Cleanup(*watchFlag)
if err != nil { if err != nil {
return err return err
} }
@ -134,7 +133,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin") return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin")
} }
ms.Env.Setenv("LOG_TIMESTAMPS", "1") ms.Env.Setenv("LOG_TIMESTAMPS", "1")
w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw, browser) w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw)
if err != nil { if err != nil {
return err return err
} }
@ -148,7 +147,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
_ = 343 _ = 343
} }
_, err = compile(ctx, ms, plugin, inputPath, outputPath, browser) _, err = compile(ctx, ms, plugin, inputPath, outputPath, pw.Page)
if err != nil { if err != nil {
return err return err
} }
@ -157,7 +156,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
return nil return nil
} }
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, browser playwright.Browser) ([]byte, error) { func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) ([]byte, error) {
input, err := ms.ReadPath(inputPath) input, err := ms.ReadPath(inputPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -188,7 +187,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input
} }
if filepath.Ext(outputPath) == ".png" { if filepath.Ext(outputPath) == ".png" {
outputImage, err = png.ExportPNG(browser, svg) outputImage, err = png.ExportPNG(ms, page, svg)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -16,11 +16,11 @@ import (
"time" "time"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/playwright-community/playwright-go"
"nhooyr.io/websocket" "nhooyr.io/websocket"
"nhooyr.io/websocket/wsjson" "nhooyr.io/websocket/wsjson"
"oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2plugin"
"oss.terrastruct.com/d2/lib/png"
"oss.terrastruct.com/d2/lib/xbrowser" "oss.terrastruct.com/d2/lib/xbrowser"
"oss.terrastruct.com/d2/lib/xhttp" "oss.terrastruct.com/d2/lib/xhttp"
"oss.terrastruct.com/d2/lib/xmain" "oss.terrastruct.com/d2/lib/xmain"
@ -63,8 +63,7 @@ type watcher struct {
resMu sync.Mutex resMu sync.Mutex
res *compileResult res *compileResult
browser playwright.Browser pw png.Playwright
pw *playwright.Playwright
} }
type compileResult struct { type compileResult struct {
@ -72,7 +71,7 @@ type compileResult struct {
SVG string `json:"svg"` SVG string `json:"svg"`
} }
func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw *playwright.Playwright, browser playwright.Browser) (*watcher, error) { func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw png.Playwright) (*watcher, error) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
w := &watcher{ w := &watcher{
@ -87,7 +86,6 @@ func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plug
compileCh: make(chan struct{}, 1), compileCh: make(chan struct{}, 1),
wsclients: make(map[*wsclient]struct{}), wsclients: make(map[*wsclient]struct{}),
browser: browser,
pw: pw, pw: pw,
} }
err := w.init() err := w.init()
@ -332,15 +330,16 @@ func (w *watcher) compileLoop(ctx context.Context) error {
recompiledPrefix = "re" recompiledPrefix = "re"
} }
if !w.browser.IsConnected() { if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() {
newBrowser, err := w.pw.Chromium.Launch() newPW, err := w.pw.RestartBrowser()
if err != nil { if err != nil {
w.ms.Log.Error.Printf("failed to refresh Playwright browser")
return err return err
} }
w.browser = newBrowser w.pw = newPW
} }
b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.browser) b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.pw.Page)
if err != nil { if err != nil {
err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err) err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err)
w.ms.Log.Error.Print(err) w.ms.Log.Error.Print(err)

View file

@ -12,19 +12,69 @@ import (
_ "embed" _ "embed"
"github.com/playwright-community/playwright-go" "github.com/playwright-community/playwright-go"
"oss.terrastruct.com/d2/lib/xmain"
) )
func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) { type Playwright struct {
PW *playwright.Playwright
Browser playwright.Browser
BrowserContext playwright.BrowserContext
Page playwright.Page
}
func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) {
if err = pw.BrowserContext.Close(); err != nil {
return Playwright{}, err
}
if err = pw.Browser.Close(); err != nil {
return Playwright{}, err
}
browser, err := pw.PW.Chromium.Launch()
if err != nil {
return Playwright{}, err
}
context, err := browser.NewContext()
if err != nil {
return Playwright{}, err
}
page, err := context.NewPage()
if err != nil {
return Playwright{}, err
}
return Playwright{
PW: pw.PW,
Browser: browser,
BrowserContext: context,
Page: page,
}, nil
}
func (pw *Playwright) Cleanup(isWatch bool) (err error) {
if !isWatch {
if err = pw.BrowserContext.Close(); err != nil {
return err
}
}
if err = pw.Browser.Close(); err != nil {
return err
}
if err = pw.PW.Stop(); err != nil {
return err
}
return nil
}
func InitPlaywright() (Playwright, error) {
// check if playwright driver/browsers are installed and up to date // check if playwright driver/browsers are installed and up to date
// https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64. // https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64.
driver, err := playwright.NewDriver(&playwright.RunOptions{}) driver, err := playwright.NewDriver(&playwright.RunOptions{})
if err != nil { if err != nil {
return nil, nil, err return Playwright{}, err
} }
if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) { if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) {
err = playwright.Install() err = playwright.Install()
if err != nil { if err != nil {
return nil, nil, err return Playwright{}, err
} }
} else if err == nil { } else if err == nil {
cmd := exec.Command(driver.DriverBinaryLocation, "--version") cmd := exec.Command(driver.DriverBinaryLocation, "--version")
@ -32,42 +82,44 @@ func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) {
if err != nil || !bytes.Contains(output, []byte(driver.Version)) { if err != nil || !bytes.Contains(output, []byte(driver.Version)) {
err = playwright.Install() err = playwright.Install()
if err != nil { if err != nil {
return nil, nil, err return Playwright{}, err
} }
} }
} }
pw, err := playwright.Run() pw, err := playwright.Run()
if err != nil { if err != nil {
return nil, nil, err return Playwright{}, err
} }
browser, err := pw.Chromium.Launch() browser, err := pw.Chromium.Launch()
if err != nil { if err != nil {
return nil, nil, err return Playwright{}, err
} }
return pw, browser, nil context, err := browser.NewContext()
if err != nil {
return Playwright{}, err
}
page, err := context.NewPage()
if err != nil {
return Playwright{}, err
}
return Playwright{
PW: pw,
Browser: browser,
BrowserContext: context,
Page: page,
}, nil
} }
//go:embed generate_png.js //go:embed generate_png.js
var genPNGScript string var genPNGScript string
func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err error) { func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) {
var page playwright.Page if page == nil {
defer func() error { ms.Log.Error.Printf("Playwright was not initialized properly for PNG export")
err = page.Close() return nil, fmt.Errorf("Playwright page is not initialized for png export")
if err != nil { }
return err
}
return nil
}()
if browser == nil {
return nil, fmt.Errorf("browser is not initialized for png export")
}
page, err = browser.NewPage()
if err != nil {
return nil, err
}
encodedSVG := base64.StdEncoding.EncodeToString(svg) encodedSVG := base64.StdEncoding.EncodeToString(svg)
pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG) pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG)
if err != nil { if err != nil {
@ -77,27 +129,9 @@ func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err
pngString := fmt.Sprintf("%v", pngInterface) pngString := fmt.Sprintf("%v", pngInterface)
pngPrefix := "data:image/png;base64," pngPrefix := "data:image/png;base64,"
if !strings.HasPrefix(pngString, pngPrefix) { if !strings.HasPrefix(pngString, pngPrefix) {
ms.Log.Error.Printf("failed to convert D2 file to PNG")
return nil, fmt.Errorf("playwright export generated invalid png") return nil, fmt.Errorf("playwright export generated invalid png")
} }
splicedPNGString := pngString[len(pngPrefix):] splicedPNGString := pngString[len(pngPrefix):]
outputImage, err = base64.StdEncoding.DecodeString(splicedPNGString) return base64.StdEncoding.DecodeString(splicedPNGString)
if err != nil {
return nil, err
}
return outputImage, nil
}
func Cleanup(pw *playwright.Playwright, browser playwright.Browser) (err error) {
if browser != nil {
if err = browser.Close(); err != nil {
return err
}
}
if pw != nil {
if err = pw.Stop(); err != nil {
return err
}
}
return nil
} }