diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml index e88f54eb9..83445d869 100644 --- a/.github/workflows/automated-releases.yml +++ b/.github/workflows/automated-releases.yml @@ -23,6 +23,7 @@ env: jobs: check-permissions: name: Check Release Permissions + permissions: {} runs-on: ubuntu-latest outputs: authorized: ${{ steps.check.outputs.authorized }} diff --git a/.github/workflows/test-nightly-releases.yml b/.github/workflows/test-nightly-releases.yml index 63df09935..1e6f12a69 100644 --- a/.github/workflows/test-nightly-releases.yml +++ b/.github/workflows/test-nightly-releases.yml @@ -1,4 +1,6 @@ name: Test Nightly Releases (Dry Run) +permissions: + contents: read on: workflow_dispatch: diff --git a/AGENTS.md b/AGENTS.md index 5c1a6e9f8..d4c111ea8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,6 +135,7 @@ For example: `bd create --help` shows `--parent`, `--deps`, `--assignee`, etc. - Check `bd ready` before asking "what should I work on?" - Store AI planning docs in `history/` directory - Run `bd --help` to discover available flags +- **ALWAYS run `coderabbit --plain` before committing** to get code analysis and catch issues early - Do NOT create markdown TODO lists - Do NOT use external issue trackers - Do NOT duplicate tracking systems diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 7529ea4e5..ee02aa8e3 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -171,7 +171,11 @@ export default defineConfig({ collapsed: true, autogenerate: { directory: "features/browser" }, }, - { label: "Drag & Drop", link: "/features/drag-drop" }, + { + label: "Drag & Drop", + collapsed: true, + autogenerate: { directory: "features/drag-and-drop" }, + }, { label: "Keyboard", collapsed: true, diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 2f826dab1..997dab63a 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,120 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.62 - 2026-01-22 + +## Fixed +- Fix SetProcessDpiAwarenessContext "Access is denied" error when DPI awareness is already set via application manifest (#4803) + +## v3.0.0-alpha.61 - 2026-01-20 + +## Fixed +- Update the docs page for keyboard shortcuts and corrects the type of the callback parameter for `KeyBinding.Add` by @ndianabasi +- Fix documentation regarding generating custom binding, must use `-d String` instead of `-o String` + +## v3.0.0-alpha.60 - 2026-01-14 + +## Fixed +- Fix menu not clearing children on `menu.Update()` + +## v3.0.0-alpha.59 - 2026-01-11 + +## Changed +- Update the README for the `Drag N Drop` example and highlights that `Internal Drag and Drop` is demonstrated with the example @ndianabasi + +## v3.0.0-alpha.58 - 2026-01-09 + +## Fixed +- Fix outdated Manager API references in documentation (31 files updated to use new pattern like `app.Window.New()`, `app.Event.Emit()`, etc.) by @leaanthony +- Fix Linux crash on panic in JS-bound Go methods due to WebKit overriding signal handlers (#3965) by @leaanthony + +## v3.0.0-alpha.57 - 2026-01-05 + +## Changed +- Replace various debug logs from Info to Debug (by @mbaklor) + +## Fixed +- Fix SaveFileDialog.SetFilename() having no effect on Linux (#4841) by @samstanier +- Fix drop coordinates showing as undefined in drag-n-drop example +- Fix macOS app bundle creation failing when APP_NAME contains spaces (brace expansion issue) +- Fix index out of bounds panic on Windows when calling service methods (revert goccy/go-json) + +## v3.0.0-alpha.56 - 2026-01-04 + +## Added +- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix + +## Changed +- **BREAKING:** Rename `EnableDragAndDrop` to `EnableFileDrop` in window options +- **BREAKING:** Rename `DropZoneDetails` to `DropTargetDetails` in event context +- **BREAKING:** Rename `DropZoneDetails()` method to `DropTargetDetails()` on `WindowEventContext` +- **BREAKING:** Remove `WindowDropZoneFilesDropped` event, use `WindowFilesDropped` instead +- **BREAKING:** Change HTML attribute from `data-wails-dropzone` to `data-file-drop-target` +- **BREAKING:** Change CSS hover class from `wails-dropzone-hover` to `file-drop-target-active` +- **BREAKING:** Remove `DragEffect`, `OnEnterEffect`, `OnOverEffect` options from Windows (were part of removed IDropTarget) + +## Fixed +- Fix file drag-and-drop on Windows not working at non-100% display scaling +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows +- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels) +- Fix file drag-and-drop on Linux not working reliably with hover effects +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux + +## Removed +- Remove native `IDropTarget` implementation on Windows in favor of JavaScript-based approach (matches v2 behavior) + +## v3.0.0-alpha.55 - 2026-01-02 + +## Changed +- Switch to goccy/go-json for all runtime JSON processing (method bindings, events, webview requests, notifications, kvstore), improving performance by 21-63% and reducing memory allocations by 40-60% +- Optimize BoundMethod struct layout and cache isVariadic flag to reduce per-call overhead +- Use stack-allocated argument buffer for methods with `<=8` arguments to avoid heap allocations +- Optimize result collection in method calls to avoid slice allocation for single return values +- Use sync.Map for MIME type cache to improve concurrent performance +- Use buffer pool for HTTP transport request body reading +- Lazily allocate CloseNotify channel in content type sniffer to reduce per-request allocations +- Remove debug CSS logging from asset server +- Expand MIME type extension map to cover 50+ common web formats (fonts, audio, video, etc.) + +## Fixed +- Update all commands in Taskfile.yml files for all operating systems to accommodate spaces in variables such as `APP_NAME` by @ndianabasi + +## Removed +- Remove github.com/wailsapp/mimetype dependency in favor of expanded extension map + stdlib http.DetectContentType, reducing binary size by ~1.2MB +- Remove gopkg.in/ini.v1 dependency by implementing minimal .desktop file parser for Linux file explorer, saving ~45KB +- Remove samber/lo from runtime code by using Go 1.21+ stdlib slices package and minimal internal helpers, saving ~310KB + +## v3.0.0-alpha.54 - 2025-12-29 + +## Added +- Add `CollectionBehavior` option to `MacWindow` for controlling window behavior across macOS Spaces and fullscreen (#4756) by @leaanthony + +## Fixed +- Fix command argument error when executing 'build:universal:lipo:go' task on Linux by @wux1an +- Fix Docker error "undefined symbol: ___ubsan_handle_xxxxxxx" when running 'wails3 build GOOS=darwin GOARCH=arm64' on Linux by @wux1an + +## Removed +- Remove debug printf statements from Darwin URL scheme handler (#4834) + +## v3.0.0-alpha.53 - 2025-12-27 + +## Added +- Add unit tests for pkg/application by @leaanthony +- Add custom protocol support to MSIX packaging by @leaanthony + +## Fixed +- Consolidate custom protocol documentation and add Universal Links sections by @leaanthony + +## v3.0.0-alpha.52 - 2025-12-26 + +## Fixed +- Fix Windows systray menu crash when clicking icon repeatedly by adding guard against concurrent TrackPopupMenuEx calls (#4151) by @leaanthony + +## v3.0.0-alpha.51 - 2025-12-23 + +## Fixed +- Prevent app crashing when calling systray.Run() before app.Run() by @leaanthony + ## v3.0.0-alpha.50 - 2025-12-21 ## Changed diff --git a/docs/src/content/docs/concepts/architecture.mdx b/docs/src/content/docs/concepts/architecture.mdx index 5b4fdd823..d72affe3b 100644 --- a/docs/src/content/docs/concepts/architecture.mdx +++ b/docs/src/content/docs/concepts/architecture.mdx @@ -264,7 +264,7 @@ Frontend2 -> EventBus: "On('data-updated', handler)" **Example:** ```go // Go: Emit an event -app.EmitEvent("user-logged-in", user) +app.Event.Emit("user-logged-in", user) ``` ```javascript diff --git a/docs/src/content/docs/concepts/bridge.mdx b/docs/src/content/docs/concepts/bridge.mdx index 6ee3745d1..7593d896a 100644 --- a/docs/src/content/docs/concepts/bridge.mdx +++ b/docs/src/content/docs/concepts/bridge.mdx @@ -538,7 +538,7 @@ func ProcessLargeFile(path string) error { for scanner.Scan() { lineNum++ // Emit progress events - app.EmitEvent("file-progress", map[string]interface{}{ + app.Event.Emit("file-progress", map[string]interface{}{ "line": lineNum, "text": scanner.Text(), }) diff --git a/docs/src/content/docs/concepts/build-system.mdx b/docs/src/content/docs/concepts/build-system.mdx index 0ac7d435e..f9de52bc5 100644 --- a/docs/src/content/docs/concepts/build-system.mdx +++ b/docs/src/content/docs/concepts/build-system.mdx @@ -325,7 +325,7 @@ func main() { }, }) - app.NewWebviewWindow() + app.Window.New() app.Run() } ``` diff --git a/docs/src/content/docs/contributing/runtime-internals.mdx b/docs/src/content/docs/contributing/runtime-internals.mdx index d150c2d8b..a56952627 100644 --- a/docs/src/content/docs/contributing/runtime-internals.mdx +++ b/docs/src/content/docs/contributing/runtime-internals.mdx @@ -39,7 +39,7 @@ win := app.Window.New(&application.WebviewWindowOptions{ win.Show() ``` -`NewWebviewWindow` delegates to `internal/runtime/webview_window_*.go` where +`app.Window.New()` delegates to `internal/runtime/webview_window_*.go` where platform-specific constructors live: ``` @@ -170,6 +170,80 @@ Follow this checklist and you'll keep the cross-platform contract intact. --- +## 9. Drag-and-Drop + +File drag-and-drop uses a **JavaScript-first approach** on all platforms. The native layer intercepts OS drag events, but the actual drop handling and DOM interaction happens in JavaScript. + +### Flow + +1. User drags files from OS over the Wails window +2. Native layer detects the drag and notifies JavaScript for hover effects +3. User drops files +4. Native layer sends file paths + coordinates to JavaScript +5. JavaScript finds the drop target element (`data-file-drop-target`) +6. JavaScript sends file paths + element details to Go backend +7. Go emits `WindowFilesDropped` event with full context + +### Platform Implementations + +| Platform | Native Layer | Key Challenge | +|----------|--------------|---------------| +| **Windows** | WebView2's built-in drag support | Coordinates in CSS pixels, no conversion needed | +| **macOS** | NSWindow drag delegates | Convert window-relative to webview-relative coords | +| **Linux** | GTK3 drag signals | Must distinguish file drags from internal HTML5 drags | + +### Linux: Distinguishing Drag Types + +GTK and WebKit both want to handle drag events. The key is checking the drag target type: + +```c +static gboolean is_file_drag(GdkDragContext *context) { + GList *targets = gdk_drag_context_list_targets(context); + for (GList *l = targets; l != NULL; l = l->next) { + GdkAtom atom = GDK_POINTER_TO_ATOM(l->data); + gchar *name = gdk_atom_name(atom); + if (name && g_strcmp0(name, "text/uri-list") == 0) { + g_free(name); + return TRUE; // External file drag + } + g_free(name); + } + return FALSE; // Internal HTML5 drag +} +``` + +Signal handlers return `FALSE` for internal drags (letting WebKit handle them) and `TRUE` for file drags (handling them ourselves). + +### Blocking File Drops + +When `EnableFileDrop` is `false`, we still need to prevent the browser from navigating to dropped files. Each platform handles this differently: + +- **Windows**: JavaScript calls `preventDefault()` on drag events +- **macOS**: JavaScript calls `preventDefault()` on drag events +- **Linux**: GTK signal handlers intercept and reject file drags at the native level + +### Key Files + +| File | Purpose | +|------|---------| +| `pkg/application/linux_cgo.go` | GTK drag signal handlers (C code in cgo preamble) | +| `pkg/application/webview_window_darwin.go` | macOS drag delegates | +| `pkg/application/webview_window_windows.go` | WebView2 message handling | +| `internal/runtime/desktop/@wailsio/runtime/src/window.ts` | JavaScript drop handling | + +### Debugging + +- **Linux**: Add `printf` in C code (remember `fflush(stdout)`) +- **Windows**: Use `globalApplication.debug()` +- **JavaScript**: Check browser console, enable debug mode + +Common issues: +1. **Internal HTML5 drag not working**: Native handler intercepting it (return `FALSE` for non-file drags) +2. **Hover effects not showing**: JavaScript handlers not being called +3. **Wrong coordinates**: Check coordinate space conversions + +--- + You now have a guided tour of the runtime internals. Combine this knowledge with the **Codebase Layout** map and the **Asset Server** docs to navigate confidently and make impactful contributions. Happy coding! diff --git a/docs/src/content/docs/faq.mdx b/docs/src/content/docs/faq.mdx index ffa28275c..3e0a6bd17 100644 --- a/docs/src/content/docs/faq.mdx +++ b/docs/src/content/docs/faq.mdx @@ -123,8 +123,8 @@ Use SignTool with your certificate. Yes! Wails v3 has native multi-window support: ```go -window1 := app.NewWebviewWindow() -window2 := app.NewWebviewWindow() +window1 := app.Window.New() +window2 := app.Window.New() ``` ### Does Wails support system tray? @@ -132,7 +132,7 @@ window2 := app.NewWebviewWindow() Yes! Create system tray applications: ```go -tray := app.NewSystemTray() +tray := app.SystemTray.New() tray.SetIcon(iconBytes) tray.SetMenu(menu) ``` @@ -142,7 +142,7 @@ tray.SetMenu(menu) Yes! Wails provides native dialogs: ```go -path, _ := app.OpenFileDialog(). +path, _ := app.Dialog.OpenFile(). SetTitle("Select File"). PromptForSingleSelection() ``` @@ -194,7 +194,7 @@ wails3 generate bindings Check if you called `Show()`: ```go -window := app.NewWebviewWindow() +window := app.Window.New() window.Show() // Don't forget this! ``` @@ -204,7 +204,7 @@ Ensure event names match exactly: ```go // Go -app.EmitEvent("my-event", data) +app.Event.Emit("my-event", data) // JavaScript OnEvent("my-event", handler) // Must match diff --git a/docs/src/content/docs/features/bindings/best-practices.mdx b/docs/src/content/docs/features/bindings/best-practices.mdx index bfaa77f7e..5b2d71d8d 100644 --- a/docs/src/content/docs/features/bindings/best-practices.mdx +++ b/docs/src/content/docs/features/bindings/best-practices.mdx @@ -274,7 +274,7 @@ func (s *Service) ProcessLargeFile(path string) error { processed++ // Emit progress - s.app.EmitEvent("progress", map[string]interface{}{ + s.app.Event.Emit("progress", map[string]interface{}{ "processed": processed, "total": total, "percent": int(float64(processed) / float64(total) * 100), diff --git a/docs/src/content/docs/features/bindings/methods.mdx b/docs/src/content/docs/features/bindings/methods.mdx index b67f0017c..76ff55edc 100644 --- a/docs/src/content/docs/features/bindings/methods.mdx +++ b/docs/src/content/docs/features/bindings/methods.mdx @@ -200,7 +200,7 @@ wails3 generate bindings -ts ### Custom Output Directory ```bash -wails3 generate bindings -o ./src/bindings +wails3 generate bindings -d ./src/bindings ``` ### Watch Mode (Development) @@ -461,7 +461,7 @@ const config = await GetConfig() func ProcessLargeFile(path string) error { // Emit progress events for line := range lines { - app.EmitEvent("progress", line) + app.Event.Emit("progress", line) } return nil } @@ -530,7 +530,7 @@ func main() { }, }) - app.NewWebviewWindow() + app.Window.New() app.Run() } ``` diff --git a/docs/src/content/docs/features/bindings/models.mdx b/docs/src/content/docs/features/bindings/models.mdx index f177545ae..f393eb694 100644 --- a/docs/src/content/docs/features/bindings/models.mdx +++ b/docs/src/content/docs/features/bindings/models.mdx @@ -628,7 +628,7 @@ func main() { }, }) - app.NewWebviewWindow() + app.Window.New() app.Run() } ``` diff --git a/docs/src/content/docs/features/bindings/services.mdx b/docs/src/content/docs/features/bindings/services.mdx index ace565d66..3d95606c3 100644 --- a/docs/src/content/docs/features/bindings/services.mdx +++ b/docs/src/content/docs/features/bindings/services.mdx @@ -438,7 +438,7 @@ func (o *OrderService) CreateOrder(items []Item) (*Order, error) { } // Emit event - o.app.EmitEvent("order-created", order) + o.app.Event.Emit("order-created", order) return order, nil } @@ -485,7 +485,7 @@ func NewNotificationService(app *application.Application) *NotificationService { func (n *NotificationService) Notify(message string) { // Use application to emit events - n.app.EmitEvent("notification", message) + n.app.Event.Emit("notification", message) // Or show system notification n.app.ShowNotification(message) @@ -780,7 +780,7 @@ func main() { }, }) - app.NewWebviewWindow() + app.Window.New() app.Run() } ``` diff --git a/docs/src/content/docs/features/clipboard/basics.mdx b/docs/src/content/docs/features/clipboard/basics.mdx index 3ecba9c0a..b75570aca 100644 --- a/docs/src/content/docs/features/clipboard/basics.mdx +++ b/docs/src/content/docs/features/clipboard/basics.mdx @@ -64,12 +64,12 @@ await CopyToClipboard("Text to copy") ```go func copyWithFeedback(text string) { if app.Clipboard.SetText(text) { - app.InfoDialog(). + app.Dialog.Info(). SetTitle("Copied"). SetMessage("Text copied to clipboard!"). Show() } else { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Copy Failed"). SetMessage("Failed to copy to clipboard."). Show() @@ -296,7 +296,7 @@ func (cm *ClipboardMonitor) checkClipboard() { if text != cm.lastText { cm.lastText = text - cm.app.EmitEvent("clipboard-changed", text) + cm.app.Event.Emit("clipboard-changed", text) } } ``` diff --git a/docs/src/content/docs/features/dialogs/custom.mdx b/docs/src/content/docs/features/dialogs/custom.mdx index 8cb354772..591d68db2 100644 --- a/docs/src/content/docs/features/dialogs/custom.mdx +++ b/docs/src/content/docs/features/dialogs/custom.mdx @@ -14,7 +14,7 @@ Create **custom dialog windows** using regular Wails windows with dialog-like be ```go // Create custom dialog window -dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Custom dialog", Width: 400, Height: 300, @@ -48,7 +48,7 @@ func NewCustomdialog(app *application.Application) *Customdialog { result: make(chan string, 1), } - dialog.window = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + dialog.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Custom dialog", Width: 400, Height: 300, @@ -79,7 +79,7 @@ func (d *Customdialog) Close(result string) { ```go func ShowModaldialog(parent *application.WebviewWindow, title string) string { // Create dialog - dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 200, @@ -119,7 +119,7 @@ func NewFormdialog(app *application.Application) *Formdialog { done: make(chan bool, 1), } - fd.window = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + fd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Enter Information", Width: 500, Height: 400, @@ -156,7 +156,7 @@ func (fd *Formdialog) Cancel() { ```go func ShowConfirmdialog(message string) bool { - dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Confirm", Width: 400, Height: 150, @@ -172,12 +172,12 @@ func ShowConfirmdialog(message string) bool { result := make(chan bool, 1) // Handle responses - app.OnEvent("confirm-yes", func(e *application.CustomEvent) { + app.Event.On("confirm-yes", func(e *application.CustomEvent) { result <- true dialog.Close() }) - app.OnEvent("confirm-no", func(e *application.CustomEvent) { + app.Event.On("confirm-no", func(e *application.CustomEvent) { result <- false dialog.Close() }) @@ -215,7 +215,7 @@ function confirm(result) { ```go func ShowInputdialog(prompt string, defaultValue string) (string, bool) { - dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Input", Width: 400, Height: 150, @@ -234,7 +234,7 @@ func ShowInputdialog(prompt string, defaultValue string) (string, bool) { }) }) - app.OnEvent("input-submit", func(e *application.CustomEvent) { + app.Event.On("input-submit", func(e *application.CustomEvent) { result <- struct { value string ok bool @@ -242,7 +242,7 @@ func ShowInputdialog(prompt string, defaultValue string) (string, bool) { dialog.Close() }) - app.OnEvent("input-cancel", func(e *application.CustomEvent) { + app.Event.On("input-cancel", func(e *application.CustomEvent) { result <- struct { value string ok bool @@ -266,7 +266,7 @@ type Progressdialog struct { func NewProgressdialog(title string) *Progressdialog { pd := &Progressdialog{} - pd.window = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + pd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 150, @@ -336,7 +336,7 @@ func NewLogindialog(app *application.Application) *Logindialog { }, 1), } - ld.window = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + ld.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Login", Width: 400, Height: 250, @@ -421,7 +421,7 @@ func NewSettingsdialog(app *application.Application, current map[string]interfac done: make(chan bool, 1), } - sd.window = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + sd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Settings", Width: 600, Height: 500, @@ -470,7 +470,7 @@ func NewWizarddialog(app *application.Application) *Wizarddialog { done: make(chan bool, 1), } - wd.window = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + wd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Setup Wizard", Width: 600, Height: 400, diff --git a/docs/src/content/docs/features/drag-and-drop/files.mdx b/docs/src/content/docs/features/drag-and-drop/files.mdx new file mode 100644 index 000000000..ffc75093a --- /dev/null +++ b/docs/src/content/docs/features/drag-and-drop/files.mdx @@ -0,0 +1,212 @@ +--- +title: File Drop +description: Accept files dragged from the operating system into your application +sidebar: + order: 1 +--- + +Wails lets users drag files from the operating system (file manager, desktop) into your application. Unlike HTML5 drag-and-drop which only works within the browser, this gives you access to actual file paths on disk. + +## Enable File Drop + +File drop is disabled by default. To enable it, set `EnableFileDrop: true` in your window options: + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 800, + Height: 600, + EnableFileDrop: true, +}) +``` + +When `EnableFileDrop` is `false` (the default), files dragged from the OS are blocked - they won't open in the webview or trigger any events. This prevents accidental navigation when users drag files over your app. + +## Define Drop Zones + +Drop zones tell Wails which elements should accept files. Files dropped outside a drop zone are ignored. + +Add the `data-file-drop-target` attribute to any element: + +```html +
+ Drop files here +
+``` + +You can have multiple drop zones. The element's `id` and CSS classes are passed to your Go code, so you can handle drops differently depending on where files land. + +## Style Drag Hover + +When files are dragged over a drop zone, Wails adds the `file-drop-target-active` class. This lets you provide visual feedback so users know where they can drop: + +```css +.drop-zone { + border: 2px dashed #ccc; + padding: 40px; + text-align: center; + transition: all 0.2s ease; +} + +.drop-zone.file-drop-target-active { + border-color: #007bff; + background-color: rgba(0, 123, 255, 0.1); +} +``` + +The class is removed automatically when files leave the zone or are dropped. + +## Detect Dropped Files + +When files are dropped on a valid drop zone, Wails fires a `WindowFilesDropped` event. The event context contains the full filesystem paths of all dropped files: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + + for _, file := range files { + fmt.Println("Dropped:", file) + } +}) +``` + +The paths are absolute, like `/home/user/documents/report.pdf` or `C:\Users\Name\Documents\report.pdf`. + +## Get Drop Target Info + +When you have multiple drop zones, you can find out which one received the files using `DropTargetDetails()`: + +```go +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + fmt.Printf("Dropped on element: id=%s, classes=%v\n", + details.ElementID, details.ClassList) + fmt.Printf("Position: x=%d, y=%d\n", details.X, details.Y) +}) +``` + +This lets you route files to different handlers: + +```go +switch details.ElementID { +case "images": + handleImageUpload(files) +case "documents": + handleDocumentUpload(files) +} +``` + +## Complete Example + +**Go:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "File Uploader", + EnableFileDrop: true, +}) + +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + // Send to frontend + app.Event.Emit("files-dropped", map[string]any{ + "files": files, + "target": details.ElementID, + }) +}) +``` + +**HTML:** + +```html +
+ Drop images here +
+ +
+ Drop documents here +
+ + +``` + +## Full Window Drop + +If you want files to be droppable anywhere in your app, add the attribute to the body element: + +```html + + + +``` + +You can use a CSS overlay to indicate the entire window is a drop target: + +```css +body.file-drop-target-active::after { + content: "Drop files anywhere"; + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #007bff; + background: rgba(255, 255, 255, 0.9); + pointer-events: none; +} +``` + +## Combining with HTML Drag & Drop + +You can use both external file drops and internal HTML drag-and-drop in the same application. When `EnableFileDrop` is `true`, Wails intercepts external file drags but lets internal HTML5 drags pass through normally. + +To distinguish between them in your HTML drop zone handlers, check if the drag contains files: + +```javascript +zone.addEventListener('dragenter', (e) => { + // Skip external file drags - Wails handles these + if (e.dataTransfer?.types.includes('Files')) { + return; + } + // Handle internal HTML5 drags + zone.classList.add('drag-over'); +}); + +zone.addEventListener('drop', (e) => { + // Skip external file drops - Wails handles these + if (e.dataTransfer?.types.includes('Files')) { + return; + } + e.preventDefault(); + zone.classList.remove('drag-over'); + // Handle internal drop +}); +``` + +This ensures your HTML drop handlers only respond to internal drags (like moving list items), while Wails handles external file drops separately via the `WindowFilesDropped` event. + +## Next Steps + +- [HTML Drag & Drop](./html) - Drag elements within your app +- [Window Options](/features/windows/options) - All window configuration options diff --git a/docs/src/content/docs/features/drag-and-drop/html.mdx b/docs/src/content/docs/features/drag-and-drop/html.mdx new file mode 100644 index 000000000..7eeebc7be --- /dev/null +++ b/docs/src/content/docs/features/drag-and-drop/html.mdx @@ -0,0 +1,232 @@ +--- +title: HTML Drag & Drop +description: Drag and drop elements within your application +sidebar: + order: 2 +--- + +HTML5 drag-and-drop lets users drag elements within your app's UI - for example, reordering a list or moving items between columns. This is standard web functionality that works in Wails without any special setup. + +## Make an Element Draggable + +By default, most elements can't be dragged. To make an element draggable, add `draggable="true"`: + +```html +
Drag me
+``` + +The element will now show a drag preview when the user clicks and drags it. + +## Define a Drop Zone + +Elements don't accept drops by default. To make an element accept drops, you need to cancel the default behaviour on `dragover`: + +```html +
Drop here
+ + +``` + +Calling `preventDefault()` on `dragover` is required - it signals that this element accepts drops. Without it, the drop event won't fire. + +## Style Drag Hover + +To show users where they can drop, add visual feedback when dragging over a drop zone. The `dragenter` event fires when something enters the zone, and `dragleave` fires when it leaves: + +```css +.drop-zone { + border: 2px dashed #ccc; + padding: 40px; + transition: all 0.2s ease; +} + +.drop-zone.drag-over { + border-color: #007bff; + background-color: rgba(0, 123, 255, 0.1); +} +``` + +```javascript +const target = document.getElementById('target'); + +target.addEventListener('dragenter', () => { + target.classList.add('drag-over'); +}); + +target.addEventListener('dragleave', () => { + target.classList.remove('drag-over'); +}); + +target.addEventListener('drop', (e) => { + e.preventDefault(); + target.classList.remove('drag-over'); + // Handle the drop +}); +``` + +Note: `dragleave` also fires when entering a child element, which can cause flickering. The complete example below shows how to handle this. + +## Complete Example + +A task list where items can be dragged between priority columns. This tracks the dragged element in a variable, which is the simplest approach when everything is on the same page: + +```html +
+
Fix login bug
+
Update docs
+
Add dark mode
+
+ +
+
+

High Priority

+
    +
    +
    +

    Low Priority

    +
      +
      +
      + + + + +``` + +## Combining with File Drop + +If your app uses both HTML drag-and-drop and [File Drop](./files), your HTML drop zones will also receive events when users drag files from the operating system. To prevent confusion, filter out file drags in your handlers: + +```javascript +zone.addEventListener('dragenter', (e) => { + // Ignore external file drags + if (e.dataTransfer?.types.includes('Files')) return; + + zone.classList.add('drag-over'); +}); + +zone.addEventListener('dragover', (e) => { + // Ignore external file drags + if (e.dataTransfer?.types.includes('Files')) return; + + e.preventDefault(); +}); + +zone.addEventListener('drop', (e) => { + // Ignore external file drags + if (e.dataTransfer?.types.includes('Files')) return; + + e.preventDefault(); + zone.classList.remove('drag-over'); + // Handle the internal drop +}); +``` + +The `dataTransfer.types` array contains `'Files'` when the user is dragging files from the OS, but contains types like `'text/plain'` for internal HTML drags. This lets you distinguish between the two. + +## Passing Data with dataTransfer + +The example above tracks the dragged element in a JavaScript variable. This works well when everything is on the same page. But if you need to drag between iframes or pass data that isn't tied to a DOM element, use the `dataTransfer` API: + +```javascript +// When drag starts, store data +item.addEventListener('dragstart', (e) => { + e.dataTransfer.setData('text/plain', item.id); +}); + +// When dropped, retrieve the data +target.addEventListener('drop', (e) => { + e.preventDefault(); + const itemId = e.dataTransfer.getData('text/plain'); + const item = document.getElementById(itemId); + // Move or copy the item +}); +``` + +The data is stored as strings, so you'll need to serialize objects with `JSON.stringify()` if needed. + +## Next Steps + +- [File Drop](./files) - Accept files from the operating system +- [MDN Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - Full browser API reference diff --git a/docs/src/content/docs/features/events/system.mdx b/docs/src/content/docs/features/events/system.mdx index f55ffca03..259add09c 100644 --- a/docs/src/content/docs/features/events/system.mdx +++ b/docs/src/content/docs/features/events/system.mdx @@ -16,7 +16,7 @@ Wails provides a **unified event system** for pub/sub communication. Emit events **Go (emit):** ```go -app.EmitEvent("user-logged-in", map[string]interface{}{ +app.Event.Emit("user-logged-in", map[string]interface{}{ "userId": 123, "name": "Alice", }) @@ -42,9 +42,9 @@ Your application-specific events: ```go // Emit from Go -app.EmitEvent("order-created", order) -app.EmitEvent("payment-processed", payment) -app.EmitEvent("notification", message) +app.Event.Emit("order-created", order) +app.Event.Emit("payment-processed", payment) +app.Event.Emit("notification", message) ``` ```javascript @@ -62,14 +62,14 @@ Built-in OS and application events: import "github.com/wailsapp/wails/v3/pkg/events" // Theme changes -app.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { if e.Context().IsDarkMode() { app.Logger.Info("Dark mode enabled") } }) // Application lifecycle -app.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { app.Logger.Info("Application started") }) ``` @@ -95,29 +95,29 @@ window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEven **Basic emit:** ```go -app.EmitEvent("event-name", data) +app.Event.Emit("event-name", data) ``` **With different data types:** ```go // String -app.EmitEvent("message", "Hello") +app.Event.Emit("message", "Hello") // Number -app.EmitEvent("count", 42) +app.Event.Emit("count", 42) // Struct -app.EmitEvent("user", User{ID: 1, Name: "Alice"}) +app.Event.Emit("user", User{ID: 1, Name: "Alice"}) // Map -app.EmitEvent("config", map[string]interface{}{ +app.Event.Emit("config", map[string]interface{}{ "theme": "dark", "fontSize": 14, }) // Array -app.EmitEvent("items", []string{"a", "b", "c"}) +app.Event.Emit("items", []string{"a", "b", "c"}) ``` **To specific window:** @@ -145,7 +145,7 @@ Emit("broadcast-message", "Hello everyone") **Application events:** ```go -app.OnEvent("custom-event", func(e *application.CustomEvent) { +app.Event.On("custom-event", func(e *application.CustomEvent) { data := e.Data // Handle event }) @@ -154,7 +154,7 @@ app.OnEvent("custom-event", func(e *application.CustomEvent) { **With type assertion:** ```go -app.OnEvent("user-updated", func(e *application.CustomEvent) { +app.Event.On("user-updated", func(e *application.CustomEvent) { user := e.Data.(User) app.Logger.Info("User updated", "name", user.Name) }) @@ -164,9 +164,9 @@ app.OnEvent("user-updated", func(e *application.CustomEvent) { ```go // All handlers will be called -app.OnEvent("order-created", logOrder) -app.OnEvent("order-created", sendEmail) -app.OnEvent("order-created", updateInventory) +app.Event.On("order-created", logOrder) +app.Event.On("order-created", sendEmail) +app.Event.On("order-created", updateInventory) ``` ### In JavaScript @@ -208,18 +208,18 @@ OnEvent("data-updated", logChange) import "github.com/wailsapp/wails/v3/pkg/events" // Application started -app.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { app.Logger.Info("App started") }) // Theme changed -app.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { isDark := e.Context().IsDarkMode() - app.EmitEvent("theme-changed", isDark) + app.Event.Emit("theme-changed", isDark) }) // File opened -app.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) { filePath := e.Context().OpenedFile() openFile(filePath) }) @@ -231,12 +231,12 @@ app.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *applicat ```go // Application became active - app.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) { app.Logger.Info("App became active") }) // Application will terminate - app.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) { cleanup() }) ``` @@ -245,12 +245,12 @@ app.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *applicat ```go // Power status changed - app.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) { app.Logger.Info("Power status changed") }) // System suspending - app.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) { saveState() }) ``` @@ -259,12 +259,12 @@ app.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *applicat ```go // Application startup - app.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) { app.Logger.Info("App starting") }) // Theme changed - app.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) { updateTheme() }) ``` @@ -347,23 +347,23 @@ func (o *OrderService) CreateOrder(items []Item) (*Order, error) { } // Publish event - o.app.EmitEvent("order-created", order) + o.app.Event.Emit("order-created", order) return order, nil } // Subscribers -app.OnEvent("order-created", func(e *application.CustomEvent) { +app.Event.On("order-created", func(e *application.CustomEvent) { order := e.Data.(*Order) sendConfirmationEmail(order) }) -app.OnEvent("order-created", func(e *application.CustomEvent) { +app.Event.On("order-created", func(e *application.CustomEvent) { order := e.Data.(*Order) updateInventory(order) }) -app.OnEvent("order-created", func(e *application.CustomEvent) { +app.Event.On("order-created", func(e *application.CustomEvent) { order := e.Data.(*Order) logOrder(order) }) @@ -376,14 +376,14 @@ app.OnEvent("order-created", func(e *application.CustomEvent) { Emit("get-user-data", { userId: 123 }) // Backend responds -app.OnEvent("get-user-data", func(e *application.CustomEvent) { +app.Event.On("get-user-data", func(e *application.CustomEvent) { data := e.Data.(map[string]interface{}) userId := int(data["userId"].(float64)) user := getUserFromDB(userId) // Send response - app.EmitEvent("user-data-response", user) + app.Event.Emit("user-data-response", user) }) // Frontend receives response @@ -398,7 +398,7 @@ OnEvent("user-data-response", (user) => { ```go // Broadcast to all windows -app.EmitEvent("global-notification", "System update available") +app.Event.Emit("global-notification", "System update available") // Each window handles it OnEvent("global-notification", (message) => { @@ -422,7 +422,7 @@ func (ea *EventAggregator) Add(event Event) { // Emit batch every 100 events if len(ea.events) >= 100 { - app.EmitEvent("event-batch", ea.events) + app.Event.Emit("event-batch", ea.events) ea.events = nil } } @@ -446,7 +446,7 @@ type NotificationService struct { func (n *NotificationService) Notify(message string) { // Emit to all windows - n.app.EmitEvent("notification", map[string]interface{}{ + n.app.Event.Emit("notification", map[string]interface{}{ "message": message, "timestamp": time.Now(), }) @@ -460,13 +460,13 @@ func main() { notifService := &NotificationService{app: app} // System events - app.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { isDark := e.Context().IsDarkMode() - app.EmitEvent("theme-changed", isDark) + app.Event.Emit("theme-changed", isDark) }) // Custom events from frontend - app.OnEvent("user-action", func(e *application.CustomEvent) { + app.Event.On("user-action", func(e *application.CustomEvent) { data := e.Data.(map[string]interface{}) action := data["action"].(string) @@ -477,15 +477,15 @@ func main() { }) // Window events - window := app.NewWebviewWindow() + window := app.Window.New() window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { - app.EmitEvent("window-focused", window.Name()) + app.Event.Emit("window-focused", window.Name()) }) window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { // Confirm before close - app.EmitEvent("confirm-close", nil) + app.Event.Emit("confirm-close", nil) e.Cancel() // Wait for confirmation }) diff --git a/docs/src/content/docs/features/keyboard/shortcuts.mdx b/docs/src/content/docs/features/keyboard/shortcuts.mdx index 51ac8ce9e..ba83603f8 100644 --- a/docs/src/content/docs/features/keyboard/shortcuts.mdx +++ b/docs/src/content/docs/features/keyboard/shortcuts.mdx @@ -29,7 +29,7 @@ keyBindings := app.KeyBinding Register a simple keyboard shortcut: ```go -app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { // Handle save action app.Logger.Info("Save shortcut triggered") // Perform save operation... @@ -42,12 +42,12 @@ Register multiple shortcuts for common operations: ```go // File operations -app.KeyBinding.Add("Ctrl+N", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+N", func(window application.Window) { // New file window.EmitEvent("file:new", nil) }) -app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+O", func(window application.Window) { // Open file dialog := app.Dialog.OpenFile() if file, err := dialog.PromptForSingleSelection(); err == nil { @@ -55,23 +55,23 @@ app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) { } }) -app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { // Save file window.EmitEvent("file:save", nil) }) // Edit operations -app.KeyBinding.Add("Ctrl+Z", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+Z", func(window application.Window) { // Undo window.EmitEvent("edit:undo", nil) }) -app.KeyBinding.Add("Ctrl+Y", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+Y", func(window application.Window) { // Redo (Windows/Linux) window.EmitEvent("edit:redo", nil) }) -app.KeyBinding.Add("Cmd+Shift+Z", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Cmd+Shift+Z", func(window application.Window) { // Redo (macOS) window.EmitEvent("edit:redo", nil) }) @@ -139,7 +139,7 @@ Remove key bindings when they're no longer needed: app.KeyBinding.Remove("Ctrl+S") // Example: Temporary key binding for a modal -app.KeyBinding.Add("Escape", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Escape", func(window application.Window) { // Close modal window.EmitEvent("modal:close", nil) // Remove this temporary binding @@ -165,7 +165,7 @@ for _, binding := range allBindings { Make key bindings context-aware by checking application state: ```go -app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { // Check current application state if isEditMode() { // Save document @@ -184,7 +184,7 @@ app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) { Key bindings receive the active window, allowing window-specific behavior: ```go -app.KeyBinding.Add("F11", func(window *application.WebviewWindow) { +app.KeyBinding.Add("F11", func(window application.Window) { // Toggle fullscreen for the active window if window.Fullscreen() { window.SetFullscreen(false) @@ -193,7 +193,7 @@ app.KeyBinding.Add("F11", func(window *application.WebviewWindow) { } }) -app.KeyBinding.Add("Ctrl+W", func(window *application.WebviewWindow) { +app.KeyBinding.Add("Ctrl+W", func(window application.Window) { // Close the active window window.Close() }) @@ -206,15 +206,15 @@ Dynamically add and remove key bindings based on application state: ```go func enableEditMode() { // Add edit-specific key bindings - app.KeyBinding.Add("Ctrl+B", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+B", func(window application.Window) { window.EmitEvent("format:bold", nil) }) - app.KeyBinding.Add("Ctrl+I", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+I", func(window application.Window) { window.EmitEvent("format:italic", nil) }) - app.KeyBinding.Add("Ctrl+U", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+U", func(window application.Window) { window.EmitEvent("format:underline", nil) }) } @@ -313,7 +313,7 @@ func disableEditMode() { 2. **Provide Visual Feedback**: Let users know when shortcuts are triggered: ```go - app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+S", func(window application.Window) { saveDocument() // Show brief notification window.EmitEvent("notification:show", "Document saved") @@ -330,7 +330,7 @@ func disableEditMode() { 4. **Document Shortcuts**: Provide help or documentation for available shortcuts: ```go - app.KeyBinding.Add("F1", func(window *application.WebviewWindow) { + app.KeyBinding.Add("F1", func(window application.Window) { // Show help dialog with available shortcuts showKeyboardShortcutsHelp() }) @@ -342,7 +342,7 @@ func disableEditMode() { app.KeyBinding.Add("Escape", exitEditModeHandler) } - func exitEditModeHandler(window *application.WebviewWindow) { + func exitEditModeHandler(window application.Window) { exitEditMode() app.KeyBinding.Remove("Escape") // Clean up temporary binding } @@ -367,33 +367,33 @@ func main() { // File operations if runtime.GOOS == "darwin" { - app.KeyBinding.Add("Cmd+N", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Cmd+N", func(window application.Window) { window.EmitEvent("file:new", nil) }) - app.KeyBinding.Add("Cmd+O", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Cmd+O", func(window application.Window) { openFile(app, window) }) - app.KeyBinding.Add("Cmd+S", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Cmd+S", func(window application.Window) { window.EmitEvent("file:save", nil) }) } else { - app.KeyBinding.Add("Ctrl+N", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+N", func(window application.Window) { window.EmitEvent("file:new", nil) }) - app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+O", func(window application.Window) { openFile(app, window) }) - app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) { + app.KeyBinding.Add("Ctrl+S", func(window application.Window) { window.EmitEvent("file:save", nil) }) } // View operations - app.KeyBinding.Add("F11", func(window *application.WebviewWindow) { + app.KeyBinding.Add("F11", func(window application.Window) { window.SetFullscreen(!window.Fullscreen()) }) - app.KeyBinding.Add("F1", func(window *application.WebviewWindow) { + app.KeyBinding.Add("F1", func(window application.Window) { showKeyboardShortcuts(window) }) @@ -407,7 +407,7 @@ func main() { } } -func openFile(app *application.App, window *application.WebviewWindow) { +func openFile(app *application.App, window application.Window) { dialog := app.Dialog.OpenFile() dialog.AddFilter("Text Files", "*.txt;*.md") @@ -416,7 +416,7 @@ func openFile(app *application.App, window *application.WebviewWindow) { } } -func showKeyboardShortcuts(window *application.WebviewWindow) { +func showKeyboardShortcuts(window application.Window) { shortcuts := ` Keyboard Shortcuts: - Ctrl/Cmd+N: New file @@ -435,4 +435,4 @@ Test your key bindings on all target platforms to ensure they work correctly and :::danger[Warning] Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications. -::: \ No newline at end of file +::: diff --git a/docs/src/content/docs/features/menus/application.mdx b/docs/src/content/docs/features/menus/application.mdx index 92d9ff74a..9e39e5caf 100644 --- a/docs/src/content/docs/features/menus/application.mdx +++ b/docs/src/content/docs/features/menus/application.mdx @@ -60,7 +60,7 @@ func main() { app.SetMenu(menu) // Create window and run - app.NewWebviewWindow() + app.Window.New() app.Run() } ``` @@ -487,7 +487,7 @@ func main() { createMenu(app) // Create main window - app.NewWebviewWindow() + app.Window.New() app.Run() } diff --git a/docs/src/content/docs/features/menus/context.mdx b/docs/src/content/docs/features/menus/context.mdx index 3321cc85d..ab7eb630a 100644 --- a/docs/src/content/docs/features/menus/context.mdx +++ b/docs/src/content/docs/features/menus/context.mdx @@ -482,7 +482,7 @@ func main() { textMenu := createTextMenu(app) app.RegisterContextMenu("text-menu", textMenu) - app.NewWebviewWindow() + app.Window.New() app.Run() } diff --git a/docs/src/content/docs/features/menus/systray.mdx b/docs/src/content/docs/features/menus/systray.mdx index beb81393d..a0c8a722b 100644 --- a/docs/src/content/docs/features/menus/systray.mdx +++ b/docs/src/content/docs/features/menus/systray.mdx @@ -38,7 +38,7 @@ func main() { }) // Create system tray - systray := app.NewSystemTray() + systray := app.SystemTray.New() systray.SetIcon(icon) systray.SetLabel("My App") @@ -53,7 +53,7 @@ func main() { systray.SetMenu(menu) // Create hidden window - window := app.NewWebviewWindow() + window := app.Window.New() window.Hide() app.Run() @@ -68,7 +68,7 @@ func main() { ```go // Create system tray -systray := app.NewSystemTray() +systray := app.SystemTray.New() // Set icon systray.SetIcon(iconBytes) @@ -95,7 +95,7 @@ func main() { Name: "My App", }) - systray := app.NewSystemTray() + systray := app.SystemTray.New() systray.SetIcon(icon) systray.SetDarkModeIcon(iconDark) // macOS dark mode @@ -162,7 +162,7 @@ Attach a window to the tray icon for automatic show/hide: ```go // Create window -window := app.NewWebviewWindow() +window := app.Window.New() // Attach to tray systray.AttachWindow(window) @@ -181,7 +181,7 @@ systray.SetWindowDebounce(200 * time.Millisecond) // Click debounce **Example: Popup window** ```go -window := app.NewWebviewWindow(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Quick Access", Width: 300, Height: 400, @@ -198,7 +198,7 @@ systray.SetWindowOffset(5) Handle tray icon clicks: ```go -systray := app.NewSystemTray() +systray := app.SystemTray.New() // Left click systray.OnClick(func() { @@ -445,7 +445,7 @@ func main() { func (t *TrayApp) setup() { // Create system tray - t.systray = t.app.NewSystemTray() + t.systray = t.app.SystemTray.New() t.systray.SetIcon(icon) t.systray.SetLabel("Inactive") @@ -453,7 +453,7 @@ func (t *TrayApp) setup() { t.createMenu() // Create window (hidden by default) - t.window = t.app.NewWebviewWindow(application.WebviewWindowOptions{ + t.window = t.app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Tray Application", Width: 400, Height: 600, diff --git a/docs/src/content/docs/features/screens/info.mdx b/docs/src/content/docs/features/screens/info.mdx index 98c744f88..a1ade0a27 100644 --- a/docs/src/content/docs/features/screens/info.mdx +++ b/docs/src/content/docs/features/screens/info.mdx @@ -266,7 +266,7 @@ func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error { screen := screens[screenIndex] // Create window - window := m.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := m.app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: fmt.Sprintf("Window on %s", screen.Name), Width: 800, Height: 600, @@ -346,7 +346,7 @@ func createDPIAwareWindow(screen *Screen) *application.WebviewWindow { width := int(float32(baseWidth) * screen.ScaleFactor) height := int(float32(baseHeight) * screen.ScaleFactor) - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "DPI-Aware Window", Width: width, Height: height, diff --git a/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx index 007c62c52..54bbb04fa 100644 --- a/docs/src/content/docs/features/windows/basics.mdx +++ b/docs/src/content/docs/features/windows/basics.mdx @@ -24,7 +24,7 @@ func main() { }) // Create a window - window := app.NewWebviewWindow() + window := app.Window.New() // Configure it window.SetTitle("Hello Wails") @@ -47,7 +47,7 @@ func main() { The simplest way to create a window: ```go -window := app.NewWebviewWindow() +window := app.Window.New() ``` **What you get:** @@ -61,7 +61,7 @@ window := app.NewWebviewWindow() Create a window with custom configuration: ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My Application", Width: 1200, Height: 800, @@ -101,7 +101,7 @@ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ Give windows names for easy retrieval: ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main-window", Title: "Main Application", }) @@ -259,7 +259,7 @@ foundWindow := app.GetWindowByID(id) Get the currently focused window: ```go -current := app.CurrentWindow() +current := app.Window.Current() if current != nil { current.SetTitle("Active Window") } @@ -270,7 +270,7 @@ if current != nil { Get all windows: ```go -windows := app.GetAllWindows() +windows := app.Window.GetAll() fmt.Printf("Total windows: %d\n", len(windows)) for _, w := range windows { @@ -323,7 +323,7 @@ window.OnDestroy(func() { ```go // Main window -mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Main Application", Width: 1200, @@ -331,7 +331,7 @@ mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ }) // Settings window -settingsWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, @@ -349,12 +349,12 @@ Windows can communicate via events: ```go // In main window -app.EmitEvent("data-updated", map[string]interface{}{ +app.Event.Emit("data-updated", map[string]interface{}{ "value": 42, }) // In settings window -app.OnEvent("data-updated", func(event *application.WailsEvent) { +app.Event.On("data-updated", func(event *application.WailsEvent) { data := event.Data.(map[string]interface{}) value := data["value"].(int) fmt.Printf("Received: %d\n", value) @@ -367,7 +367,7 @@ app.OnEvent("data-updated", func(event *application.WailsEvent) { ```go // Create child window -childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Child Window", Parent: mainWindow, // Set parent }) @@ -410,22 +410,29 @@ childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ **macOS-specific features:** - + ```go // Transparent title bar - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ - Mac: application.MacOptions{ - TitleBarAppearsTransparent: true, - Backdrop: application.MacBackdropTranslucent, + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + }, + Backdrop: application.MacBackdropTranslucent, }, }) ``` - + **Backdrop types:** - `MacBackdropNormal` - Standard window - `MacBackdropTranslucent` - Translucent background - `MacBackdropTransparent` - Fully transparent - + + **Collection behavior:** + Control how windows behave across Spaces: + - `MacWindowCollectionBehaviorCanJoinAllSpaces` - Visible on all Spaces + - `MacWindowCollectionBehaviorFullScreenAuxiliary` - Can overlay fullscreen apps + **Native fullscreen:** macOS fullscreen creates a new Space (virtual desktop). @@ -435,7 +442,7 @@ childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ ```go // Set window icon - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Linux: application.LinuxOptions{ Icon: iconBytes, }, @@ -460,7 +467,7 @@ childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ ```go // Create splash screen -splash := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Loading...", Width: 400, Height: 300, @@ -486,7 +493,7 @@ var settingsWindow *application.WebviewWindow func showSettings() { if settingsWindow == nil { - settingsWindow = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, diff --git a/docs/src/content/docs/features/windows/events.mdx b/docs/src/content/docs/features/windows/events.mdx index 8d3ba317d..b45c6596c 100644 --- a/docs/src/content/docs/features/windows/events.mdx +++ b/docs/src/content/docs/features/windows/events.mdx @@ -74,7 +74,7 @@ window.OnClose(func() bool { } // Show confirmation dialog - dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Confirm Close", Width: 400, Height: 150, @@ -164,7 +164,7 @@ window.OnFocus(func() { refreshContent() // Notify other windows - app.EmitEvent("window-focused", window.ID()) + app.Event.Emit("window-focused", window.ID()) }) ``` @@ -437,7 +437,7 @@ func main() { } func (mw *ManagedWindow) CreateWindow() { - mw.window = mw.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Event Demo", Width: 800, @@ -566,11 +566,11 @@ Coordinate between multiple windows: // In main window mainWindow.OnFocus(func() { // Notify all windows - app.EmitEvent("main-window-focused", nil) + app.Event.Emit("main-window-focused", nil) }) // In other windows -app.OnEvent("main-window-focused", func(event *application.WailsEvent) { +app.Event.On("main-window-focused", func(event *application.WailsEvent) { // Update UI updateRelativeToMain() }) @@ -589,7 +589,7 @@ window.OnMaximise(func() { window.EmitEvent("layout-changed", "maximised") // Notify other windows - app.EmitEvent("window-maximised", window.ID()) + app.Event.Emit("window-maximised", window.ID()) }) ``` @@ -656,7 +656,7 @@ window.Destroy() ```go // Register handlers immediately after creation -window := app.NewWebviewWindow() +window := app.Window.New() window.OnClose(func() bool { return true }) ``` diff --git a/docs/src/content/docs/features/windows/frameless.mdx b/docs/src/content/docs/features/windows/frameless.mdx index 4c6b266f8..c13cb9d4d 100644 --- a/docs/src/content/docs/features/windows/frameless.mdx +++ b/docs/src/content/docs/features/windows/frameless.mdx @@ -14,7 +14,7 @@ Wails provides **frameless window support** with CSS-based drag regions and plat ## Quick Start ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Frameless App", Width: 800, Height: 600, @@ -52,7 +52,7 @@ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ ### Basic Frameless Window ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Width: 800, Height: 600, @@ -73,7 +73,7 @@ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ ### With Transparent Background ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, BackgroundType: application.BackgroundTypeTransparent, }) @@ -336,7 +336,7 @@ body { **Windows frameless windows:** ```go - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Windows: application.WindowsOptions{ DisableFramelessWindowDecorations: false, @@ -371,7 +371,7 @@ body { **macOS frameless windows:** ```go - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Mac: application.MacOptions{ TitleBarAppearsTransparent: true, @@ -406,7 +406,7 @@ body { **Linux frameless windows:** ```go - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, }) ``` @@ -500,7 +500,7 @@ body { ### Pattern 2: Splash Screen ```go -splash := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Loading...", Width: 400, Height: 300, @@ -530,7 +530,7 @@ body { ### Pattern 3: Rounded Window ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, BackgroundType: application.BackgroundTypeTransparent, }) @@ -560,7 +560,7 @@ body { ### Pattern 4: Overlay Window ```go -overlay := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, AlwaysOnTop: true, BackgroundType: application.BackgroundTypeTransparent, @@ -602,7 +602,7 @@ func main() { Name: "Frameless App", }) - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Frameless Application", Width: 1000, Height: 700, diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx index 3fa42abb7..41d5a18fe 100644 --- a/docs/src/content/docs/features/windows/multiple.mdx +++ b/docs/src/content/docs/features/windows/multiple.mdx @@ -32,7 +32,7 @@ func main() { }) // Create main window - app.mainWindow = app.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + app.mainWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Main Application", Width: 1200, @@ -40,7 +40,7 @@ func main() { }) // Create settings window (hidden initially) - app.settingsWindow = app.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + app.settingsWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, @@ -71,7 +71,7 @@ func (a *App) ShowSettings() { ### Get All Windows ```go -windows := app.GetAllWindows() +windows := app.Window.GetAll() fmt.Printf("Total windows: %d\n", len(windows)) for _, window := range windows { @@ -92,7 +92,7 @@ if settings != nil { window := app.GetWindowByID(123) // Current (focused) window -current := app.CurrentWindow() +current := app.Window.Current() ``` ### Window Registry Pattern @@ -132,13 +132,13 @@ Windows communicate via the event system: ```go // In main window - emit event -app.EmitEvent("settings-changed", map[string]interface{}{ +app.Event.Emit("settings-changed", map[string]interface{}{ "theme": "dark", "fontSize": 14, }) // In settings window - listen for event -app.OnEvent("settings-changed", func(event *application.WailsEvent) { +app.Event.On("settings-changed", func(event *application.WailsEvent) { data := event.Data.(map[string]interface{}) theme := data["theme"].(string) fontSize := data["fontSize"].(int) @@ -170,7 +170,7 @@ func (s *AppState) SetTheme(theme string) { s.mu.Unlock() // Notify all windows - app.EmitEvent("theme-changed", theme) + app.Event.Emit("theme-changed", theme) } func (s *AppState) GetTheme() string { @@ -204,7 +204,7 @@ var settingsWindow *application.WebviewWindow func ShowSettings(app *application.Application) { // Create if doesn't exist if settingsWindow == nil { - settingsWindow = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, @@ -245,7 +245,7 @@ func OpenDocument(app *application.Application, filePath string) { } // Create new document window - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: filepath.Base(filePath), Width: 800, Height: 600, @@ -275,7 +275,7 @@ Floating windows that stay on top: ```go func CreateToolPalette(app *application.Application) *application.WebviewWindow { - palette := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + palette := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "tools", Title: "Tools", Width: 200, @@ -294,7 +294,7 @@ Child windows that block parent: ```go func ShowModaldialog(parent *application.WebviewWindow, title string) { - dialog := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 200, @@ -332,7 +332,7 @@ func (e *EditorApp) UpdatePreview(content string) { func (e *EditorApp) TogglePreview() { if e.preview == nil { - e.preview = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + e.preview = app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "preview", Title: "Preview", Width: 600, @@ -357,7 +357,7 @@ func (e *EditorApp) TogglePreview() { ### Creating Child Windows ```go -childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Child Window", Parent: parentWindow, }) @@ -382,7 +382,7 @@ Create modal-like behaviour: ```go func ShowModal(parent *application.WebviewWindow) { - modal := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + modal := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Modal dialog", Width: 400, Height: 200, @@ -463,7 +463,7 @@ Always clean up window references: var windows = make(map[string]*application.WebviewWindow) func CreateWindow(name string) { - window := app.NewWebviewWindow() + window := app.Window.New() windows[name] = window // IMPORTANT: Clean up on destroy @@ -530,7 +530,7 @@ func (wp *WindowPool) Acquire() *application.WebviewWindow { } // Create new window - window := app.NewWebviewWindow() + window := app.Window.New() wp.inUse[window.ID()] = window return window } @@ -598,7 +598,7 @@ type WindowState struct { func SaveLayout() *WindowLayout { layout := &WindowLayout{} - for _, window := range app.GetAllWindows() { + for _, window := range app.Window.GetAll() { x, y := window.Position() width, height := window.Size() @@ -667,7 +667,7 @@ func main() { } func (mwa *MultiWindowApp) CreateMainWindow() { - window := mwa.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Main Application", Width: 1200, @@ -684,7 +684,7 @@ func (mwa *MultiWindowApp) ShowSettings() { return } - window := mwa.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, @@ -703,7 +703,7 @@ func (mwa *MultiWindowApp) OpenDocument(path string) { return } - window := mwa.app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: name, Title: path, Width: 800, diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 521d50b01..b7cea7f85 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -86,7 +86,7 @@ Name: "main-window" **Example:** ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings-window", }) @@ -161,7 +161,7 @@ Y: 100, // 100px from top edge **Example:** ```go -settings := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +settings := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "coordinate-window", InitialPosition: application.WindowXY, // use coordinate system X: 100, @@ -171,7 +171,7 @@ settings := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ **Best practice:** Use `Center()` to center the window: ```go -window := app.NewWebviewWindow() +window := app.Window.New() window.Center() ``` @@ -248,7 +248,7 @@ Hidden: true, ```go // Create hidden window -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main-window", Hidden: true, BackgroundColour: application.RGBA{R: 30, G: 30, B: 30, A: 255}, // Match your theme @@ -264,7 +264,7 @@ window.Show() **Example:** ```go -settings := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +settings := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Hidden: true, }) @@ -546,6 +546,61 @@ Assets: application.AssetOptions{ **See [Build System](/concepts/build-system) for details.** +## Input Options + +### EnableFileDrop + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +EnableFileDrop: true, +``` + +**Purpose:** Enable drag-and-drop of files from the operating system into the window. + +When enabled: +- Files dragged from file managers can be dropped into your application +- The `WindowFilesDropped` event fires with the dropped file paths +- Elements with `data-file-drop-target` attribute provide detailed drop information + +**Use cases:** +- File upload interfaces +- Document editors +- Media importers +- Any app that accepts files + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "File Uploader", + EnableFileDrop: true, +}) + +// Handle dropped files +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + for _, file := range files { + fmt.Println("Dropped:", file) + } +}) +``` + +**HTML drop zones:** + +```html + +
      + Drop files here +
      +``` + +**See [File Drop](/features/drag-and-drop/files) for complete documentation.** + ## Security Options ### ContentProtectionEnabled @@ -580,7 +635,7 @@ ContentProtectionEnabled: true, **Example:** ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Secure Window", ContentProtectionEnabled: true, }) @@ -665,39 +720,85 @@ OnDestroy: func() { ### Mac Options ```go -Mac: application.MacOptions{ - TitleBarAppearsTransparent: true, - Backdrop: application.MacBackdropTranslucent, - InvisibleTitleBarHeight: 50, - TitleBarStyle: application.MacTitleBarStyleHidden, +Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + }, + Backdrop: application.MacBackdropTranslucent, + InvisibleTitleBarHeight: 50, + WindowLevel: application.MacWindowLevelNormal, + CollectionBehavior: application.MacWindowCollectionBehaviorDefault, }, ``` -**TitleBarAppearsTransparent** (`bool`) -- Makes title bar transparent -- Content extends into title bar area +**TitleBar** (`MacTitleBar`) +- `AppearsTransparent` - Makes title bar transparent, content extends into title bar area +- `Hide` - Hides the title bar completely +- `HideTitle` - Hides only the title text +- `FullSizeContent` - Extends content to full window size **Backdrop** (`MacBackdrop`) -- `MacBackdropNormal` - Standard -- `MacBackdropTranslucent` - Blurred translucent -- `MacBackdropTransparent` - Fully transparent +- `MacBackdropNormal` - Standard opaque background +- `MacBackdropTranslucent` - Blurred translucent background +- `MacBackdropTransparent` - Fully transparent background **InvisibleTitleBarHeight** (`int`) -- Height of invisible title bar (for dragging) -- Only when `TitleBarStyle` is `MacTitleBarStyleHidden` +- Height of invisible title bar area (for dragging) +- Useful when title bar is hidden -**TitleBarStyle** (`MacTitleBarStyle`) -- `MacTitleBarStyleDefault` - Standard title bar -- `MacTitleBarStyleHidden` - Hidden title bar -- `MacTitleBarStyleHiddenInset` - Hidden with inset +**WindowLevel** (`MacWindowLevel`) +- `MacWindowLevelNormal` - Standard window level (default) +- `MacWindowLevelFloating` - Floats above normal windows +- `MacWindowLevelTornOffMenu` - Torn-off menu level +- `MacWindowLevelModalPanel` - Modal panel level +- `MacWindowLevelMainMenu` - Main menu level +- `MacWindowLevelStatus` - Status window level +- `MacWindowLevelPopUpMenu` - Pop-up menu level +- `MacWindowLevelScreenSaver` - Screen saver level -**Example:** +**CollectionBehavior** (`MacWindowCollectionBehavior`) + +Controls how the window behaves across macOS Spaces and fullscreen. These are bitmask values that can be combined using bitwise OR (`|`). + +**Space behavior:** +- `MacWindowCollectionBehaviorDefault` - Uses FullScreenPrimary (default, backwards compatible) +- `MacWindowCollectionBehaviorCanJoinAllSpaces` - Window appears on all Spaces +- `MacWindowCollectionBehaviorMoveToActiveSpace` - Moves to active Space when shown +- `MacWindowCollectionBehaviorManaged` - Default managed window behavior +- `MacWindowCollectionBehaviorTransient` - Temporary/transient window +- `MacWindowCollectionBehaviorStationary` - Stays stationary during Space switches + +**Window cycling:** +- `MacWindowCollectionBehaviorParticipatesInCycle` - Included in Cmd+` cycling +- `MacWindowCollectionBehaviorIgnoresCycle` - Excluded from Cmd+` cycling + +**Fullscreen behavior:** +- `MacWindowCollectionBehaviorFullScreenPrimary` - Can enter fullscreen mode +- `MacWindowCollectionBehaviorFullScreenAuxiliary` - Can overlay fullscreen apps +- `MacWindowCollectionBehaviorFullScreenNone` - Disables fullscreen capability +- `MacWindowCollectionBehaviorFullScreenAllowsTiling` - Allows side-by-side tiling (macOS 10.11+) +- `MacWindowCollectionBehaviorFullScreenDisallowsTiling` - Prevents tiling (macOS 10.11+) + +**Example - Spotlight-like window:** ```go -Mac: application.MacOptions{ - TitleBarAppearsTransparent: true, - Backdrop: application.MacBackdropTranslucent, - InvisibleTitleBarHeight: 50, +// Window that appears on all Spaces AND can overlay fullscreen apps +Mac: application.MacWindow{ + CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, + WindowLevel: application.MacWindowLevelFloating, +}, +``` + +**Example - Single behavior:** + +```go +// Window that can appear over fullscreen applications +Mac: application.MacWindow{ + CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenAuxiliary, }, ``` @@ -791,7 +892,7 @@ func main() { Name: "My Application", }) - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ // Identity Name: "main-window", Title: "My Application", diff --git a/docs/src/content/docs/guides/build/linux.mdx b/docs/src/content/docs/guides/build/linux.mdx index 773db73cf..18666c2aa 100644 --- a/docs/src/content/docs/guides/build/linux.mdx +++ b/docs/src/content/docs/guides/build/linux.mdx @@ -146,7 +146,7 @@ sudo pacman -S base-devel Alternatively, run `wails3 task setup:docker` and the build system will use Docker automatically. -### AppImage strip compatibility {#appimage-strip-compatibility} +### AppImage strip compatibility On modern Linux distributions (Arch Linux, Fedora 39+, Ubuntu 24.04+), system libraries are compiled with `.relr.dyn` ELF sections for more efficient relocations. The `linuxdeploy` tool used to create AppImages bundles an older `strip` binary that cannot process these modern sections. diff --git a/docs/src/content/docs/guides/custom-protocol-association.mdx b/docs/src/content/docs/guides/custom-protocol-association.mdx deleted file mode 100644 index b4f2167c2..000000000 --- a/docs/src/content/docs/guides/custom-protocol-association.mdx +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Custom Protocol Schemes (Deep Linking) -description: Guide to implementing custom URL schemes for deep linking in Wails applications across macOS, Windows, and Linux. ---- - -import { Aside } from '@astrojs/starlight/components'; - -# Custom Protocol Schemes (Deep Linking) - -Custom protocol schemes (also known as custom URL schemes or deep linking) allow your Wails application to be launched or brought to the foreground by clicking a URL with a scheme you define (e.g., `myapp://some/data`). This is useful for various purposes, such as: - -- OAuth authentication flows. -- Inter-application communication. -- Launching your app with a specific context or to perform a particular action. - -Wails provides a unified way to handle these custom URL invocations across macOS, Windows, and Linux through the `events.Common.ApplicationLaunchedWithUrl` event. - -## Defining Your Protocols - -First, you need to define the custom protocol schemes your application will use. This is done in your `wails.json` project configuration file. Wails reads this file during the build process (`wails build`) to configure the necessary platform-specific assets like `Info.plist` for macOS, NSIS installer scripts for Windows, and `.desktop` files for Linux. - -**Example: `wails.json`** - -```json title="wails.json" -{ - "name": "My App", - "description": "An amazing Wails app!", - "info": { - "companyName": "My Company", - "productName": "My Product", - // ... other info fields ... - "protocols": [ - { - "scheme": "myapp", - "description": "My Application Custom Protocol" - }, - { - "scheme": "anotherprotocol", - "description": "Another protocol for specific actions" - } - ] - } - // ... other wails.json fields ... -} -``` - -This `info.protocols` array is what Wails uses to generate the necessary entries in platform-specific files. For example, in template files, you might access this via a path like `{{.Info.Protocols}}`. - - - -## Handling the Event in Your Application - -When your application is launched or activated via a custom URL, Wails emits an `events.Common.ApplicationLaunchedWithUrl` event. You can listen for this event and retrieve the URL that triggered the launch. - -```go title="main.go" -import ( - "log" - "github.com/wailsapp/wails/v3/pkg/application" - "github.com/wailsapp/wails/v3/pkg/events" -) - -func main() { - app := application.New(application.Options{ - Name: "My App", // Ensure this matches relevant info from wails.json if needed - Description: "An amazing Wails app!", - // ... other runtime options ... - }) - - app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) { - launchedURL := e.Context().URL() // Retrieve the URL from the event context - log.Printf("Application launched with URL: %s", launchedURL) - - // TODO: Process the URL (e.g., navigate, perform action, etc.) - // Example: app.Event.Emit("frontend:ShowURL", launchedURL) - }) - - // ... rest of your main function ... - err := app.Run() - if err != nil { - log.Fatal(err) - } -} -``` - - - -## Platform-Specific Setup and Behavior - -While Wails aims for a unified event, the underlying mechanism for custom protocol registration and URL delivery varies by operating system. - -### macOS - -- **Setup:** Wails automatically configures your application's `Info.plist` file during the build process. It adds `CFBundleURLTypes` entries based on the `info.protocols` defined in your `wails.json` file. - ```xml title="Info.plist (excerpt generated by Wails)" - CFBundleURLTypes - - - CFBundleURLName - My Application Custom Protocol - CFBundleURLSchemes - - myapp - - - - - ``` -- **How it Works:** When a URL like `myapp://` is opened, macOS uses LaunchServices to find the application registered for that scheme and sends it an Apple Event (`kAEGetURL`). Wails intercepts this event and translates it into the common `events.Common.ApplicationLaunchedWithUrl` Wails event, providing the URL via `e.Context().URL()`. - -#### Universal Links - -In addition to custom protocol schemes, macOS also supports **Universal Links**, which allow your app to be launched by regular HTTPS links (e.g., `https://myawesomeapp.com/path`). Universal Links provide a seamless user experience between your web and desktop app. - - - -To enable Universal Links, follow the [Apple guide on supporting Universal Links in your app](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app). You'll need to: - -1. **Add entitlements** in your `entitlements.plist`: - ```xml title="entitlements.plist" - com.apple.developer.associated-domains - - applinks:myawesomeapp.com - - ``` - -2. **Add NSUserActivityTypes to Info.plist**: - ```xml title="Info.plist" - NSUserActivityTypes - - NSUserActivityTypeBrowsingWeb - - ``` - -3. **Configure `apple-app-site-association` on your website:** Host an `apple-app-site-association` file at `https://myawesomeapp.com/.well-known/apple-app-site-association`. - -When a Universal Link triggers your app, you'll receive the same `events.Common.ApplicationLaunchedWithUrl` event, making the handling code identical to custom protocol schemes. - -### Windows - -- **Setup:** Custom protocol schemes on Windows are registered in the Windows Registry. Wails facilitates this through its NSIS installer template. - - When you build your application with the `-nsis` flag, Wails uses the `v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl` file. - - This template contains macros like `CUSTOM_PROTOCOL_ASSOCIATE` and `wails.associateCustomProtocols` which use the `info.protocols` from your `wails.json` (passed as `{{.Info.Protocols}}` to the template) to create the necessary registry entries during installation. - ```nsis title="wails_tools.nsh.tmpl (excerpt)" - !macro wails.associateCustomProtocols - ; Create custom protocols associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - {{end}} - !macroend - ``` -- **How it Works:** The installer registers your application executable to be called with the URL as a command-line argument (`%1`). For example, `your_app.exe "myapp://some/data"`. - - The Wails runtime for Windows (`v3/pkg/application/application_windows.go`) has been updated to check `os.Args` upon startup. If it detects an argument that looks like a URL (e.g., `os.Args[1]` contains `"://"`), it now emits the `events.Common.ApplicationLaunchedWithUrl` event with this URL. - - - -#### Universal Links (Web-to-App Linking) - -Windows supports **Web-to-App linking**, which works similarly to Universal Links on macOS. When deploying your application as an MSIX package, you can enable HTTPS links to launch your app directly. - -To enable Web-to-App linking, follow the [Microsoft guide on web-to-app linking](https://learn.microsoft.com/en-us/windows/apps/develop/launch/web-to-app-linking). You'll need to: - -1. **Add App URI Handler in your MSIX manifest**: - ```xml title="AppPackage.appxmanifest (excerpt)" - - - - - - ``` - -2. **Configure `windows-app-web-link` on your website:** Host a `windows-app-web-link` file at `https://my.app.org/.well-known/windows-app-web-link`. This file should contain your app's package information and the paths it handles. - -When a Web-to-App link launches your application, you'll receive the same `events.Common.ApplicationLaunchedWithUrl` event as with custom protocol schemes. - -### Linux - -- **Setup:** On Linux, custom protocol handling is typically managed via `.desktop` files and the MIME type system. - - Wails uses a `.desktop` file template (e.g., `v3/internal/commands/updatable_build_assets/linux/desktop.tmpl`) which is populated during the build using information from `wails.json`. - ```desktop title="desktop.tmpl (excerpt)" - [Desktop Entry] - Name={{.ProductName}} - Exec=/usr/local/bin/{{.BinaryName}} %u - MimeType={{range $index, $protocol := .Info.Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}} - ``` - The `Exec` line uses `%u` which gets replaced by the URL. The `MimeType` line registers your application as a handler for `x-scheme-handler/your-scheme` for each protocol defined in `wails.json` (via `{{.Info.Protocols}}`). - - When packaging for Linux (e.g., using `nfpm`), this `.desktop` file is installed to `/usr/share/applications/`. - - A `postinstall.sh` script (e.g., `v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh`) is used to update the system's application and MIME databases: - ```sh title="postinstall.sh (excerpt)" - #!/bin/sh - update-desktop-database -q /usr/share/applications - update-mime-database -n /usr/share/mime - ``` -- **How it Works:** When a URL like `myapp://` is opened, the desktop environment uses the MIME database to find the associated `.desktop` file and executes the command specified in its `Exec` line, substituting `%u` with the URL. Your application receives this URL as a command-line argument. - - The Wails runtime for Linux (`v3/pkg/application/application_linux.go`) checks `os.Args` on startup. If it detects an argument that looks like a URL, it emits the `events.Common.ApplicationLaunchedWithUrl` event. - -## Testing Your Custom Protocols - -- **macOS:** Open Terminal and type `open "your-scheme://your/data"`. -- **Linux:** Open a terminal and type `xdg-open "your-scheme://your/data"` (requires `xdg-utils` to be installed and the app to be properly packaged and registered). -- **Windows:** After installation via NSIS: - - You can try running `start your-scheme://your/data` from Command Prompt or PowerShell. - - Create a simple HTML file with a link `Test Link` and open it in a browser. - - - -By following this guide, you can effectively use custom protocol schemes to enhance your Wails application's interactivity and integration with other applications or web services. diff --git a/docs/src/content/docs/guides/distribution/custom-protocols.mdx b/docs/src/content/docs/guides/distribution/custom-protocols.mdx index 97c4b1372..39a9b157c 100644 --- a/docs/src/content/docs/guides/distribution/custom-protocols.mdx +++ b/docs/src/content/docs/guides/distribution/custom-protocols.mdx @@ -5,7 +5,7 @@ sidebar: order: 3 --- -import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; Custom URL protocols (also called URL schemes) allow your application to be launched when users click links with your custom protocol, such as `myapp://action` or `myapp://open/document`. @@ -42,7 +42,7 @@ func main() { }) // Register handler for protocol events - app.OnEvent(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { + app.Event.On(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { url := event.Context().ClickedURL() handleCustomURL(url) }) @@ -62,7 +62,7 @@ func handleCustomURL(url string) { Listen for protocol events to handle incoming URLs: ```go -app.OnEvent(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { +app.Event.On(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { url := event.Context().ClickedURL() // Parse the URL @@ -159,6 +159,47 @@ Start-Process "myapp://test/action" start myapp://test/action ``` +### Windows MSIX Package + +Custom protocols are also automatically registered when using MSIX packaging. + +#### Automatic Registration + +When you build your application with MSIX, the manifest automatically includes protocol registrations from your `build/config.yml` protocols configuration. + +The generated manifest includes: + +```xml + + + My Application Protocol + + +``` + +#### Universal Links (Web-to-App Linking) + +Windows supports **Web-to-App linking**, which works similarly to Universal Links on macOS. When deploying your application as an MSIX package, you can enable HTTPS links to launch your app directly. + + + +To enable Web-to-App linking, follow the [Microsoft guide on web-to-app linking](https://learn.microsoft.com/en-us/windows/apps/develop/launch/web-to-app-linking). You'll need to: + +1. **Manually add App URI Handler to your MSIX manifest** (`build/windows/msix/app_manifest.xml`): + ```xml + + + + + + ``` + +2. **Configure `windows-app-web-link` on your website:** Host a `windows-app-web-link` file at `https://myawesomeapp.com/.well-known/windows-app-web-link`. This file should contain your app's package information and the paths it handles. + +When a Web-to-App link launches your application, you'll receive the same `ApplicationOpenedWithURL` event as with custom protocol schemes. +
      @@ -199,6 +240,36 @@ open "myapp://test/action" /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep myapp ``` +#### Universal Links + +In addition to custom protocol schemes, macOS also supports **Universal Links**, which allow your app to be launched by regular HTTPS links (e.g., `https://myawesomeapp.com/path`). Universal Links provide a seamless user experience between your web and desktop app. + + + +To enable Universal Links, follow the [Apple guide on supporting Universal Links in your app](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app). You'll need to: + +1. **Add entitlements** in your `entitlements.plist`: + ```xml + com.apple.developer.associated-domains + + applinks:myawesomeapp.com + + ``` + +2. **Add NSUserActivityTypes to Info.plist**: + ```xml + NSUserActivityTypes + + NSUserActivityTypeBrowsingWeb + + ``` + +3. **Configure `apple-app-site-association` on your website:** Host an `apple-app-site-association` file at `https://myawesomeapp.com/.well-known/apple-app-site-association`. + +When a Universal Link triggers your app, you'll receive the same `ApplicationOpenedWithURL` event, making the handling code identical to custom protocol schemes. + @@ -291,7 +362,7 @@ func main() { func (a *App) setup() { // Create window - a.window = a.app.NewWebviewWindow(application.WebviewWindowOptions{ + a.window = a.app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "DeepLink Demo", Width: 800, Height: 600, @@ -299,7 +370,7 @@ func (a *App) setup() { }) // Handle custom protocol URLs - a.app.OnEvent(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { + a.app.Event.On(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { customURL := event.Context().ClickedURL() a.handleDeepLink(customURL) }) diff --git a/docs/src/content/docs/guides/performance.mdx b/docs/src/content/docs/guides/performance.mdx index 9b4f28064..1d38a1ef4 100644 --- a/docs/src/content/docs/guides/performance.mdx +++ b/docs/src/content/docs/guides/performance.mdx @@ -116,10 +116,10 @@ func (s *Service) ProcessLargeFile(path string) error { go func() { result, err := s.process(path) if err != nil { - s.app.EmitEvent("process-error", err.Error()) + s.app.Event.Emit("process-error", err.Error()) return } - s.app.EmitEvent("process-complete", result) + s.app.Event.Emit("process-complete", result) }() return nil diff --git a/docs/src/content/docs/guides/raw-messages.mdx b/docs/src/content/docs/guides/raw-messages.mdx index 72b067102..5f2a7a653 100644 --- a/docs/src/content/docs/guides/raw-messages.mdx +++ b/docs/src/content/docs/guides/raw-messages.mdx @@ -47,7 +47,7 @@ func main() { }, }) - app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My App", Name: "main", }) @@ -291,7 +291,7 @@ func main() { }, }) - app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Raw Message Demo", Name: "main", Width: 400, @@ -382,7 +382,7 @@ RawMessageHandler: func(window application.Window, message string, originInfo *a window.EmitEvent("response", result) // Or broadcast to all windows - app.EmitEvent("broadcast", result) + app.Event.Emit("broadcast", result) } ``` diff --git a/docs/src/content/docs/migration/v2-to-v3.mdx b/docs/src/content/docs/migration/v2-to-v3.mdx index 6fea62912..5eb94ca67 100644 --- a/docs/src/content/docs/migration/v2-to-v3.mdx +++ b/docs/src/content/docs/migration/v2-to-v3.mdx @@ -49,7 +49,7 @@ app := application.New(application.Options{ }, }) -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My App", Width: 1024, Height: 768, @@ -137,12 +137,12 @@ type MyService struct { } func (s *MyService) UpdateTitle() { - window := s.app.CurrentWindow() + window := s.app.Window.Current() window.SetTitle("New Title") } func (s *MyService) EmitEvent() { - s.app.EmitEvent("event-name", data) + s.app.Event.Emit("event-name", data) } ``` @@ -203,19 +203,19 @@ runtime.EventsEmit(ctx, "event-name", data) **v3:** ```go -app.OnEvent("event-name", func(e *application.CustomEvent) { +app.Event.On("event-name", func(e *application.CustomEvent) { data := e.Data // Handle event }) -app.EmitEvent("event-name", data) +app.Event.Emit("event-name", data) ``` **Why this is better:** - **Type safety**: Events use proper event objects instead of `...interface{}` - **Better debugging**: Event objects contain metadata like event name, making debugging easier -- **Clearer API**: `app.OnEvent()` and `app.EmitEvent()` are more intuitive than runtime functions +- **Clearer API**: `app.Event.On()` and `app.Event.Emit()` are more intuitive than runtime functions - **No context needed**: Events work directly on the app object without threading context - **Simpler handlers**: Event handlers have a clear signature instead of variadic parameters @@ -236,10 +236,10 @@ runtime.WindowSetSize(ctx, 800, 600) ```go // Multiple windows supported -window1 := app.NewWebviewWindow() +window1 := app.Window.New() window1.SetSize(800, 600) -window2 := app.NewWebviewWindow() +window2 := app.Window.New() window2.SetSize(1024, 768) ``` @@ -339,7 +339,7 @@ func main() { }, }) - app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My App", Width: 1024, Height: 768, @@ -416,10 +416,10 @@ func (a *App) DoSomething() { ```go func (s *MyService) DoSomething() { - window := s.app.CurrentWindow() + window := s.app.Window.Current() window.SetTitle("New Title") - s.app.EmitEvent("update", data) + s.app.Event.Emit("update", data) s.app.Logger.Info("Message") } @@ -510,7 +510,7 @@ selection, err := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{ **v3:** ```go -selection, err := app.OpenFileDialog(application.OpenFileDialogOptions{ +selection, err := app.Dialog.OpenFile(application.OpenFileDialogOptions{ Title: "Select File", }) ``` @@ -549,7 +549,7 @@ fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { **v3:** ```go -systray := app.NewSystemTray() +systray := app.SystemTray.New() systray.SetIcon(iconBytes) systray.SetLabel("My App") @@ -602,7 +602,7 @@ func NewMyService(app *application.Application) *MyService { Use window methods directly: ```go -window := s.app.CurrentWindow() +window := s.app.Window.Current() window.SetTitle("New Title") ``` @@ -616,7 +616,7 @@ Check event names match exactly: ```go // Go -app.EmitEvent("my-event", data) +app.Event.Emit("my-event", data) // JavaScript OnEvent("my-event", handler) // Must match exactly diff --git a/docs/src/content/docs/quick-start/next-steps.mdx b/docs/src/content/docs/quick-start/next-steps.mdx index fc17185d4..394d8e69a 100644 --- a/docs/src/content/docs/quick-start/next-steps.mdx +++ b/docs/src/content/docs/quick-start/next-steps.mdx @@ -24,7 +24,7 @@ Wails provides native desktop capabilities: - [Events](/features/events/system) - Communication between components - [Bindings](/features/bindings/methods) - Type-safe Go ↔ JavaScript calls - [Clipboard](/features/clipboard) - Copy/paste operations -- [Drag & Drop](/features/drag-drop) - File drag and drop +- [Drag & Drop](/features/drag-and-drop/files) - File drag and drop - [Keyboard](/features/keyboard) - Global shortcuts ## Get Help diff --git a/docs/src/content/docs/reference/application.mdx b/docs/src/content/docs/reference/application.mdx index 050578a62..0fbc63b02 100644 --- a/docs/src/content/docs/reference/application.mdx +++ b/docs/src/content/docs/reference/application.mdx @@ -80,33 +80,33 @@ fmt.Println("App name:", config.Name) ## Window Management -### NewWebviewWindow() +### app.Window.New() Creates a new webview window with default options. ```go -func (a *App) NewWebviewWindow() *WebviewWindow +func (wm *WindowManager) New() *WebviewWindow ``` **Example:** ```go -window := app.NewWebviewWindow() +window := app.Window.New() window.Show() ``` -### NewWebviewWindowWithOptions() +### app.Window.NewWithOptions() Creates a new webview window with custom options. ```go -func (a *App) NewWebviewWindowWithOptions(options WebviewWindowOptions) *WebviewWindow +func (wm *WindowManager) NewWithOptions(options WebviewWindowOptions) *WebviewWindow ``` **Example:** ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My Window", Width: 800, Height: 600, @@ -114,35 +114,35 @@ window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ }) ``` -### GetWindowByName() +### app.Window.GetByName() Gets a window by its name. ```go -func (a *App) GetWindowByName(name string) Window +func (wm *WindowManager) GetByName(name string) Window ``` **Example:** ```go -window := app.GetWindowByName("main") +window := app.Window.GetByName("main") if window != nil { window.Show() } ``` -### GetWindows() +### app.Window.GetAll() Returns all application windows. ```go -func (a *App) GetWindows() []Window +func (wm *WindowManager) GetAll() []Window ``` **Example:** ```go -windows := app.GetWindows() +windows := app.Window.GetAll() for _, window := range windows { fmt.Println("Window:", window.Name()) } @@ -168,7 +168,7 @@ app.Env // Environment variables ```go // Create window -window := app.Window.NewWebviewWindow() +window := app.Window.New() // Show dialog app.Dialog.Info().SetMessage("Hello!").Show() @@ -207,55 +207,55 @@ app.RegisterService(application.NewService(NewMyService(app))) ## Event Management -### EmitEvent() +### app.Event.Emit() Emits a custom event. ```go -func (a *App) EmitEvent(name string, data ...interface{}) +func (em *EventManager) Emit(name string, data ...interface{}) ``` **Example:** ```go // Emit event with data -app.EmitEvent("user-logged-in", map[string]interface{}{ +app.Event.Emit("user-logged-in", map[string]interface{}{ "username": "john", "timestamp": time.Now(), }) ``` -### OnEvent() +### app.Event.On() Listens for custom events. ```go -func (a *App) OnEvent(name string, callback func(*CustomEvent)) +func (em *EventManager) On(name string, callback func(*CustomEvent)) ``` **Example:** ```go -app.OnEvent("user-logged-in", func(e *application.CustomEvent) { +app.Event.On("user-logged-in", func(e *application.CustomEvent) { data := e.Data.(map[string]interface{}) username := data["username"].(string) fmt.Println("User logged in:", username) }) ``` -### OnApplicationEvent() +### app.Event.OnApplicationEvent() Listens for application lifecycle events. ```go -func (a *App) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func() +func (em *EventManager) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func() ``` **Example:** ```go // Listen for shutdown -app.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { fmt.Println("Application shutting down") // Cleanup }) @@ -415,7 +415,7 @@ func main() { }) // Create main window - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My App", Width: 1024, Height: 768, diff --git a/docs/src/content/docs/reference/events.mdx b/docs/src/content/docs/reference/events.mdx index a75ea62d5..d6286018e 100644 --- a/docs/src/content/docs/reference/events.mdx +++ b/docs/src/content/docs/reference/events.mdx @@ -24,12 +24,12 @@ The Events API provides methods to emit and listen to events, enabling communica ## Event Methods (Go) -### EmitEvent() +### app.Event.Emit() Emits a custom event to all windows. ```go -func (a *App) EmitEvent(name string, data ...interface{}) +func (em *EventManager) Emit(name string, data ...interface{}) ``` **Parameters:** @@ -39,24 +39,24 @@ func (a *App) EmitEvent(name string, data ...interface{}) **Example:** ```go // Emit simple event -app.EmitEvent("user-logged-in") +app.Event.Emit("user-logged-in") // Emit with data -app.EmitEvent("data-updated", map[string]interface{}{ +app.Event.Emit("data-updated", map[string]interface{}{ "count": 42, "status": "success", }) // Emit multiple values -app.EmitEvent("progress", 75, "Processing files...") +app.Event.Emit("progress", 75, "Processing files...") ``` -### OnEvent() +### app.Event.On() Listens for custom events in Go. ```go -func (a *App) OnEvent(name string, callback func(*CustomEvent)) func() +func (em *EventManager) On(name string, callback func(*CustomEvent)) func() ``` **Parameters:** @@ -68,7 +68,7 @@ func (a *App) OnEvent(name string, callback func(*CustomEvent)) func() **Example:** ```go // Listen for events -cleanup := app.OnEvent("user-action", func(e *application.CustomEvent) { +cleanup := app.Event.On("user-action", func(e *application.CustomEvent) { data := e.Data.(map[string]interface{}) action := data["action"].(string) app.Logger.Info("User action", "action", action) @@ -87,7 +87,7 @@ Emit events to a specific window: window.EmitEvent("notification", "Hello from Go!") // Emit to all windows -app.EmitEvent("global-update", data) +app.Event.Emit("global-update", data) ``` ## Event Methods (Frontend) @@ -187,12 +187,12 @@ OffAll('data-updated') ## Application Events -### OnApplicationEvent() +### app.Event.OnApplicationEvent() Listens for application lifecycle events. ```go -func (a *App) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func() +func (em *EventManager) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func() ``` **Event Types:** @@ -203,13 +203,13 @@ func (a *App) OnApplicationEvent(eventType ApplicationEventType, callback func(* **Example:** ```go // Handle application startup -app.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) { app.Logger.Info("Application started") // Initialize resources }) // Handle application shutdown -app.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { app.Logger.Info("Application shutting down") // Cleanup resources, save state database.Close() @@ -268,7 +268,7 @@ func (s *DataService) FetchData(query string) ([]Item, error) { items := fetchFromDatabase(query) // Emit event when done - s.app.EmitEvent("data-fetched", map[string]interface{}{ + s.app.Event.Emit("data-fetched", map[string]interface{}{ "query": query, "count": len(items), }) @@ -309,7 +309,7 @@ func (s *Service) ProcessFiles(files []string) error { processFile(file) // Emit progress event - s.app.EmitEvent("progress", map[string]interface{}{ + s.app.Event.Emit("progress", map[string]interface{}{ "current": i + 1, "total": total, "percent": float64(i+1) / float64(total) * 100, @@ -317,7 +317,7 @@ func (s *Service) ProcessFiles(files []string) error { }) } - s.app.EmitEvent("processing-complete") + s.app.Event.Emit("processing-complete") return nil } ``` @@ -349,7 +349,7 @@ Perfect for applications with multiple windows like settings panels, dashboards, ```go // Broadcast to all windows -app.EmitEvent("theme-changed", "dark") +app.Event.Emit("theme-changed", "dark") // Send to specific window preferencesWindow.EmitEvent("settings-updated", settings) @@ -391,7 +391,7 @@ func (s *StateService) UpdateState(key string, value interface{}) { s.mu.Unlock() // Notify all windows - s.app.EmitEvent("state-updated", map[string]interface{}{ + s.app.Event.Emit("state-updated", map[string]interface{}{ "key": key, "value": value, }) @@ -435,21 +435,21 @@ type NotificationService struct { } func (s *NotificationService) Success(message string) { - s.app.EmitEvent("notification", map[string]interface{}{ + s.app.Event.Emit("notification", map[string]interface{}{ "type": "success", "message": message, }) } func (s *NotificationService) Error(message string) { - s.app.EmitEvent("notification", map[string]interface{}{ + s.app.Event.Emit("notification", map[string]interface{}{ "type": "error", "message": message, }) } func (s *NotificationService) Info(message string) { - s.app.EmitEvent("notification", map[string]interface{}{ + s.app.Event.Emit("notification", map[string]interface{}{ "type": "info", "message": message, }) @@ -498,7 +498,7 @@ func NewEventDemoService(app *application.Application) *EventDemoService { service := &EventDemoService{app: app} // Listen for custom events - app.OnEvent("user-action", func(e *application.CustomEvent) { + app.Event.On("user-action", func(e *application.CustomEvent) { data := e.Data.(map[string]interface{}) app.Logger.Info("User action received", "data", data) }) @@ -508,26 +508,26 @@ func NewEventDemoService(app *application.Application) *EventDemoService { func (s *EventDemoService) StartLongTask() { go func() { - s.app.EmitEvent("task-started") + s.app.Event.Emit("task-started") for i := 1; i <= 10; i++ { time.Sleep(500 * time.Millisecond) - s.app.EmitEvent("task-progress", map[string]interface{}{ + s.app.Event.Emit("task-progress", map[string]interface{}{ "step": i, "total": 10, "percent": i * 10, }) } - s.app.EmitEvent("task-completed", map[string]interface{}{ + s.app.Event.Emit("task-completed", map[string]interface{}{ "message": "Task finished successfully!", }) }() } func (s *EventDemoService) BroadcastMessage(message string) { - s.app.EmitEvent("broadcast", message) + s.app.Event.Emit("broadcast", message) } func main() { @@ -536,11 +536,11 @@ func main() { }) // Handle application lifecycle - app.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) { app.Logger.Info("Application started!") }) - app.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { + app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { app.Logger.Info("Application shutting down...") }) @@ -549,7 +549,7 @@ func main() { app.RegisterService(application.NewService(service)) // Create window - window := app.NewWebviewWindow() + window := app.Window.New() // Handle window events window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { @@ -673,11 +673,11 @@ This mapping happens automatically in the background, so when you listen for `ev ```go import "github.com/wailsapp/wails/v3/pkg/events" -app.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { app.Logger.Info("Application ready!") }) -app.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { // Update app theme }) ``` @@ -720,7 +720,7 @@ window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) // Cancel window close window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { - result, _ := app.QuestionDialog(). + result, _ := app.Dialog.Question(). SetMessage("Close window?"). SetButtons("Yes", "No"). Show() @@ -749,14 +749,14 @@ window.OnWindowEvent(events.Common.WindowRuntimeReady, func(e *application.Windo ```go // Good - descriptive and specific -app.EmitEvent("user:logged-in", user) -app.EmitEvent("data:fetch:complete", results) -app.EmitEvent("ui:theme:changed", theme) +app.Event.Emit("user:logged-in", user) +app.Event.Emit("data:fetch:complete", results) +app.Event.Emit("ui:theme:changed", theme) // Bad - vague and unclear -app.EmitEvent("event1", data) -app.EmitEvent("update", stuff) -app.EmitEvent("e", value) +app.Event.Emit("event1", data) +app.Event.Emit("update", stuff) +app.Event.Emit("e", value) ``` ## Performance Considerations @@ -776,7 +776,7 @@ func (s *Service) EmitWithDebounce(event string, data interface{}) { return // Skip this emission } - s.app.EmitEvent(event, data) + s.app.Event.Emit(event, data) s.lastEmit = now } ``` diff --git a/docs/src/content/docs/reference/menu.mdx b/docs/src/content/docs/reference/menu.mdx index 485357073..f0b9f0640 100644 --- a/docs/src/content/docs/reference/menu.mdx +++ b/docs/src/content/docs/reference/menu.mdx @@ -316,12 +316,12 @@ menu.Update() ## Application Menu -### SetApplicationMenu() +### app.Menu.Set() Sets the application's main menu bar. ```go -func (a *App) SetApplicationMenu(menu *Menu) +func (mm *MenuManager) Set(menu *Menu) ``` **Example:** @@ -347,7 +347,7 @@ editMenu.Add("Cut").SetAccelerator("Ctrl+X").OnClick(cut) editMenu.Add("Copy").SetAccelerator("Ctrl+C").OnClick(copy) editMenu.Add("Paste").SetAccelerator("Ctrl+V").OnClick(paste) -app.SetApplicationMenu(menu) +app.Menu.Set(menu) ``` **Platform notes:** @@ -357,12 +357,12 @@ app.SetApplicationMenu(menu) ## Context Menus -### RegisterContextMenu() +### app.ContextMenu.Add() Registers a context menu (right-click menu) with a specific name. ```go -func (a *App) RegisterContextMenu(name string, menu *Menu) +func (cm *ContextMenuManager) Add(name string, menu *Menu) ``` **Parameters:** @@ -412,17 +412,17 @@ func updateContextMenu() { ## System Tray Menu -### NewSystemTray() +### app.SystemTray.New() Creates a new system tray icon. ```go -func (a *App) NewSystemTray() *SystemTray +func (sm *SystemTrayManager) New() *SystemTray ``` **Example:** ```go -tray := app.NewSystemTray() +tray := app.SystemTray.New() ``` ### SetIcon() @@ -572,9 +572,9 @@ func main() { }) menu := createMenu(app) - app.SetApplicationMenu(menu) + app.Menu.Set(menu) - window := app.NewWebviewWindow() + window := app.Window.New() window.Show() app.Run() @@ -586,7 +586,7 @@ func main() { ```go func setupSystemTray(app *application.Application, window *application.Window) { // Create system tray - tray := app.NewSystemTray() + tray := app.SystemTray.New() // Set icon iconData, _ := os.ReadFile("icon.png") @@ -667,7 +667,7 @@ func (e *Editor) createMenu() { }) e.updateMenuState() - e.app.SetApplicationMenu(e.menu) + e.app.Menu.Set(e.menu) } func (e *Editor) updateMenuState() { diff --git a/docs/src/content/docs/reference/overview.mdx b/docs/src/content/docs/reference/overview.mdx index 44b2a819c..8a3b77bc3 100644 --- a/docs/src/content/docs/reference/overview.mdx +++ b/docs/src/content/docs/reference/overview.mdx @@ -37,7 +37,7 @@ This is the complete API reference for Wails v3. It documents every public type, Most methods that can fail return `error` as the last return value: ```go -window, err := app.NewWebviewWindow() +window, err := app.Window.New() if err != nil { log.Fatal(err) } diff --git a/docs/src/content/docs/reference/window.mdx b/docs/src/content/docs/reference/window.mdx index 33d0a8b55..c376ee426 100644 --- a/docs/src/content/docs/reference/window.mdx +++ b/docs/src/content/docs/reference/window.mdx @@ -30,7 +30,7 @@ func (w *Window) Show() **Example:** ```go -window := app.NewWebviewWindow() +window := app.Window.New() window.Show() ``` @@ -202,7 +202,7 @@ func (w *Window) Centre() **Example:** ```go -window := app.NewWebviewWindow() +window := app.Window.New() window.Centre() window.Show() ``` @@ -441,7 +441,7 @@ func (w *Window) RegisterHook( import "github.com/wailsapp/wails/v3/pkg/events" window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { - result, _ := app.QuestionDialog(). + result, _ := app.Dialog.Question(). SetTitle("Confirm Close"). SetMessage("Are you sure you want to close this window?"). SetButtons("Yes", "No"). @@ -457,7 +457,7 @@ window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent ```go window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { if hasUnsavedChanges { - result, _ := app.QuestionDialog(). + result, _ := app.Dialog.Question(). SetMessage("Save changes before closing?"). SetButtons("Save", "Don't Save", "Cancel"). Show() @@ -620,7 +620,7 @@ func main() { }) // Create window with options - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My Application", Width: 1024, Height: 768, @@ -638,7 +638,7 @@ func main() { // Set up event hooks window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { // Confirm before closing - result, _ := app.QuestionDialog(). + result, _ := app.Dialog.Question(). SetTitle("Confirm Close"). SetMessage("Are you sure you want to close this window?"). SetButtons("Yes", "No"). diff --git a/v3/Taskfile.yaml b/v3/Taskfile.yaml index 04afd60f4..396b7aee5 100644 --- a/v3/Taskfile.yaml +++ b/v3/Taskfile.yaml @@ -181,7 +181,6 @@ tasks: gin-routing gin-service hide-window - html-dnd-api ignore-mouse keybindings liquid-glass @@ -306,7 +305,6 @@ tasks: gin-routing gin-service hide-window - html-dnd-api ignore-mouse keybindings liquid-glass diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index e91584c78..2f378b9ba 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased Changes - - Fix macOS system tray menu real-time updates using NSMenuDelegate (#4630) - Implement macOS system tray onMenuOpen/onMenuClose callbacks for parity with Windows and Linux +- Fix `Position()` and `SetPosition()` using inconsistent coordinate systems on macOS, causing window position drift when saving/restoring state (#4816) by @leaanthony ## Deprecated diff --git a/v3/examples/drag-n-drop/README.md b/v3/examples/drag-n-drop/README.md index cb3c56fd0..a2cde8844 100644 --- a/v3/examples/drag-n-drop/README.md +++ b/v3/examples/drag-n-drop/README.md @@ -1,27 +1,75 @@ -# Drag-n-drop Example +# File Drop Example -This example demonstrates how to handle files being dragged into the application. +This example demonstrates how to handle files being dragged from the operating system (Finder, Explorer, file managers) into a Wails application. + +Dropped files are automatically categorised by type and displayed in separate buckets: documents, images, or other files. + +## How it works + +1. Enable file drops in window options: + ```go + EnableFileDrop: true + ``` + +2. Mark elements as drop targets in HTML: + ```html +
      Drop files here
      + ``` + +3. Listen for the `WindowFilesDropped` event: + ```go + win.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + // Handle the dropped files + }) + ``` + +4. Optionally forward to frontend: + ```go + application.Get().Event.Emit("files-dropped", map[string]any{ + "files": files, + "details": details, + }) + ``` + +## Drop Target Details + +When files are dropped, you can get information about the drop location: + +- `ElementID` - The ID of the element that received the drop +- `ClassList` - CSS classes on the drop target +- `X`, `Y` - Coordinates of the drop within the element + +## Styling + +When files are dragged over a valid drop target, Wails adds the `file-drop-target-active` class: + +```css +.file-drop-target-active { + border-color: #4a9eff; + background: rgba(74, 158, 255, 0.1); +} +``` ## Running the example -To run the example, simply run the following command: - ```bash go run main.go ``` -## Building the example +Then drag files from your desktop or file manager into the drop zone. -To build the example in debug mode, simply run the following command: +## HTML5 Drag and Drop API -```bash -wails3 task build -``` +This example also includes a demonstration for dragging elements *within* your application via the HTML5 Drag and Drop API. -# Status +Scroll down to the `Internal Drag and Drop` section within the launched application to interact with the demo. -| Platform | Status | -|----------|-------------| -| Mac | Working | -| Windows | Not Working | -| Linux | | +## Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | Working | diff --git a/v3/examples/drag-n-drop/assets/index.html b/v3/examples/drag-n-drop/assets/index.html index 3d89c9bcb..53af94212 100644 --- a/v3/examples/drag-n-drop/assets/index.html +++ b/v3/examples/drag-n-drop/assets/index.html @@ -3,422 +3,433 @@ - File Tree Drag-and-Drop Example - - - - - - + Drag and Drop Demo -
      -

      File Tree Drag & Drop Example

      -

      Drag files onto folders to upload them to that location

      +

      Drag and Drop Demo

      + + +

      External File Drop

      +
      +

      + Drop files from your operating system (Finder, Explorer, file managers). + Uses EnableFileDrop: true and data-file-drop-target +

      -
      -
      -
      /home/user
      - - -
      - folder - Home +
      +
      +

      Drop files from your desktop or file manager here

      +
      + +
      +
      +

      Documents

      +
        +
      • No documents yet
      • +
      -
      - -
      - folder - Documents +
      +

      Images

      +
        +
      • No images yet
      • +
      +
      + +
      +

      Other Files

      +
        +
      • No other files yet
      • +
      +
      +
      +
      + + +

      Internal Drag and Drop

      +
      +

      + Drag items between zones using the HTML5 Drag and Drop API. + Uses draggable="true" and standard DOM events. +

      +
      + +
      +
      +
      +

      Tasks

      +
      Fix login bug
      +
      Update documentation
      +
      Add dark mode
      +
      Refactor API calls
      +
      Write unit tests
      +
      + +
      +
      +

      High Priority

      +
        -
        -
        - description - report.pdf -
        -
        - description - notes.txt -
        +
        +

        Medium Priority

        +
          - -
          - folder - Pictures -
          - -
          -
          - image - vacation.jpg -
          -
          - image - profile.png -
          -
          - - -
          - folder - Downloads -
          - -
          -
          - archive - app.dmg -
          -
          - archive - data.zip -
          -
          - - -
          - folder - Projects -
          - -
          - -
          - folder - Wails -
          - -
          -
          - code - main.go -
          -
          - code - go.mod -
          -
          +
          +

          Low Priority

          +
            - -
            -

            Drop Information

            -
            Drag and drop files onto any folder in the file tree... +
            + +
            + Last action: No actions yet +
            -The folder elements have the following attributes: -- data-wails-dropzone: Marks the element as a dropzone -- data-path: Contains the full path (used for destination) -- data-folder-id: A unique ID for the folder -- data-folder-name: The display name of the folder
            -
            -
            - - \ No newline at end of file + diff --git a/v3/examples/drag-n-drop/main.go b/v3/examples/drag-n-drop/main.go index 3746a406c..5caafef1e 100644 --- a/v3/examples/drag-n-drop/main.go +++ b/v3/examples/drag-n-drop/main.go @@ -1,10 +1,7 @@ package main import ( - "context" "embed" - _ "embed" - "fmt" "log" "github.com/wailsapp/wails/v3/pkg/application" @@ -14,153 +11,50 @@ import ( //go:embed assets var assets embed.FS -// App struct -type App struct { - ctx context.Context - app *application.App -} - -// NewApp creates a new App application struct -func NewApp() *App { - return &App{} -} - -// Startup is called when the app starts. The context is saved -// so we can call the runtime methods -func (a *App) Startup(ctx context.Context) { - a.ctx = ctx - a.app = application.Get() -} - -// FileDropInfo defines the payload for the file drop event sent to the frontend. -type FileDropInfo struct { - Files []string `json:"files"` - TargetID string `json:"targetID"` - TargetClasses []string `json:"targetClasses"` - DropX float64 `json:"dropX"` - DropY float64 `json:"dropY"` - Attributes map[string]string `json:"attributes,omitempty"` -} - -// FilesDroppedOnTarget is called when files are dropped onto a registered drop target -// or the window if no specific target is hit. -func FilesDroppedOnTarget( - files []string, - targetID string, - targetClasses []string, - dropX float64, - dropY float64, - isTargetDropzone bool, // This parameter is kept for logging but not sent to frontend in this event - attributes map[string]string, -) { - log.Println("=============== Go: FilesDroppedOnTarget Debug Info ===============") - log.Println(fmt.Sprintf(" Files: %v", files)) - log.Println(fmt.Sprintf(" Target ID: '%s'", targetID)) - log.Println(fmt.Sprintf(" Target Classes: %v", targetClasses)) - log.Println(fmt.Sprintf(" Drop X: %f, Drop Y: %f", dropX, dropY)) - log.Println( - fmt.Sprintf( - " Drop occurred on a designated dropzone (runtime validated before this Go event): %t", - isTargetDropzone, - ), - ) - log.Println(fmt.Sprintf(" Element Attributes: %v", attributes)) - log.Println("================================================================") - - payload := FileDropInfo{ - Files: files, - TargetID: targetID, - TargetClasses: targetClasses, - DropX: dropX, - DropY: dropY, - Attributes: attributes, - } - - log.Println("Go: Emitted 'frontend:FileDropInfo' event with payload:", payload) -} - func main() { - appInstance := NewApp() - app := application.New(application.Options{ - Name: "Drag-n-drop Demo", - Description: "A demo of the Drag-n-drop API", + Name: "File Drop Demo", + Description: "A demo of file drag and drop", Assets: application.AssetOptions{ Handler: application.BundledAssetFileServer(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, }, - Services: []application.Service{ - application.NewService(appInstance), - }, }) win := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Drag-n-drop Demo", + Title: "File Drop Demo", + Width: 800, + Height: 600, + EnableFileDrop: true, Mac: application.MacWindow{ Backdrop: application.MacBackdropTranslucent, TitleBar: application.MacTitleBarHiddenInsetUnified, InvisibleTitleBarHeight: 50, }, - EnableDragAndDrop: true, }) - log.Println("Setting up event listener for 'WindowDropZoneFilesDropped'...") - win.OnWindowEvent( - events.Common.WindowDropZoneFilesDropped, - func(event *application.WindowEvent) { + // Listen for file drop events + win.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() - droppedFiles := event.Context().DroppedFiles() - details := event.Context().DropZoneDetails() + log.Printf("Files dropped: %v", files) + if details != nil { + log.Printf("Drop target: id=%s, classes=%v, x=%d, y=%d", + details.ElementID, details.ClassList, details.X, details.Y) + } - log.Printf("Dropped files count: %d", len(droppedFiles)) - log.Printf("Event context: %+v", event.Context()) - - if details != nil { - log.Printf("DropZone details found:") - log.Printf(" ElementID: '%s'", details.ElementID) - log.Printf(" ClassList: %v", details.ClassList) - log.Printf(" X: %d, Y: %d", details.X, details.Y) - log.Printf(" Attributes: %+v", details.Attributes) - - // Call the App method with the extracted data - FilesDroppedOnTarget( - droppedFiles, - details.ElementID, - details.ClassList, - float64(details.X), - float64(details.Y), - details.ElementID != "", // isTargetDropzone based on whether an ID was found - details.Attributes, - ) - } else { - log.Println("DropZone details are nil - drop was not on a specific registered zone") - // This case might occur if DropZoneDetails are nil, meaning the drop was not on a specific registered zone - // or if the context itself was problematic. - FilesDroppedOnTarget(droppedFiles, "", nil, 0, 0, false, nil) - } - - payload := FileDropInfo{ - Files: droppedFiles, - TargetID: details.ElementID, - TargetClasses: details.ClassList, - DropX: float64(details.X), - DropY: float64(details.Y), - Attributes: details.Attributes, // Add the attributes - } - - log.Printf("Emitting event payload: %+v", payload) - application.Get().Event.Emit("frontend:FileDropInfo", payload) - log.Println( - "=============== End WindowDropZoneFilesDropped Event Debug ===============", - ) - }, - ) + // Emit event to frontend + application.Get().Event.Emit("files-dropped", map[string]any{ + "files": files, + "details": details, + }) + }) err := app.Run() - if err != nil { - log.Fatal(err.Error()) + log.Fatal(err) } } diff --git a/v3/examples/html-dnd-api/README.md b/v3/examples/html-dnd-api/README.md deleted file mode 100644 index 1b06f3882..000000000 --- a/v3/examples/html-dnd-api/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# HTML Drag and Drop API Example - -This example should demonstrate whether the [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API") works correctly. - -## Expected Behaviour - -When dragging the "draggable", in the console should be printed: -1. "dragstart" once -2. "drag" many times -3. "dragend" once - -When dragging the "draggable" on the drop target, the inner text of the latter shoud change and in the console should be printed: -1. "dragstart" once -2. "drag" many times -3. "dragenter" once -4. "dragover" many times (alternating with "drag") -5. - "drop" once (in case of a drop inside the drop target) - - "dragleave" once (in case the draggable div leaves the drop target) -6. "dragend" once - -## Running the example - -To run the example, simply run the following command: - -```bash -go run main.go -``` - -## Building the example - -To build the example in debug mode, simply run the following command: - -```bash -wails3 task build -``` - -# Status - -| Platform | Status | -|----------|-------------| -| Mac | Working | -| Windows | Not Working | -| Linux | | diff --git a/v3/examples/html-dnd-api/assets/index.html b/v3/examples/html-dnd-api/assets/index.html deleted file mode 100644 index a64b9378a..000000000 --- a/v3/examples/html-dnd-api/assets/index.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Title - - - -

            HTML Drag and Drop API Demo

            -
            - -
            draggable
            - -
            drop target
            - - - - - - diff --git a/v3/examples/html-dnd-api/main.go b/v3/examples/html-dnd-api/main.go deleted file mode 100644 index 549523ef1..000000000 --- a/v3/examples/html-dnd-api/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "embed" - "log" - - "github.com/wailsapp/wails/v3/pkg/application" -) - -//go:embed assets -var assets embed.FS - -func main() { - - app := application.New(application.Options{ - Name: "HTML Drag and Drop API Demo", - Description: "A demo of the HTML Drag and drop API", - Assets: application.AssetOptions{ - Handler: application.BundledAssetFileServer(assets), - }, - Mac: application.MacOptions{ - ApplicationShouldTerminateAfterLastWindowClosed: true, - }, - }) - - app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Drag-n-drop Demo", - Mac: application.MacWindow{ - Backdrop: application.MacBackdropTranslucent, - TitleBar: application.MacTitleBarHiddenInsetUnified, - InvisibleTitleBarHeight: 50, - }, - }) - - err := app.Run() - - if err != nil { - log.Fatal(err.Error()) - } -} diff --git a/v3/examples/liquid-glass/main.go b/v3/examples/liquid-glass/main.go index ff2644cab..61f338b96 100644 --- a/v3/examples/liquid-glass/main.go +++ b/v3/examples/liquid-glass/main.go @@ -61,15 +61,15 @@ func main() { // Window 1: Light style with no tint window1 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Light Glass", - Width: 350, - Height: 280, - X: 100, - Y: 100, - Frameless: true, - EnableDragAndDrop: false, - HTML: lightHTML, - InitialPosition: application.WindowXY, + Title: "Light Glass", + Width: 350, + Height: 280, + X: 100, + Y: 100, + Frameless: true, + EnableFileDrop: false, + HTML: lightHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, @@ -84,15 +84,15 @@ func main() { // Window 2: Dark style window2 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Dark Glass", - Width: 350, - Height: 280, - X: 500, - Y: 100, - Frameless: true, - EnableDragAndDrop: false, - HTML: darkHTML, - InitialPosition: application.WindowXY, + Title: "Dark Glass", + Width: 350, + Height: 280, + X: 500, + Y: 100, + Frameless: true, + EnableFileDrop: false, + HTML: darkHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, @@ -107,15 +107,15 @@ func main() { // Window 3: Vibrant style window3 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Vibrant Glass", - Width: 350, - Height: 280, - X: 900, - Y: 100, - Frameless: true, - EnableDragAndDrop: false, - HTML: vibrantHTML, - InitialPosition: application.WindowXY, + Title: "Vibrant Glass", + Width: 350, + Height: 280, + X: 900, + Y: 100, + Frameless: true, + EnableFileDrop: false, + HTML: vibrantHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, @@ -130,15 +130,15 @@ func main() { // Window 4: Blue tinted glass window4 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Tinted Glass", - Width: 350, - Height: 280, - X: 300, - Y: 420, - Frameless: true, - EnableDragAndDrop: false, - HTML: tintedHTML, - InitialPosition: application.WindowXY, + Title: "Tinted Glass", + Width: 350, + Height: 280, + X: 300, + Y: 420, + Frameless: true, + EnableFileDrop: false, + HTML: tintedHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, @@ -153,15 +153,15 @@ func main() { // Window 5: Using specific NSVisualEffectMaterial window5 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Sheet Material", - Width: 350, - Height: 280, - X: 700, - Y: 420, - Frameless: true, - EnableDragAndDrop: false, - HTML: sheetHTML, - InitialPosition: application.WindowXY, + Title: "Sheet Material", + Width: 350, + Height: 280, + X: 700, + Y: 420, + Frameless: true, + EnableFileDrop: false, + HTML: sheetHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, @@ -176,15 +176,15 @@ func main() { // Window 6: HUD Window Material (very light, translucent) window6 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "HUD Window", - Width: 350, - Height: 280, - X: 100, - Y: 740, - Frameless: true, - EnableDragAndDrop: false, - HTML: hudHTML, - InitialPosition: application.WindowXY, + Title: "HUD Window", + Width: 350, + Height: 280, + X: 100, + Y: 740, + Frameless: true, + EnableFileDrop: false, + HTML: hudHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, @@ -199,15 +199,15 @@ func main() { // Window 7: Content Background Material window7 := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Content Background", - Width: 350, - Height: 280, - X: 500, - Y: 740, - Frameless: true, - EnableDragAndDrop: false, - HTML: contentHTML, - InitialPosition: application.WindowXY, + Title: "Content Background", + Width: 350, + Height: 280, + X: 500, + Y: 740, + Frameless: true, + EnableFileDrop: false, + HTML: contentHTML, + InitialPosition: application.WindowXY, Mac: application.MacWindow{ Backdrop: application.MacBackdropLiquidGlass, InvisibleTitleBarHeight: 500, diff --git a/v3/examples/spotlight/README.md b/v3/examples/spotlight/README.md new file mode 100644 index 000000000..bda8f464e --- /dev/null +++ b/v3/examples/spotlight/README.md @@ -0,0 +1,66 @@ +# Spotlight Example + +This example demonstrates how to create a Spotlight-like launcher window using the `CollectionBehavior` option on macOS. + +## Features + +- **Appears on all Spaces**: Using `MacWindowCollectionBehaviorCanJoinAllSpaces`, the window is visible across all virtual desktops +- **Overlays fullscreen apps**: Using `MacWindowCollectionBehaviorFullScreenAuxiliary`, the window can appear over fullscreen applications +- **Combined behaviors**: Demonstrates combining multiple behaviors with bitwise OR +- **Floating window**: `MacWindowLevelFloating` keeps the window above other windows +- **Accessory app**: Doesn't appear in the Dock (uses `ActivationPolicyAccessory`) +- **Frameless design**: Clean, borderless appearance with translucent backdrop + +## Running the example + +```bash +go run . +``` + +**Note**: This example is macOS-specific due to the use of `CollectionBehavior`. + +## Combining CollectionBehaviors + +Behaviors can be combined using bitwise OR (`|`): + +```go +CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, +``` + +## CollectionBehavior Options + +These are bitmask values that can be combined: + +**Space behavior:** +| Option | Description | +|--------|-------------| +| `MacWindowCollectionBehaviorDefault` | Uses FullScreenPrimary (default) | +| `MacWindowCollectionBehaviorCanJoinAllSpaces` | Window appears on all Spaces | +| `MacWindowCollectionBehaviorMoveToActiveSpace` | Moves to active Space when shown | +| `MacWindowCollectionBehaviorManaged` | Default managed window behavior | +| `MacWindowCollectionBehaviorTransient` | Temporary/transient window | +| `MacWindowCollectionBehaviorStationary` | Stays stationary during Space switches | + +**Fullscreen behavior:** +| Option | Description | +|--------|-------------| +| `MacWindowCollectionBehaviorFullScreenPrimary` | Can enter fullscreen mode | +| `MacWindowCollectionBehaviorFullScreenAuxiliary` | Can overlay fullscreen apps | +| `MacWindowCollectionBehaviorFullScreenNone` | Disables fullscreen | +| `MacWindowCollectionBehaviorFullScreenAllowsTiling` | Allows side-by-side tiling | + +## Use Cases + +- **Launcher apps** (like Spotlight, Alfred, Raycast) +- **Quick capture tools** (notes, screenshots) +- **System utilities** that need to be accessible anywhere +- **Overlay widgets** that should appear over fullscreen apps + +## Status + +| Platform | Status | +|----------|--------| +| Mac | Working | +| Windows | N/A (macOS-specific feature) | +| Linux | N/A (macOS-specific feature) | diff --git a/v3/examples/spotlight/main.go b/v3/examples/spotlight/main.go new file mode 100644 index 000000000..d961f1f54 --- /dev/null +++ b/v3/examples/spotlight/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "log" + "net/http" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// This example demonstrates how to create a Spotlight-like launcher window +// that appears on all macOS Spaces and can overlay fullscreen applications. +// +// Key features: +// - Window appears on all Spaces (virtual desktops) +// - Can overlay fullscreen applications +// - Floating window level keeps it above other windows +// - Accessory activation policy hides from Dock +// - Frameless design with translucent backdrop + +func main() { + app := application.New(application.Options{ + Name: "Spotlight Example", + Description: "A Spotlight-like launcher demonstrating CollectionBehavior", + Mac: application.MacOptions{ + // Accessory apps don't appear in the Dock + ActivationPolicy: application.ActivationPolicyAccessory, + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(spotlightHTML)) + }), + }, + }) + + // Create a Spotlight-like window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Spotlight", + Width: 680, + Height: 80, + Frameless: true, + // Center the window + InitialPosition: application.WindowCentered, + // Prevent resizing + DisableResize: true, + Mac: application.MacWindow{ + // Combine multiple behaviors using bitwise OR: + // - CanJoinAllSpaces: window appears on ALL Spaces (virtual desktops) + // - FullScreenAuxiliary: window can overlay fullscreen applications + CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, + // Float above other windows + WindowLevel: application.MacWindowLevelFloating, + // Translucent vibrancy effect + Backdrop: application.MacBackdropTranslucent, + // Hidden title bar for clean look + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + Hide: true, + }, + }, + URL: "/", + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} + +const spotlightHTML = ` + + + Spotlight + + + + +
            + + + + + +
            + +` diff --git a/v3/go.mod b/v3/go.mod index 5eb97b298..b5fe8b282 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -1,6 +1,6 @@ module github.com/wailsapp/wails/v3 -go 1.24.0 +go 1.25 require ( git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 @@ -12,6 +12,7 @@ require ( github.com/charmbracelet/huh v0.8.0 github.com/ebitengine/purego v0.8.2 github.com/go-git/go-git/v5 v5.13.2 + github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e github.com/go-ole/go-ole v1.3.0 github.com/godbus/dbus/v5 v5.1.0 github.com/google/go-cmp v0.7.0 @@ -38,13 +39,11 @@ require ( github.com/stretchr/testify v1.10.0 github.com/tc-hib/winres v0.3.1 github.com/wailsapp/go-webview2 v1.0.22 - github.com/wailsapp/mimetype v1.4.1 github.com/wailsapp/task/v3 v3.40.1-patched3 github.com/zalando/go-keyring v0.2.6 golang.org/x/sys v0.33.0 golang.org/x/term v0.30.0 golang.org/x/tools v0.31.0 - gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.36.0 ) @@ -160,7 +159,7 @@ require ( golang.org/x/text v0.23.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - howett.net/plist v1.0.1 // indirect + howett.net/plist v1.0.1 modernc.org/libc v1.61.13 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.8.2 // indirect diff --git a/v3/go.sum b/v3/go.sum index 42265744d..f5bde5f7c 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -157,6 +157,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -363,8 +365,6 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= -github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= -github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/task/v3 v3.40.1-patched3 h1:i6O1WNdSur9CGaiMDIYGjsmj/qS4465zqv+WEs6sPRs= github.com/wailsapp/task/v3 v3.40.1-patched3/go.mod h1:jIP48r8ftoSQNlxFP4+aEnkvGQqQXqCnRi/B7ROaecE= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -406,7 +406,6 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -464,8 +463,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= diff --git a/v3/internal/assetserver/assetserver.go b/v3/internal/assetserver/assetserver.go index 6ff1b169f..60b2fcbfb 100644 --- a/v3/internal/assetserver/assetserver.go +++ b/v3/internal/assetserver/assetserver.go @@ -69,7 +69,7 @@ func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { req = req.WithContext(contextWithLogger(req.Context(), a.options.Logger)) a.handler.ServeHTTP(wrapped, req) - a.options.Logger.Info( + a.options.Logger.Debug( "Asset Request:", "windowName", req.Header.Get(webViewRequestHeaderWindowName), "windowID", req.Header.Get(webViewRequestHeaderWindowId), diff --git a/v3/internal/assetserver/assetserver_bench_test.go b/v3/internal/assetserver/assetserver_bench_test.go new file mode 100644 index 000000000..59a55411b --- /dev/null +++ b/v3/internal/assetserver/assetserver_bench_test.go @@ -0,0 +1,242 @@ +//go:build bench + +package assetserver + +import ( + "fmt" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "sync" + "testing" +) + +// resetMimeCache clears the mime cache for benchmark isolation +func resetMimeCache() { + mimeCache = sync.Map{} +} + +// BenchmarkGetMimetype measures MIME type detection performance +func BenchmarkGetMimetype(b *testing.B) { + // Reset cache between runs + resetMimeCache() + + b.Run("ByExtension/JS", func(b *testing.B) { + data := []byte("function test() {}") + for b.Loop() { + _ = GetMimetype("script.js", data) + } + }) + + resetMimeCache() + b.Run("ByExtension/CSS", func(b *testing.B) { + data := []byte(".class { color: red; }") + for b.Loop() { + _ = GetMimetype("style.css", data) + } + }) + + resetMimeCache() + b.Run("ByExtension/HTML", func(b *testing.B) { + data := []byte("") + for b.Loop() { + _ = GetMimetype("index.html", data) + } + }) + + resetMimeCache() + b.Run("ByExtension/JSON", func(b *testing.B) { + data := []byte(`{"key": "value"}`) + for b.Loop() { + _ = GetMimetype("data.json", data) + } + }) + + resetMimeCache() + b.Run("Detection/Unknown", func(b *testing.B) { + data := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05} + for b.Loop() { + _ = GetMimetype("unknown.bin", data) + } + }) + + resetMimeCache() + b.Run("Detection/PNG", func(b *testing.B) { + // PNG magic bytes + data := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00} + for b.Loop() { + _ = GetMimetype("image.unknown", data) + } + }) + + resetMimeCache() + b.Run("CacheHit", func(b *testing.B) { + data := []byte{0x00, 0x01, 0x02} + // Prime the cache + _ = GetMimetype("cached.bin", data) + b.ResetTimer() + for b.Loop() { + _ = GetMimetype("cached.bin", data) + } + }) +} + +// BenchmarkGetMimetype_Concurrent tests concurrent MIME type lookups +func BenchmarkGetMimetype_Concurrent(b *testing.B) { + resetMimeCache() + data := []byte("function test() {}") + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = GetMimetype("script.js", data) + } + }) +} + +// BenchmarkAssetServerServeHTTP measures request handling overhead +func BenchmarkAssetServerServeHTTP(b *testing.B) { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError + 1})) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello")) + }) + + server, err := NewAssetServer(&Options{ + Handler: handler, + Logger: logger, + }) + if err != nil { + b.Fatal(err) + } + + b.Run("SimpleRequest", func(b *testing.B) { + req := httptest.NewRequest("GET", "/index.html", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) + + b.Run("WithHeaders", func(b *testing.B) { + req := httptest.NewRequest("GET", "/index.html", nil) + req.Header.Set("x-wails-window-id", "1") + req.Header.Set("x-wails-window-name", "main") + req.Header.Set("Accept-Language", "en-US,en;q=0.9") + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) +} + +// BenchmarkAssetServerServeHTTP_Concurrent tests concurrent request handling +func BenchmarkAssetServerServeHTTP_Concurrent(b *testing.B) { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError + 1})) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello")) + }) + + server, err := NewAssetServer(&Options{ + Handler: handler, + Logger: logger, + }) + if err != nil { + b.Fatal(err) + } + + b.RunParallel(func(pb *testing.PB) { + req := httptest.NewRequest("GET", "/index.html", nil) + for pb.Next() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) +} + +// BenchmarkContentTypeSniffer measures the content type sniffer overhead +func BenchmarkContentTypeSniffer(b *testing.B) { + b.Run("SmallResponse", func(b *testing.B) { + data := []byte("Hello, World!") + for b.Loop() { + rr := httptest.NewRecorder() + sniffer := newContentTypeSniffer(rr) + _, _ = sniffer.Write(data) + _, _ = sniffer.complete() + } + }) + + b.Run("HTMLResponse", func(b *testing.B) { + data := []byte("Test

            Hello

            ") + for b.Loop() { + rr := httptest.NewRecorder() + sniffer := newContentTypeSniffer(rr) + _, _ = sniffer.Write(data) + _, _ = sniffer.complete() + } + }) + + b.Run("LargeResponse", func(b *testing.B) { + data := make([]byte, 64*1024) // 64KB + for i := range data { + data[i] = byte(i % 256) + } + for b.Loop() { + rr := httptest.NewRecorder() + sniffer := newContentTypeSniffer(rr) + _, _ = sniffer.Write(data) + _, _ = sniffer.complete() + } + }) +} + +// BenchmarkServiceRouting measures service route matching performance +func BenchmarkServiceRouting(b *testing.B) { + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError + 1})) + + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + server, err := NewAssetServer(&Options{ + Handler: dummyHandler, + Logger: logger, + }) + if err != nil { + b.Fatal(err) + } + + // Attach multiple service routes + for i := 0; i < 10; i++ { + server.AttachServiceHandler(fmt.Sprintf("/api/v%d/", i), dummyHandler) + } + + b.Run("FirstRoute", func(b *testing.B) { + req := httptest.NewRequest("GET", "/api/v0/users", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) + + b.Run("LastRoute", func(b *testing.B) { + req := httptest.NewRequest("GET", "/api/v9/users", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) + + b.Run("NoMatch", func(b *testing.B) { + req := httptest.NewRequest("GET", "/static/app.js", nil) + for b.Loop() { + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + } + }) +} diff --git a/v3/internal/assetserver/bundledassets/runtime.debug.js b/v3/internal/assetserver/bundledassets/runtime.debug.js index ad54d9bdf..bd783ca6b 100644 --- a/v3/internal/assetserver/bundledassets/runtime.debug.js +++ b/v3/internal/assetserver/bundledassets/runtime.debug.js @@ -546,8 +546,7 @@ var Types = Object.freeze({ WindowZoom: "common:WindowZoom", WindowZoomIn: "common:WindowZoomIn", WindowZoomOut: "common:WindowZoomOut", - WindowZoomReset: "common:WindowZoomReset", - WindowDropZoneFilesDropped: "common:WindowDropZoneFilesDropped" + WindowZoomReset: "common:WindowZoomReset" }) }); @@ -651,9 +650,9 @@ function whenReady(callback) { } // desktop/@wailsio/runtime/src/window.ts -var DROPZONE_ATTRIBUTE = "data-wails-dropzone"; -var DROPZONE_HOVER_CLASS = "wails-dropzone-hover"; -var currentHoveredDropzone = null; +var DROP_TARGET_ATTRIBUTE = "data-file-drop-target"; +var DROP_TARGET_ACTIVE_CLASS = "file-drop-target-active"; +var currentDropTarget = null; var PositionMethod = 0; var CenterMethod = 1; var CloseMethod = 2; @@ -704,13 +703,62 @@ var ZoomInMethod = 46; var ZoomOutMethod = 47; var ZoomResetMethod = 48; var SnapAssistMethod = 49; -var WindowDropZoneDropped = 50; +var FilesDropped = 50; var PrintMethod = 51; -function getDropzoneElement(element) { +function getDropTargetElement(element) { if (!element) { return null; } - return element.closest("[".concat(DROPZONE_ATTRIBUTE, "]")); + return element.closest("[".concat(DROP_TARGET_ATTRIBUTE, "]")); +} +function canResolveFilePaths() { + var _a2, _b, _c, _d; + if (((_b = (_a2 = window.chrome) == null ? void 0 : _a2.webview) == null ? void 0 : _b.postMessageWithAdditionalObjects) == null) { + return false; + } + return ((_d = (_c = window._wails) == null ? void 0 : _c.flags) == null ? void 0 : _d.enableFileDrop) === true; +} +function resolveFilePaths(x, y, files) { + var _a2, _b; + if ((_b = (_a2 = window.chrome) == null ? void 0 : _a2.webview) == null ? void 0 : _b.postMessageWithAdditionalObjects) { + window.chrome.webview.postMessageWithAdditionalObjects("file:drop:".concat(x, ":").concat(y), files); + } +} +var nativeDragActive = false; +function cleanupNativeDrag() { + nativeDragActive = false; + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } +} +function handleDragEnter() { + var _a2, _b; + if (((_b = (_a2 = window._wails) == null ? void 0 : _a2.flags) == null ? void 0 : _b.enableFileDrop) === false) { + return; + } + nativeDragActive = true; +} +function handleDragLeave() { + cleanupNativeDrag(); +} +function handleDragOver(x, y) { + var _a2, _b; + if (!nativeDragActive) return; + if (((_b = (_a2 = window._wails) == null ? void 0 : _a2.flags) == null ? void 0 : _b.enableFileDrop) === false) { + return; + } + const targetElement = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(targetElement); + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = dropTarget; + } else { + currentDropTarget = null; + } } var callerSym = /* @__PURE__ */ Symbol("caller"); callerSym; @@ -1091,28 +1139,30 @@ var _Window = class _Window { return this[callerSym](ZoomResetMethod); } /** - * Handles file drops originating from platform-specific code (e.g., macOS native drag-and-drop). + * Handles file drops originating from platform-specific code (e.g., macOS/Linux native drag-and-drop). * Gathers information about the drop target element and sends it back to the Go backend. * * @param filenames - An array of file paths (strings) that were dropped. - * @param x - The x-coordinate of the drop event. - * @param y - The y-coordinate of the drop event. + * @param x - The x-coordinate of the drop event (CSS pixels). + * @param y - The y-coordinate of the drop event (CSS pixels). */ HandlePlatformFileDrop(filenames, x, y) { - const element = document.elementFromPoint(x, y); - const dropzoneTarget = getDropzoneElement(element); - if (!dropzoneTarget) { - console.log("Wails Runtime: Drop on element (or no element) at ".concat(x, ",").concat(y, " which is not a designated dropzone. Ignoring. Element:"), element); + var _a2, _b; + if (((_b = (_a2 = window._wails) == null ? void 0 : _a2.flags) == null ? void 0 : _b.enableFileDrop) === false) { + return; + } + const element = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(element); + if (!dropTarget) { return; } - console.log("Wails Runtime: Drop on designated dropzone. Element at (".concat(x, ", ").concat(y, "):"), element, "Effective dropzone:", dropzoneTarget); const elementDetails = { - id: dropzoneTarget.id, - classList: Array.from(dropzoneTarget.classList), + id: dropTarget.id, + classList: Array.from(dropTarget.classList), attributes: {} }; - for (let i = 0; i < dropzoneTarget.attributes.length; i++) { - const attr = dropzoneTarget.attributes[i]; + for (let i = 0; i < dropTarget.attributes.length; i++) { + const attr = dropTarget.attributes[i]; elementDetails.attributes[attr.name] = attr.value; } const payload = { @@ -1121,7 +1171,8 @@ var _Window = class _Window { y, elementDetails }; - this[callerSym](WindowDropZoneDropped, payload); + this[callerSym](FilesDropped, payload); + cleanupNativeDrag(); } /* Triggers Windows 11 Snap Assist feature (Windows only). * This is equivalent to pressing Win+Z and shows snap layout options. @@ -1138,65 +1189,117 @@ var _Window = class _Window { }; var Window = _Window; var thisWindow = new Window(""); -function setupGlobalDropzoneListeners() { +function setupDropTargetListeners() { const docElement = document.documentElement; let dragEnterCounter = 0; docElement.addEventListener("dragenter", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } event.preventDefault(); - if (event.dataTransfer && event.dataTransfer.types.includes("Files")) { - dragEnterCounter++; - const targetElement = document.elementFromPoint(event.clientX, event.clientY); - const dropzone = getDropzoneElement(targetElement); - if (currentHoveredDropzone && currentHoveredDropzone !== dropzone) { - currentHoveredDropzone.classList.remove(DROPZONE_HOVER_CLASS); - } - if (dropzone) { - dropzone.classList.add(DROPZONE_HOVER_CLASS); - event.dataTransfer.dropEffect = "copy"; - currentHoveredDropzone = dropzone; - } else { - event.dataTransfer.dropEffect = "none"; - currentHoveredDropzone = null; - } + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + event.dataTransfer.dropEffect = "none"; + return; + } + dragEnterCounter++; + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + event.dataTransfer.dropEffect = "copy"; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = "none"; + currentDropTarget = null; } }, false); docElement.addEventListener("dragover", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } event.preventDefault(); - if (event.dataTransfer && event.dataTransfer.types.includes("Files")) { - if (currentHoveredDropzone) { - if (!currentHoveredDropzone.classList.contains(DROPZONE_HOVER_CLASS)) { - currentHoveredDropzone.classList.add(DROPZONE_HOVER_CLASS); - } - event.dataTransfer.dropEffect = "copy"; - } else { - event.dataTransfer.dropEffect = "none"; + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + event.dataTransfer.dropEffect = "none"; + return; + } + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + if (dropTarget) { + if (!dropTarget.classList.contains(DROP_TARGET_ACTIVE_CLASS)) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); } + event.dataTransfer.dropEffect = "copy"; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = "none"; + currentDropTarget = null; } }, false); docElement.addEventListener("dragleave", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } event.preventDefault(); - if (event.dataTransfer && event.dataTransfer.types.includes("Files")) { - dragEnterCounter--; - if (dragEnterCounter === 0 || event.relatedTarget === null || currentHoveredDropzone && !currentHoveredDropzone.contains(event.relatedTarget)) { - if (currentHoveredDropzone) { - currentHoveredDropzone.classList.remove(DROPZONE_HOVER_CLASS); - currentHoveredDropzone = null; - } - dragEnterCounter = 0; + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + return; + } + if (event.relatedTarget === null) { + return; + } + dragEnterCounter--; + if (dragEnterCounter === 0 || currentDropTarget && !currentDropTarget.contains(event.relatedTarget)) { + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; } + dragEnterCounter = 0; } }, false); docElement.addEventListener("drop", (event) => { + var _a2, _b, _c; + if (!((_a2 = event.dataTransfer) == null ? void 0 : _a2.types.includes("Files"))) { + return; + } event.preventDefault(); + if (((_c = (_b = window._wails) == null ? void 0 : _b.flags) == null ? void 0 : _c.enableFileDrop) === false) { + return; + } dragEnterCounter = 0; - if (currentHoveredDropzone) { - currentHoveredDropzone.classList.remove(DROPZONE_HOVER_CLASS); - currentHoveredDropzone = null; + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } + if (canResolveFilePaths()) { + const files = []; + if (event.dataTransfer.items) { + for (const item of event.dataTransfer.items) { + if (item.kind === "file") { + const file = item.getAsFile(); + if (file) files.push(file); + } + } + } else if (event.dataTransfer.files) { + for (const file of event.dataTransfer.files) { + files.push(file); + } + } + if (files.length > 0) { + resolveFilePaths(event.clientX, event.clientY, files); + } } }, false); } if (typeof window !== "undefined" && typeof document !== "undefined") { - setupGlobalDropzoneListeners(); + setupDropTargetListeners(); } var window_default = thisWindow; @@ -1345,7 +1448,6 @@ var system_exports = {}; __export(system_exports, { Capabilities: () => Capabilities, Environment: () => Environment, - HandlePlatformFileDrop: () => HandlePlatformFileDrop, IsAMD64: () => IsAMD64, IsARM: () => IsARM, IsARM64: () => IsARM64, @@ -1360,7 +1462,6 @@ var call4 = newRuntimeCaller(objectNames.System); var SystemIsDarkMode = 0; var SystemEnvironment = 1; var SystemCapabilities = 2; -var ApplicationFilesDroppedWithContext = 100; var _invoke = (function() { var _a2, _b, _c, _d, _e, _f; try { @@ -1421,23 +1522,6 @@ function IsDebug() { var _a2, _b; return Boolean((_b = (_a2 = window._wails) == null ? void 0 : _a2.environment) == null ? void 0 : _b.Debug); } -function HandlePlatformFileDrop(filenames, x, y) { - const element = document.elementFromPoint(x, y); - const elementId = element ? element.id : ""; - const classList = element ? Array.from(element.classList) : []; - const payload = { - filenames, - x, - y, - elementId, - classList - }; - call4(ApplicationFilesDroppedWithContext, payload).then(() => { - console.log("Platform file drop processed and sent to Go."); - }).catch((err) => { - console.error("Error sending platform file drop to Go:", err); - }); -} // desktop/@wailsio/runtime/src/contextmenu.ts window.addEventListener("contextmenu", contextMenuHandler); @@ -2647,6 +2731,9 @@ var Device; window._wails = window._wails || {}; window._wails.invoke = invoke; window._wails.handlePlatformFileDrop = window_default.HandlePlatformFileDrop.bind(window_default); +window._wails.handleDragEnter = handleDragEnter; +window._wails.handleDragLeave = handleDragLeave; +window._wails.handleDragOver = handleDragOver; invoke("wails:runtime:ready"); export { application_exports as Application, @@ -2670,4 +2757,4 @@ export { objectNames, setTransport }; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2luZGV4LnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy93bWwudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2Jyb3dzZXIudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL25hbm9pZC50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvcnVudGltZS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZGlhbG9ncy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZXZlbnRzLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9saXN0ZW5lci50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY3JlYXRlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9ldmVudF90eXBlcy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvdXRpbHMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3dpbmRvdy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvY29tcGlsZWQvbWFpbi5qcyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvc3lzdGVtLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jb250ZXh0bWVudS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZmxhZ3MudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2RyYWcudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2FwcGxpY2F0aW9uLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jYWxscy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY2FsbGFibGUudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2NhbmNlbGxhYmxlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jbGlwYm9hcmQudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3NjcmVlbnMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2lvcy50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcblxuaW1wb3J0IFwiLi9jb250ZXh0bWVudS5qc1wiO1xuaW1wb3J0IFwiLi9kcmFnLmpzXCI7XG5cbi8vIFJlLWV4cG9ydCBwdWJsaWMgQVBJXG5pbXBvcnQgKiBhcyBBcHBsaWNhdGlvbiBmcm9tIFwiLi9hcHBsaWNhdGlvbi5qc1wiO1xuaW1wb3J0ICogYXMgQnJvd3NlciBmcm9tIFwiLi9icm93c2VyLmpzXCI7XG5pbXBvcnQgKiBhcyBDYWxsIGZyb20gXCIuL2NhbGxzLmpzXCI7XG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSBcIi4vY2xpcGJvYXJkLmpzXCI7XG5pbXBvcnQgKiBhcyBDcmVhdGUgZnJvbSBcIi4vY3JlYXRlLmpzXCI7XG5pbXBvcnQgKiBhcyBEaWFsb2dzIGZyb20gXCIuL2RpYWxvZ3MuanNcIjtcbmltcG9ydCAqIGFzIEV2ZW50cyBmcm9tIFwiLi9ldmVudHMuanNcIjtcbmltcG9ydCAqIGFzIEZsYWdzIGZyb20gXCIuL2ZsYWdzLmpzXCI7XG5pbXBvcnQgKiBhcyBTY3JlZW5zIGZyb20gXCIuL3NjcmVlbnMuanNcIjtcbmltcG9ydCAqIGFzIFN5c3RlbSBmcm9tIFwiLi9zeXN0ZW0uanNcIjtcbmltcG9ydCAqIGFzIElPUyBmcm9tIFwiLi9pb3MuanNcIjtcbmltcG9ydCBXaW5kb3cgZnJvbSBcIi4vd2luZG93LmpzXCI7XG5pbXBvcnQgKiBhcyBXTUwgZnJvbSBcIi4vd21sLmpzXCI7XG5cbmV4cG9ydCB7XG4gICAgQXBwbGljYXRpb24sXG4gICAgQnJvd3NlcixcbiAgICBDYWxsLFxuICAgIENsaXBib2FyZCxcbiAgICBEaWFsb2dzLFxuICAgIEV2ZW50cyxcbiAgICBGbGFncyxcbiAgICBTY3JlZW5zLFxuICAgIFN5c3RlbSxcbiAgICBJT1MsXG4gICAgV2luZG93LFxuICAgIFdNTFxufTtcblxuLyoqXG4gKiBBbiBpbnRlcm5hbCB1dGlsaXR5IGNvbnN1bWVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqXG4gKiBAaWdub3JlXG4gKi9cbmV4cG9ydCB7IENyZWF0ZSB9O1xuXG5leHBvcnQgKiBmcm9tIFwiLi9jYW5jZWxsYWJsZS5qc1wiO1xuXG4vLyBFeHBvcnQgdHJhbnNwb3J0IGludGVyZmFjZXMgYW5kIHV0aWxpdGllc1xuZXhwb3J0IHtcbiAgICBzZXRUcmFuc3BvcnQsXG4gICAgZ2V0VHJhbnNwb3J0LFxuICAgIHR5cGUgUnVudGltZVRyYW5zcG9ydCxcbiAgICBvYmplY3ROYW1lcyxcbiAgICBjbGllbnRJZCxcbn0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG4vLyBOb3RpZnkgYmFja2VuZFxud2luZG93Ll93YWlscy5pbnZva2UgPSBTeXN0ZW0uaW52b2tlO1xuXG4vLyBSZWdpc3RlciBwbGF0Zm9ybSBoYW5kbGVycyAoaW50ZXJuYWwgQVBJKVxuLy8gTm90ZTogV2luZG93IGlzIHRoZSB0aGlzV2luZG93IGluc3RhbmNlIChkZWZhdWx0IGV4cG9ydCBmcm9tIHdpbmRvdy50cylcbi8vIEJpbmRpbmcgZW5zdXJlcyAndGhpcycgY29ycmVjdGx5IHJlZmVycyB0byB0aGUgY3VycmVudCB3aW5kb3cgaW5zdGFuY2VcbndpbmRvdy5fd2FpbHMuaGFuZGxlUGxhdGZvcm1GaWxlRHJvcCA9IFdpbmRvdy5IYW5kbGVQbGF0Zm9ybUZpbGVEcm9wLmJpbmQoV2luZG93KTtcblxuU3lzdGVtLmludm9rZShcIndhaWxzOnJ1bnRpbWU6cmVhZHlcIik7XG4iLCAiLypcbiBfICAgICBfXyAgICAgXyBfX1xufCB8ICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IE9wZW5VUkwgfSBmcm9tIFwiLi9icm93c2VyLmpzXCI7XG5pbXBvcnQgeyBRdWVzdGlvbiB9IGZyb20gXCIuL2RpYWxvZ3MuanNcIjtcbmltcG9ydCB7IEVtaXQgfSBmcm9tIFwiLi9ldmVudHMuanNcIjtcbmltcG9ydCB7IGNhbkFib3J0TGlzdGVuZXJzLCB3aGVuUmVhZHkgfSBmcm9tIFwiLi91dGlscy5qc1wiO1xuaW1wb3J0IFdpbmRvdyBmcm9tIFwiLi93aW5kb3cuanNcIjtcblxuLyoqXG4gKiBTZW5kcyBhbiBldmVudCB3aXRoIHRoZSBnaXZlbiBuYW1lIGFuZCBvcHRpb25hbCBkYXRhLlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byBzZW5kLlxuICogQHBhcmFtIFtkYXRhPW51bGxdIC0gLSBPcHRpb25hbCBkYXRhIHRvIHNlbmQgYWxvbmcgd2l0aCB0aGUgZXZlbnQuXG4gKi9cbmZ1bmN0aW9uIHNlbmRFdmVudChldmVudE5hbWU6IHN0cmluZywgZGF0YTogYW55ID0gbnVsbCk6IHZvaWQge1xuICAgIEVtaXQoZXZlbnROYW1lLCBkYXRhKTtcbn1cblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBvbiBhIHNwZWNpZmllZCB3aW5kb3cuXG4gKlxuICogQHBhcmFtIHdpbmRvd05hbWUgLSBUaGUgbmFtZSBvZiB0aGUgd2luZG93IHRvIGNhbGwgdGhlIG1ldGhvZCBvbi5cbiAqIEBwYXJhbSBtZXRob2ROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIG1ldGhvZCB0byBjYWxsLlxuICovXG5mdW5jdGlvbiBjYWxsV2luZG93TWV0aG9kKHdpbmRvd05hbWU6IHN0cmluZywgbWV0aG9kTmFtZTogc3RyaW5nKSB7XG4gICAgY29uc3QgdGFyZ2V0V2luZG93ID0gV2luZG93LkdldCh3aW5kb3dOYW1lKTtcbiAgICBjb25zdCBtZXRob2QgPSAodGFyZ2V0V2luZG93IGFzIGFueSlbbWV0aG9kTmFtZV07XG5cbiAgICBpZiAodHlwZW9mIG1ldGhvZCAhPT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYFdpbmRvdyBtZXRob2QgJyR7bWV0aG9kTmFtZX0nIG5vdCBmb3VuZGApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgICAgbWV0aG9kLmNhbGwodGFyZ2V0V2luZG93KTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYEVycm9yIGNhbGxpbmcgd2luZG93IG1ldGhvZCAnJHttZXRob2ROYW1lfSc6IGAsIGUpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZXNwb25kcyB0byBhIHRyaWdnZXJpbmcgZXZlbnQgYnkgcnVubmluZyBhcHByb3ByaWF0ZSBXTUwgYWN0aW9ucyBmb3IgdGhlIGN1cnJlbnQgdGFyZ2V0LlxuICovXG5mdW5jdGlvbiBvbldNTFRyaWdnZXJlZChldjogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZXYuY3VycmVudFRhcmdldCBhcyBFbGVtZW50O1xuXG4gICAgZnVuY3Rpb24gcnVuRWZmZWN0KGNob2ljZSA9IFwiWWVzXCIpIHtcbiAgICAgICAgaWYgKGNob2ljZSAhPT0gXCJZZXNcIilcbiAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICBjb25zdCBldmVudFR5cGUgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWV2ZW50JykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWV2ZW50Jyk7XG4gICAgICAgIGNvbnN0IHRhcmdldFdpbmRvdyA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdGFyZ2V0LXdpbmRvdycpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10YXJnZXQtd2luZG93JykgfHwgXCJcIjtcbiAgICAgICAgY29uc3Qgd2luZG93TWV0aG9kID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC13aW5kb3cnKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtd2luZG93Jyk7XG4gICAgICAgIGNvbnN0IHVybCA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtb3BlbnVybCcpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1vcGVudXJsJyk7XG5cbiAgICAgICAgaWYgKGV2ZW50VHlwZSAhPT0gbnVsbClcbiAgICAgICAgICAgIHNlbmRFdmVudChldmVudFR5cGUpO1xuICAgICAgICBpZiAod2luZG93TWV0aG9kICE9PSBudWxsKVxuICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh0YXJnZXRXaW5kb3csIHdpbmRvd01ldGhvZCk7XG4gICAgICAgIGlmICh1cmwgIT09IG51bGwpXG4gICAgICAgICAgICB2b2lkIE9wZW5VUkwodXJsKTtcbiAgICB9XG5cbiAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1jb25maXJtJykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWNvbmZpcm0nKTtcblxuICAgIGlmIChjb25maXJtKSB7XG4gICAgICAgIFF1ZXN0aW9uKHtcbiAgICAgICAgICAgIFRpdGxlOiBcIkNvbmZpcm1cIixcbiAgICAgICAgICAgIE1lc3NhZ2U6IGNvbmZpcm0sXG4gICAgICAgICAgICBEZXRhY2hlZDogZmFsc2UsXG4gICAgICAgICAgICBCdXR0b25zOiBbXG4gICAgICAgICAgICAgICAgeyBMYWJlbDogXCJZZXNcIiB9LFxuICAgICAgICAgICAgICAgIHsgTGFiZWw6IFwiTm9cIiwgSXNEZWZhdWx0OiB0cnVlIH1cbiAgICAgICAgICAgIF1cbiAgICAgICAgfSkudGhlbihydW5FZmZlY3QpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJ1bkVmZmVjdCgpO1xuICAgIH1cbn1cblxuLy8gUHJpdmF0ZSBmaWVsZCBuYW1lcy5cbmNvbnN0IGNvbnRyb2xsZXJTeW0gPSBTeW1ib2woXCJjb250cm9sbGVyXCIpO1xuY29uc3QgdHJpZ2dlck1hcFN5bSA9IFN5bWJvbChcInRyaWdnZXJNYXBcIik7XG5jb25zdCBlbGVtZW50Q291bnRTeW0gPSBTeW1ib2woXCJlbGVtZW50Q291bnRcIik7XG5cbi8qKlxuICogQWJvcnRDb250cm9sbGVyUmVnaXN0cnkgZG9lcyBub3QgYWN0dWFsbHkgcmVtZW1iZXIgYWN0aXZlIGV2ZW50IGxpc3RlbmVyczogaW5zdGVhZFxuICogaXQgdGllcyB0aGVtIHRvIGFuIEFib3J0U2lnbmFsIGFuZCB1c2VzIGFuIEFib3J0Q29udHJvbGxlciB0byByZW1vdmUgdGhlbSBhbGwgYXQgb25jZS5cbiAqL1xuY2xhc3MgQWJvcnRDb250cm9sbGVyUmVnaXN0cnkge1xuICAgIC8vIFByaXZhdGUgZmllbGRzLlxuICAgIFtjb250cm9sbGVyU3ltXTogQWJvcnRDb250cm9sbGVyO1xuXG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXNbY29udHJvbGxlclN5bV0gPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhbiBvcHRpb25zIG9iamVjdCBmb3IgYWRkRXZlbnRMaXN0ZW5lciB0aGF0IHRpZXMgdGhlIGxpc3RlbmVyXG4gICAgICogdG8gdGhlIEFib3J0U2lnbmFsIGZyb20gdGhlIGN1cnJlbnQgQWJvcnRDb250cm9sbGVyLlxuICAgICAqXG4gICAgICogQHBhcmFtIGVsZW1lbnQgLSBBbiBIVE1MIGVsZW1lbnRcbiAgICAgKiBAcGFyYW0gdHJpZ2dlcnMgLSBUaGUgbGlzdCBvZiBhY3RpdmUgV01MIHRyaWdnZXIgZXZlbnRzIGZvciB0aGUgc3BlY2lmaWVkIGVsZW1lbnRzXG4gICAgICovXG4gICAgc2V0KGVsZW1lbnQ6IEVsZW1lbnQsIHRyaWdnZXJzOiBzdHJpbmdbXSk6IEFkZEV2ZW50TGlzdGVuZXJPcHRpb25zIHtcbiAgICAgICAgcmV0dXJuIHsgc2lnbmFsOiB0aGlzW2NvbnRyb2xsZXJTeW1dLnNpZ25hbCB9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgYWxsIHJlZ2lzdGVyZWQgZXZlbnQgbGlzdGVuZXJzIGFuZCByZXNldHMgdGhlIHJlZ2lzdHJ5LlxuICAgICAqL1xuICAgIHJlc2V0KCk6IHZvaWQge1xuICAgICAgICB0aGlzW2NvbnRyb2xsZXJTeW1dLmFib3J0KCk7XG4gICAgICAgIHRoaXNbY29udHJvbGxlclN5bV0gPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgfVxufVxuXG4vKipcbiAqIFdlYWtNYXBSZWdpc3RyeSBtYXBzIGFjdGl2ZSB0cmlnZ2VyIGV2ZW50cyB0byBlYWNoIERPTSBlbGVtZW50IHRocm91Z2ggYSBXZWFrTWFwLlxuICogVGhpcyBlbnN1cmVzIHRoYXQgdGhlIG1hcHBpbmcgcmVtYWlucyBwcml2YXRlIHRvIHRoaXMgbW9kdWxlLCB3aGlsZSBzdGlsbCBhbGxvd2luZyBnYXJiYWdlXG4gKiBjb2xsZWN0aW9uIG9mIHRoZSBpbnZvbHZlZCBlbGVtZW50cy5cbiAqL1xuY2xhc3MgV2Vha01hcFJlZ2lzdHJ5IHtcbiAgICAvKiogU3RvcmVzIHRoZSBjdXJyZW50IGVsZW1lbnQtdG8tdHJpZ2dlciBtYXBwaW5nLiAqL1xuICAgIFt0cmlnZ2VyTWFwU3ltXTogV2Vha01hcDxFbGVtZW50LCBzdHJpbmdbXT47XG4gICAgLyoqIENvdW50cyB0aGUgbnVtYmVyIG9mIGVsZW1lbnRzIHdpdGggYWN0aXZlIFdNTCB0cmlnZ2Vycy4gKi9cbiAgICBbZWxlbWVudENvdW50U3ltXTogbnVtYmVyO1xuXG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXNbdHJpZ2dlck1hcFN5bV0gPSBuZXcgV2Vha01hcCgpO1xuICAgICAgICB0aGlzW2VsZW1lbnRDb3VudFN5bV0gPSAwO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgYWN0aXZlIHRyaWdnZXJzIGZvciB0aGUgc3BlY2lmaWVkIGVsZW1lbnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZWxlbWVudCAtIEFuIEhUTUwgZWxlbWVudFxuICAgICAqIEBwYXJhbSB0cmlnZ2VycyAtIFRoZSBsaXN0IG9mIGFjdGl2ZSBXTUwgdHJpZ2dlciBldmVudHMgZm9yIHRoZSBzcGVjaWZpZWQgZWxlbWVudFxuICAgICAqL1xuICAgIHNldChlbGVtZW50OiBFbGVtZW50LCB0cmlnZ2Vyczogc3RyaW5nW10pOiBBZGRFdmVudExpc3RlbmVyT3B0aW9ucyB7XG4gICAgICAgIGlmICghdGhpc1t0cmlnZ2VyTWFwU3ltXS5oYXMoZWxlbWVudCkpIHsgdGhpc1tlbGVtZW50Q291bnRTeW1dKys7IH1cbiAgICAgICAgdGhpc1t0cmlnZ2VyTWFwU3ltXS5zZXQoZWxlbWVudCwgdHJpZ2dlcnMpO1xuICAgICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlcyBhbGwgcmVnaXN0ZXJlZCBldmVudCBsaXN0ZW5lcnMuXG4gICAgICovXG4gICAgcmVzZXQoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzW2VsZW1lbnRDb3VudFN5bV0gPD0gMClcbiAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICBmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuYm9keS5xdWVyeVNlbGVjdG9yQWxsKCcqJykpIHtcbiAgICAgICAgICAgIGlmICh0aGlzW2VsZW1lbnRDb3VudFN5bV0gPD0gMClcbiAgICAgICAgICAgICAgICBicmVhaztcblxuICAgICAgICAgICAgY29uc3QgdHJpZ2dlcnMgPSB0aGlzW3RyaWdnZXJNYXBTeW1dLmdldChlbGVtZW50KTtcbiAgICAgICAgICAgIGlmICh0cmlnZ2VycyAhPSBudWxsKSB7IHRoaXNbZWxlbWVudENvdW50U3ltXS0tOyB9XG5cbiAgICAgICAgICAgIGZvciAoY29uc3QgdHJpZ2dlciBvZiB0cmlnZ2VycyB8fCBbXSlcbiAgICAgICAgICAgICAgICBlbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodHJpZ2dlciwgb25XTUxUcmlnZ2VyZWQpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpc1t0cmlnZ2VyTWFwU3ltXSA9IG5ldyBXZWFrTWFwKCk7XG4gICAgICAgIHRoaXNbZWxlbWVudENvdW50U3ltXSA9IDA7XG4gICAgfVxufVxuXG5jb25zdCB0cmlnZ2VyUmVnaXN0cnkgPSBjYW5BYm9ydExpc3RlbmVycygpID8gbmV3IEFib3J0Q29udHJvbGxlclJlZ2lzdHJ5KCkgOiBuZXcgV2Vha01hcFJlZ2lzdHJ5KCk7XG5cbi8qKlxuICogQWRkcyBldmVudCBsaXN0ZW5lcnMgdG8gdGhlIHNwZWNpZmllZCBlbGVtZW50LlxuICovXG5mdW5jdGlvbiBhZGRXTUxMaXN0ZW5lcnMoZWxlbWVudDogRWxlbWVudCk6IHZvaWQge1xuICAgIGNvbnN0IHRyaWdnZXJSZWdFeHAgPSAvXFxTKy9nO1xuICAgIGNvbnN0IHRyaWdnZXJBdHRyID0gKGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdHJpZ2dlcicpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiKTtcbiAgICBjb25zdCB0cmlnZ2Vyczogc3RyaW5nW10gPSBbXTtcblxuICAgIGxldCBtYXRjaDtcbiAgICB3aGlsZSAoKG1hdGNoID0gdHJpZ2dlclJlZ0V4cC5leGVjKHRyaWdnZXJBdHRyKSkgIT09IG51bGwpXG4gICAgICAgIHRyaWdnZXJzLnB1c2gobWF0Y2hbMF0pO1xuXG4gICAgY29uc3Qgb3B0aW9ucyA9IHRyaWdnZXJSZWdpc3RyeS5zZXQoZWxlbWVudCwgdHJpZ2dlcnMpO1xuICAgIGZvciAoY29uc3QgdHJpZ2dlciBvZiB0cmlnZ2VycylcbiAgICAgICAgZWxlbWVudC5hZGRFdmVudExpc3RlbmVyKHRyaWdnZXIsIG9uV01MVHJpZ2dlcmVkLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTY2hlZHVsZXMgYW4gYXV0b21hdGljIHJlbG9hZCBvZiBXTUwgdG8gYmUgcGVyZm9ybWVkIGFzIHNvb24gYXMgdGhlIGRvY3VtZW50IGlzIGZ1bGx5IGxvYWRlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEVuYWJsZSgpOiB2b2lkIHtcbiAgICB3aGVuUmVhZHkoUmVsb2FkKTtcbn1cblxuLyoqXG4gKiBSZWxvYWRzIHRoZSBXTUwgcGFnZSBieSBhZGRpbmcgbmVjZXNzYXJ5IGV2ZW50IGxpc3RlbmVycyBhbmQgYnJvd3NlciBsaXN0ZW5lcnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZWxvYWQoKTogdm9pZCB7XG4gICAgdHJpZ2dlclJlZ2lzdHJ5LnJlc2V0KCk7XG4gICAgZG9jdW1lbnQuYm9keS5xdWVyeVNlbGVjdG9yQWxsKCdbd21sLWV2ZW50XSwgW3dtbC13aW5kb3ddLCBbd21sLW9wZW51cmxdLCBbZGF0YS13bWwtZXZlbnRdLCBbZGF0YS13bWwtd2luZG93XSwgW2RhdGEtd21sLW9wZW51cmxdJykuZm9yRWFjaChhZGRXTUxMaXN0ZW5lcnMpO1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lcyB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQnJvd3Nlcik7XG5cbmNvbnN0IEJyb3dzZXJPcGVuVVJMID0gMDtcblxuLyoqXG4gKiBPcGVuIGEgYnJvd3NlciB3aW5kb3cgdG8gdGhlIGdpdmVuIFVSTC5cbiAqXG4gKiBAcGFyYW0gdXJsIC0gVGhlIFVSTCB0byBvcGVuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPcGVuVVJMKHVybDogc3RyaW5nIHwgVVJMKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoQnJvd3Nlck9wZW5VUkwsIHt1cmw6IHVybC50b1N0cmluZygpfSk7XG59XG4iLCAiLy8gU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vYWkvbmFub2lkXG5cbi8vIFRoZSBNSVQgTGljZW5zZSAoTUlUKVxuLy9cbi8vIENvcHlyaWdodCAyMDE3IEFuZHJleSBTaXRuaWsgPGFuZHJleUBzaXRuaWsucnU+XG4vL1xuLy8gUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weSBvZlxuLy8gdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgXCJTb2Z0d2FyZVwiKSwgdG8gZGVhbCBpblxuLy8gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cyB0b1xuLy8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2Zcbi8vIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25zIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzIGZ1cm5pc2hlZCB0byBkbyBzbyxcbi8vICAgICBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczpcbi8vXG4vLyAgICAgVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsXG4vLyBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vICAgICBUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELCBFWFBSRVNTIE9SXG4vLyBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTU1xuLy8gRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SXG4vLyBDT1BZUklHSFQgSE9MREVSUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVJcbi8vIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOXG4vLyBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG4vLyBUaGlzIGFscGhhYmV0IHVzZXMgYEEtWmEtejAtOV8tYCBzeW1ib2xzLlxuLy8gVGhlIG9yZGVyIG9mIGNoYXJhY3RlcnMgaXMgb3B0aW1pemVkIGZvciBiZXR0ZXIgZ3ppcCBhbmQgYnJvdGxpIGNvbXByZXNzaW9uLlxuLy8gUmVmZXJlbmNlcyB0byB0aGUgc2FtZSBmaWxlICh3b3JrcyBib3RoIGZvciBnemlwIGFuZCBicm90bGkpOlxuLy8gYCd1c2VgLCBgYW5kb21gLCBhbmQgYHJpY3QnYFxuLy8gUmVmZXJlbmNlcyB0byB0aGUgYnJvdGxpIGRlZmF1bHQgZGljdGlvbmFyeTpcbi8vIGAtMjZUYCwgYDE5ODNgLCBgNDBweGAsIGA3NXB4YCwgYGJ1c2hgLCBgamFja2AsIGBtaW5kYCwgYHZlcnlgLCBhbmQgYHdvbGZgXG5jb25zdCB1cmxBbHBoYWJldCA9XG4gICAgJ3VzZWFuZG9tLTI2VDE5ODM0MFBYNzVweEpBQ0tWRVJZTUlOREJVU0hXT0xGX0dRWmJmZ2hqa2xxdnd5enJpY3QnXG5cbmV4cG9ydCBmdW5jdGlvbiBuYW5vaWQoc2l6ZTogbnVtYmVyID0gMjEpOiBzdHJpbmcge1xuICAgIGxldCBpZCA9ICcnXG4gICAgLy8gQSBjb21wYWN0IGFsdGVybmF0aXZlIGZvciBgZm9yICh2YXIgaSA9IDA7IGkgPCBzdGVwOyBpKyspYC5cbiAgICBsZXQgaSA9IHNpemUgfCAwXG4gICAgd2hpbGUgKGktLSkge1xuICAgICAgICAvLyBgfCAwYCBpcyBtb3JlIGNvbXBhY3QgYW5kIGZhc3RlciB0aGFuIGBNYXRoLmZsb29yKClgLlxuICAgICAgICBpZCArPSB1cmxBbHBoYWJldFsoTWF0aC5yYW5kb20oKSAqIDY0KSB8IDBdXG4gICAgfVxuICAgIHJldHVybiBpZFxufVxuIiwgIi8qXG4gXyAgICAgX18gICAgIF8gX19cbnwgfCAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuYW5vaWQgfSBmcm9tIFwiLi9uYW5vaWQuanNcIjtcblxuY29uc3QgcnVudGltZVVSTCA9IHdpbmRvdy5sb2NhdGlvbi5vcmlnaW4gKyBcIi93YWlscy9ydW50aW1lXCI7XG5cbi8vIFJlLWV4cG9ydCBuYW5vaWQgZm9yIGN1c3RvbSB0cmFuc3BvcnQgaW1wbGVtZW50YXRpb25zXG5leHBvcnQgeyBuYW5vaWQgfTtcblxuLy8gT2JqZWN0IE5hbWVzXG5leHBvcnQgY29uc3Qgb2JqZWN0TmFtZXMgPSBPYmplY3QuZnJlZXplKHtcbiAgICBDYWxsOiAwLFxuICAgIENsaXBib2FyZDogMSxcbiAgICBBcHBsaWNhdGlvbjogMixcbiAgICBFdmVudHM6IDMsXG4gICAgQ29udGV4dE1lbnU6IDQsXG4gICAgRGlhbG9nOiA1LFxuICAgIFdpbmRvdzogNixcbiAgICBTY3JlZW5zOiA3LFxuICAgIFN5c3RlbTogOCxcbiAgICBCcm93c2VyOiA5LFxuICAgIENhbmNlbENhbGw6IDEwLFxuICAgIElPUzogMTEsXG59KTtcbmV4cG9ydCBsZXQgY2xpZW50SWQgPSBuYW5vaWQoKTtcblxuLyoqXG4gKiBSdW50aW1lVHJhbnNwb3J0IGRlZmluZXMgdGhlIGludGVyZmFjZSBmb3IgY3VzdG9tIElQQyB0cmFuc3BvcnQgaW1wbGVtZW50YXRpb25zLlxuICogSW1wbGVtZW50IHRoaXMgaW50ZXJmYWNlIHRvIHVzZSBXZWJTb2NrZXRzLCBjdXN0b20gcHJvdG9jb2xzLCBvciBhbnkgb3RoZXJcbiAqIHRyYW5zcG9ydCBtZWNoYW5pc20gaW5zdGVhZCBvZiB0aGUgZGVmYXVsdCBIVFRQIGZldGNoLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJ1bnRpbWVUcmFuc3BvcnQge1xuICAgIC8qKlxuICAgICAqIFNlbmQgYSBydW50aW1lIGNhbGwgYW5kIHJldHVybiB0aGUgcmVzcG9uc2UuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gb2JqZWN0SUQgLSBUaGUgV2FpbHMgb2JqZWN0IElEICgwPUNhbGwsIDE9Q2xpcGJvYXJkLCBldGMuKVxuICAgICAqIEBwYXJhbSBtZXRob2QgLSBUaGUgbWV0aG9kIElEIHRvIGNhbGxcbiAgICAgKiBAcGFyYW0gd2luZG93TmFtZSAtIE9wdGlvbmFsIHdpbmRvdyBuYW1lXG4gICAgICogQHBhcmFtIGFyZ3MgLSBBcmd1bWVudHMgdG8gcGFzcyAod2lsbCBiZSBKU09OIHN0cmluZ2lmaWVkIGlmIHByZXNlbnQpXG4gICAgICogQHJldHVybnMgUHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIHJlc3BvbnNlIGRhdGFcbiAgICAgKi9cbiAgICBjYWxsKG9iamVjdElEOiBudW1iZXIsIG1ldGhvZDogbnVtYmVyLCB3aW5kb3dOYW1lOiBzdHJpbmcsIGFyZ3M6IGFueSk6IFByb21pc2U8YW55Pjtcbn1cblxuLyoqXG4gKiBDdXN0b20gdHJhbnNwb3J0IGltcGxlbWVudGF0aW9uIChjYW4gYmUgc2V0IGJ5IHVzZXIpXG4gKi9cbmxldCBjdXN0b21UcmFuc3BvcnQ6IFJ1bnRpbWVUcmFuc3BvcnQgfCBudWxsID0gbnVsbDtcblxuLyoqXG4gKiBTZXQgYSBjdXN0b20gdHJhbnNwb3J0IGZvciBhbGwgV2FpbHMgcnVudGltZSBjYWxscy5cbiAqIFRoaXMgYWxsb3dzIHlvdSB0byByZXBsYWNlIHRoZSBkZWZhdWx0IEhUVFAgZmV0Y2ggdHJhbnNwb3J0IHdpdGhcbiAqIFdlYlNvY2tldHMsIGN1c3RvbSBwcm90b2NvbHMsIG9yIGFueSBvdGhlciBtZWNoYW5pc20uXG4gKlxuICogQHBhcmFtIHRyYW5zcG9ydCAtIFlvdXIgY3VzdG9tIHRyYW5zcG9ydCBpbXBsZW1lbnRhdGlvblxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBpbXBvcnQgeyBzZXRUcmFuc3BvcnQgfSBmcm9tICcvd2FpbHMvcnVudGltZS5qcyc7XG4gKlxuICogY29uc3Qgd3NUcmFuc3BvcnQgPSB7XG4gKiAgIGNhbGw6IGFzeW5jIChvYmplY3RJRCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKSA9PiB7XG4gKiAgICAgLy8gWW91ciBXZWJTb2NrZXQgaW1wbGVtZW50YXRpb25cbiAqICAgfVxuICogfTtcbiAqXG4gKiBzZXRUcmFuc3BvcnQod3NUcmFuc3BvcnQpO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZXRUcmFuc3BvcnQodHJhbnNwb3J0OiBSdW50aW1lVHJhbnNwb3J0IHwgbnVsbCk6IHZvaWQge1xuICAgIGN1c3RvbVRyYW5zcG9ydCA9IHRyYW5zcG9ydDtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIGN1cnJlbnQgdHJhbnNwb3J0ICh1c2VmdWwgZm9yIGV4dGVuZGluZy93cmFwcGluZylcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFRyYW5zcG9ydCgpOiBSdW50aW1lVHJhbnNwb3J0IHwgbnVsbCB7XG4gICAgcmV0dXJuIGN1c3RvbVRyYW5zcG9ydDtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgbmV3IHJ1bnRpbWUgY2FsbGVyIHdpdGggc3BlY2lmaWVkIElELlxuICpcbiAqIEBwYXJhbSBvYmplY3QgLSBUaGUgb2JqZWN0IHRvIGludm9rZSB0aGUgbWV0aG9kIG9uLlxuICogQHBhcmFtIHdpbmRvd05hbWUgLSBUaGUgbmFtZSBvZiB0aGUgd2luZG93LlxuICogQHJldHVybiBUaGUgbmV3IHJ1bnRpbWUgY2FsbGVyIGZ1bmN0aW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbmV3UnVudGltZUNhbGxlcihvYmplY3Q6IG51bWJlciwgd2luZG93TmFtZTogc3RyaW5nID0gJycpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZDogbnVtYmVyLCBhcmdzOiBhbnkgPSBudWxsKSB7XG4gICAgICAgIHJldHVybiBydW50aW1lQ2FsbFdpdGhJRChvYmplY3QsIG1ldGhvZCwgd2luZG93TmFtZSwgYXJncyk7XG4gICAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcnVudGltZUNhbGxXaXRoSUQob2JqZWN0SUQ6IG51bWJlciwgbWV0aG9kOiBudW1iZXIsIHdpbmRvd05hbWU6IHN0cmluZywgYXJnczogYW55KTogUHJvbWlzZTxhbnk+IHtcbiAgICAvLyBVc2UgY3VzdG9tIHRyYW5zcG9ydCBpZiBhdmFpbGFibGVcbiAgICBpZiAoY3VzdG9tVHJhbnNwb3J0KSB7XG4gICAgICAgIHJldHVybiBjdXN0b21UcmFuc3BvcnQuY2FsbChvYmplY3RJRCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcbiAgICB9XG5cbiAgICAvLyBEZWZhdWx0IEhUVFAgZmV0Y2ggdHJhbnNwb3J0XG4gICAgbGV0IHVybCA9IG5ldyBVUkwocnVudGltZVVSTCk7XG5cbiAgICBsZXQgYm9keTogeyBvYmplY3Q6IG51bWJlcjsgbWV0aG9kOiBudW1iZXIsIGFyZ3M/OiBhbnkgfSA9IHtcbiAgICAgIG9iamVjdDogb2JqZWN0SUQsXG4gICAgICBtZXRob2RcbiAgICB9XG4gICAgaWYgKGFyZ3MgIT09IG51bGwgJiYgYXJncyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBib2R5LmFyZ3MgPSBhcmdzO1xuICAgIH1cblxuICAgIGxldCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgICAgICBbXCJ4LXdhaWxzLWNsaWVudC1pZFwiXTogY2xpZW50SWQsXG4gICAgICAgIFtcIkNvbnRlbnQtVHlwZVwiXTogXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgICB9XG4gICAgaWYgKHdpbmRvd05hbWUpIHtcbiAgICAgICAgaGVhZGVyc1tcIngtd2FpbHMtd2luZG93LW5hbWVcIl0gPSB3aW5kb3dOYW1lO1xuICAgIH1cblxuICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCwge1xuICAgICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgICBoZWFkZXJzLFxuICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoYm9keSlcbiAgICB9KTtcbiAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihhd2FpdCByZXNwb25zZS50ZXh0KCkpO1xuICAgIH1cblxuICAgIGlmICgocmVzcG9uc2UuaGVhZGVycy5nZXQoXCJDb250ZW50LVR5cGVcIik/LmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpID8/IC0xKSAhPT0gLTEpIHtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gcmVzcG9uc2UudGV4dCgpO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG4vLyBzZXR1cFxud2luZG93Ll93YWlscyA9IHdpbmRvdy5fd2FpbHMgfHwge307XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkRpYWxvZyk7XG5cbi8vIERlZmluZSBjb25zdGFudHMgZnJvbSB0aGUgYG1ldGhvZHNgIG9iamVjdCBpbiBUaXRsZSBDYXNlXG5jb25zdCBEaWFsb2dJbmZvID0gMDtcbmNvbnN0IERpYWxvZ1dhcm5pbmcgPSAxO1xuY29uc3QgRGlhbG9nRXJyb3IgPSAyO1xuY29uc3QgRGlhbG9nUXVlc3Rpb24gPSAzO1xuY29uc3QgRGlhbG9nT3BlbkZpbGUgPSA0O1xuY29uc3QgRGlhbG9nU2F2ZUZpbGUgPSA1O1xuXG5leHBvcnQgaW50ZXJmYWNlIE9wZW5GaWxlRGlhbG9nT3B0aW9ucyB7XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY2hvc2VuLiAqL1xuICAgIENhbkNob29zZURpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGZpbGVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZGlyZWN0b3JpZXMgY2FuIGJlIGNyZWF0ZWQuICovXG4gICAgQ2FuQ3JlYXRlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGZpbGVzIHNob3VsZCBiZSBzaG93bi4gKi9cbiAgICBTaG93SGlkZGVuRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgYWxpYXNlcyBzaG91bGQgYmUgcmVzb2x2ZWQuICovXG4gICAgUmVzb2x2ZXNBbGlhc2VzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG11bHRpcGxlIHNlbGVjdGlvbiBpcyBhbGxvd2VkLiAqL1xuICAgIEFsbG93c011bHRpcGxlU2VsZWN0aW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBleHRlbnNpb24gc2hvdWxkIGJlIGhpZGRlbi4gKi9cbiAgICBIaWRlRXh0ZW5zaW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGhpZGRlbiBleHRlbnNpb25zIGNhbiBiZSBzZWxlY3RlZC4gKi9cbiAgICBDYW5TZWxlY3RIaWRkZW5FeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZSBwYWNrYWdlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBkaXJlY3Rvcmllcy4gKi9cbiAgICBUcmVhdHNGaWxlUGFja2FnZXNBc0RpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG90aGVyIGZpbGUgdHlwZXMgYXJlIGFsbG93ZWQuICovXG4gICAgQWxsb3dzT3RoZXJGaWxldHlwZXM/OiBib29sZWFuO1xuICAgIC8qKiBBcnJheSBvZiBmaWxlIGZpbHRlcnMuICovXG4gICAgRmlsdGVycz86IEZpbGVGaWx0ZXJbXTtcbiAgICAvKiogVGl0bGUgb2YgdGhlIGRpYWxvZy4gKi9cbiAgICBUaXRsZT86IHN0cmluZztcbiAgICAvKiogTWVzc2FnZSB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgTWVzc2FnZT86IHN0cmluZztcbiAgICAvKiogVGV4dCB0byBkaXNwbGF5IG9uIHRoZSBidXR0b24uICovXG4gICAgQnV0dG9uVGV4dD86IHN0cmluZztcbiAgICAvKiogRGlyZWN0b3J5IHRvIG9wZW4gaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBEaXJlY3Rvcnk/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiB0aGUgZGlhbG9nIHNob3VsZCBhcHBlYXIgZGV0YWNoZWQgZnJvbSB0aGUgbWFpbiB3aW5kb3cuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFNhdmVGaWxlRGlhbG9nT3B0aW9ucyB7XG4gICAgLyoqIERlZmF1bHQgZmlsZW5hbWUgdG8gdXNlIGluIHRoZSBkaWFsb2cuICovXG4gICAgRmlsZW5hbWU/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY2hvc2VuLiAqL1xuICAgIENhbkNob29zZURpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGZpbGVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZGlyZWN0b3JpZXMgY2FuIGJlIGNyZWF0ZWQuICovXG4gICAgQ2FuQ3JlYXRlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGZpbGVzIHNob3VsZCBiZSBzaG93bi4gKi9cbiAgICBTaG93SGlkZGVuRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgYWxpYXNlcyBzaG91bGQgYmUgcmVzb2x2ZWQuICovXG4gICAgUmVzb2x2ZXNBbGlhc2VzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBleHRlbnNpb24gc2hvdWxkIGJlIGhpZGRlbi4gKi9cbiAgICBIaWRlRXh0ZW5zaW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGhpZGRlbiBleHRlbnNpb25zIGNhbiBiZSBzZWxlY3RlZC4gKi9cbiAgICBDYW5TZWxlY3RIaWRkZW5FeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZSBwYWNrYWdlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBkaXJlY3Rvcmllcy4gKi9cbiAgICBUcmVhdHNGaWxlUGFja2FnZXNBc0RpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG90aGVyIGZpbGUgdHlwZXMgYXJlIGFsbG93ZWQuICovXG4gICAgQWxsb3dzT3RoZXJGaWxldHlwZXM/OiBib29sZWFuO1xuICAgIC8qKiBBcnJheSBvZiBmaWxlIGZpbHRlcnMuICovXG4gICAgRmlsdGVycz86IEZpbGVGaWx0ZXJbXTtcbiAgICAvKiogVGl0bGUgb2YgdGhlIGRpYWxvZy4gKi9cbiAgICBUaXRsZT86IHN0cmluZztcbiAgICAvKiogTWVzc2FnZSB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgTWVzc2FnZT86IHN0cmluZztcbiAgICAvKiogVGV4dCB0byBkaXNwbGF5IG9uIHRoZSBidXR0b24uICovXG4gICAgQnV0dG9uVGV4dD86IHN0cmluZztcbiAgICAvKiogRGlyZWN0b3J5IHRvIG9wZW4gaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBEaXJlY3Rvcnk/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiB0aGUgZGlhbG9nIHNob3VsZCBhcHBlYXIgZGV0YWNoZWQgZnJvbSB0aGUgbWFpbiB3aW5kb3cuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE1lc3NhZ2VEaWFsb2dPcHRpb25zIHtcbiAgICAvKiogVGhlIHRpdGxlIG9mIHRoZSBkaWFsb2cgd2luZG93LiAqL1xuICAgIFRpdGxlPzogc3RyaW5nO1xuICAgIC8qKiBUaGUgbWFpbiBtZXNzYWdlIHRvIHNob3cgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBNZXNzYWdlPzogc3RyaW5nO1xuICAgIC8qKiBBcnJheSBvZiBidXR0b24gb3B0aW9ucyB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgQnV0dG9ucz86IEJ1dHRvbltdO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBkaWFsb2cgc2hvdWxkIGFwcGVhciBkZXRhY2hlZCBmcm9tIHRoZSBtYWluIHdpbmRvdyAoaWYgYXBwbGljYWJsZSkuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEJ1dHRvbiB7XG4gICAgLyoqIFRleHQgdGhhdCBhcHBlYXJzIHdpdGhpbiB0aGUgYnV0dG9uLiAqL1xuICAgIExhYmVsPzogc3RyaW5nO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBidXR0b24gc2hvdWxkIGNhbmNlbCBhbiBvcGVyYXRpb24gd2hlbiBjbGlja2VkLiAqL1xuICAgIElzQ2FuY2VsPzogYm9vbGVhbjtcbiAgICAvKiogVHJ1ZSBpZiB0aGUgYnV0dG9uIHNob3VsZCBiZSB0aGUgZGVmYXVsdCBhY3Rpb24gd2hlbiB0aGUgdXNlciBwcmVzc2VzIGVudGVyLiAqL1xuICAgIElzRGVmYXVsdD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmlsZUZpbHRlciB7XG4gICAgLyoqIERpc3BsYXkgbmFtZSBmb3IgdGhlIGZpbHRlciwgaXQgY291bGQgYmUgXCJUZXh0IEZpbGVzXCIsIFwiSW1hZ2VzXCIgZXRjLiAqL1xuICAgIERpc3BsYXlOYW1lPzogc3RyaW5nO1xuICAgIC8qKiBQYXR0ZXJuIHRvIG1hdGNoIGZvciB0aGUgZmlsdGVyLCBlLmcuIFwiKi50eHQ7Ki5tZFwiIGZvciB0ZXh0IG1hcmtkb3duIGZpbGVzLiAqL1xuICAgIFBhdHRlcm4/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogUHJlc2VudHMgYSBkaWFsb2cgb2Ygc3BlY2lmaWVkIHR5cGUgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cbiAqXG4gKiBAcGFyYW0gdHlwZSAtIERpYWxvZyB0eXBlLlxuICogQHBhcmFtIG9wdGlvbnMgLSBPcHRpb25zIGZvciB0aGUgZGlhbG9nLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCByZXN1bHQgb2YgZGlhbG9nLlxuICovXG5mdW5jdGlvbiBkaWFsb2codHlwZTogbnVtYmVyLCBvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyB8IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyB8IFNhdmVGaWxlRGlhbG9nT3B0aW9ucyA9IHt9KTogUHJvbWlzZTxhbnk+IHtcbiAgICByZXR1cm4gY2FsbCh0eXBlLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBQcmVzZW50cyBhbiBpbmZvIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBsYWJlbCBvZiB0aGUgY2hvc2VuIGJ1dHRvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEluZm8ob3B0aW9uczogTWVzc2FnZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ0luZm8sIG9wdGlvbnMpOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSB3YXJuaW5nIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgbGFiZWwgb2YgdGhlIGNob3NlbiBidXR0b24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXYXJuaW5nKG9wdGlvbnM6IE1lc3NhZ2VEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dXYXJuaW5nLCBvcHRpb25zKTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGFuIGVycm9yIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgbGFiZWwgb2YgdGhlIGNob3NlbiBidXR0b24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFcnJvcihvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nRXJyb3IsIG9wdGlvbnMpOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSBxdWVzdGlvbiBkaWFsb2cuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9ucy5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGxhYmVsIG9mIHRoZSBjaG9zZW4gYnV0dG9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gUXVlc3Rpb24ob3B0aW9uczogTWVzc2FnZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ1F1ZXN0aW9uLCBvcHRpb25zKTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGEgZmlsZSBzZWxlY3Rpb24gZGlhbG9nIHRvIHBpY2sgb25lIG9yIG1vcmUgZmlsZXMgdG8gb3Blbi5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgU2VsZWN0ZWQgZmlsZSBvciBsaXN0IG9mIGZpbGVzLCBvciBhIGJsYW5rIHN0cmluZy9lbXB0eSBsaXN0IGlmIG5vIGZpbGUgaGFzIGJlZW4gc2VsZWN0ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMgJiB7IEFsbG93c011bHRpcGxlU2VsZWN0aW9uOiB0cnVlIH0pOiBQcm9taXNlPHN0cmluZ1tdPjtcbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMgJiB7IEFsbG93c011bHRpcGxlU2VsZWN0aW9uPzogZmFsc2UgfCB1bmRlZmluZWQgfSk6IFByb21pc2U8c3RyaW5nPjtcbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZyB8IHN0cmluZ1tdPjtcbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZyB8IHN0cmluZ1tdPiB7IHJldHVybiBkaWFsb2coRGlhbG9nT3BlbkZpbGUsIG9wdGlvbnMpID8/IFtdOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSBmaWxlIHNlbGVjdGlvbiBkaWFsb2cgdG8gcGljayBhIGZpbGUgdG8gc2F2ZS5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgU2VsZWN0ZWQgZmlsZSwgb3IgYSBibGFuayBzdHJpbmcgaWYgbm8gZmlsZSBoYXMgYmVlbiBzZWxlY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNhdmVGaWxlKG9wdGlvbnM6IFNhdmVGaWxlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nU2F2ZUZpbGUsIG9wdGlvbnMpOyB9XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgZXZlbnRMaXN0ZW5lcnMsIExpc3RlbmVyLCBsaXN0ZW5lck9mZiB9IGZyb20gXCIuL2xpc3RlbmVyLmpzXCI7XG5pbXBvcnQgeyBFdmVudHMgYXMgQ3JlYXRlIH0gZnJvbSBcIi4vY3JlYXRlLmpzXCI7XG5pbXBvcnQgeyBUeXBlcyB9IGZyb20gXCIuL2V2ZW50X3R5cGVzLmpzXCI7XG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcbndpbmRvdy5fd2FpbHMuZGlzcGF0Y2hXYWlsc0V2ZW50ID0gZGlzcGF0Y2hXYWlsc0V2ZW50O1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5FdmVudHMpO1xuY29uc3QgRW1pdE1ldGhvZCA9IDA7XG5cbmV4cG9ydCAqIGZyb20gXCIuL2V2ZW50X3R5cGVzLmpzXCI7XG5cbi8qKlxuICogQSB0YWJsZSBvZiBkYXRhIHR5cGVzIGZvciBhbGwga25vd24gZXZlbnRzLlxuICogV2lsbCBiZSBtb25rZXktcGF0Y2hlZCBieSB0aGUgYmluZGluZyBnZW5lcmF0b3IuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ3VzdG9tRXZlbnRzIHt9XG5cbi8qKlxuICogRWl0aGVyIGEga25vd24gZXZlbnQgbmFtZSBvciBhbiBhcmJpdHJhcnkgc3RyaW5nLlxuICovXG5leHBvcnQgdHlwZSBXYWlsc0V2ZW50TmFtZTxFIGV4dGVuZHMga2V5b2YgQ3VzdG9tRXZlbnRzID0ga2V5b2YgQ3VzdG9tRXZlbnRzPiA9IEUgfCAoc3RyaW5nICYge30pO1xuXG4vKipcbiAqIFVuaW9uIG9mIGFsbCBrbm93biBzeXN0ZW0gZXZlbnQgbmFtZXMuXG4gKi9cbnR5cGUgU3lzdGVtRXZlbnROYW1lID0ge1xuICAgIFtLIGluIGtleW9mICh0eXBlb2YgVHlwZXMpXTogKHR5cGVvZiBUeXBlcylbS11ba2V5b2YgKCh0eXBlb2YgVHlwZXMpW0tdKV1cbn0gZXh0ZW5kcyAoaW5mZXIgTSkgPyBNW2tleW9mIE1dIDogbmV2ZXI7XG5cbi8qKlxuICogVGhlIGRhdGEgdHlwZSBhc3NvY2lhdGVkIHRvIGEgZ2l2ZW4gZXZlbnQuXG4gKi9cbmV4cG9ydCB0eXBlIFdhaWxzRXZlbnREYXRhPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPiA9XG4gICAgRSBleHRlbmRzIGtleW9mIEN1c3RvbUV2ZW50cyA/IEN1c3RvbUV2ZW50c1tFXSA6IChFIGV4dGVuZHMgU3lzdGVtRXZlbnROYW1lID8gdm9pZCA6IGFueSk7XG5cbi8qKlxuICogVGhlIHR5cGUgb2YgaGFuZGxlcnMgZm9yIGEgZ2l2ZW4gZXZlbnQuXG4gKi9cbmV4cG9ydCB0eXBlIFdhaWxzRXZlbnRDYWxsYmFjazxFIGV4dGVuZHMgV2FpbHNFdmVudE5hbWUgPSBXYWlsc0V2ZW50TmFtZT4gPSAoZXY6IFdhaWxzRXZlbnQ8RT4pID0+IHZvaWQ7XG5cbi8qKlxuICogUmVwcmVzZW50cyBhIHN5c3RlbSBldmVudCBvciBhIGN1c3RvbSBldmVudCBlbWl0dGVkIHRocm91Z2ggd2FpbHMtcHJvdmlkZWQgZmFjaWxpdGllcy5cbiAqL1xuZXhwb3J0IGNsYXNzIFdhaWxzRXZlbnQ8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+IHtcbiAgICAvKipcbiAgICAgKiBUaGUgbmFtZSBvZiB0aGUgZXZlbnQuXG4gICAgICovXG4gICAgbmFtZTogRTtcblxuICAgIC8qKlxuICAgICAqIE9wdGlvbmFsIGRhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBlbWl0dGVkIGV2ZW50LlxuICAgICAqL1xuICAgIGRhdGE6IFdhaWxzRXZlbnREYXRhPEU+O1xuXG4gICAgLyoqXG4gICAgICogTmFtZSBvZiB0aGUgb3JpZ2luYXRpbmcgd2luZG93LiBPbWl0dGVkIGZvciBhcHBsaWNhdGlvbiBldmVudHMuXG4gICAgICogV2lsbCBiZSBvdmVycmlkZGVuIGlmIHNldCBtYW51YWxseS5cbiAgICAgKi9cbiAgICBzZW5kZXI/OiBzdHJpbmc7XG5cbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBFLCBkYXRhOiBXYWlsc0V2ZW50RGF0YTxFPik7XG4gICAgY29uc3RydWN0b3IobmFtZTogV2FpbHNFdmVudERhdGE8RT4gZXh0ZW5kcyBudWxsIHwgdm9pZCA/IEUgOiBuZXZlcilcbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBFLCBkYXRhPzogYW55KSB7XG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XG4gICAgICAgIHRoaXMuZGF0YSA9IGRhdGEgPz8gbnVsbDtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGRpc3BhdGNoV2FpbHNFdmVudChldmVudDogYW55KSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudC5uYW1lKTtcbiAgICBpZiAoIWxpc3RlbmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGV0IHdhaWxzRXZlbnQgPSBuZXcgV2FpbHNFdmVudChcbiAgICAgICAgZXZlbnQubmFtZSxcbiAgICAgICAgKGV2ZW50Lm5hbWUgaW4gQ3JlYXRlKSA/IENyZWF0ZVtldmVudC5uYW1lXShldmVudC5kYXRhKSA6IGV2ZW50LmRhdGFcbiAgICApO1xuICAgIGlmICgnc2VuZGVyJyBpbiBldmVudCkge1xuICAgICAgICB3YWlsc0V2ZW50LnNlbmRlciA9IGV2ZW50LnNlbmRlcjtcbiAgICB9XG5cbiAgICBsaXN0ZW5lcnMgPSBsaXN0ZW5lcnMuZmlsdGVyKGxpc3RlbmVyID0+ICFsaXN0ZW5lci5kaXNwYXRjaCh3YWlsc0V2ZW50KSk7XG4gICAgaWYgKGxpc3RlbmVycy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgZXZlbnRMaXN0ZW5lcnMuZGVsZXRlKGV2ZW50Lm5hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudC5uYW1lLCBsaXN0ZW5lcnMpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZWdpc3RlciBhIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBtdWx0aXBsZSB0aW1lcyBmb3IgYSBzcGVjaWZpYyBldmVudC5cbiAqXG4gKiBAcGFyYW0gZXZlbnROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIHJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmb3IuXG4gKiBAcGFyYW0gY2FsbGJhY2sgLSBUaGUgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIHdoZW4gdGhlIGV2ZW50IGlzIHRyaWdnZXJlZC5cbiAqIEBwYXJhbSBtYXhDYWxsYmFja3MgLSBUaGUgbWF4aW11bSBudW1iZXIgb2YgdGltZXMgdGhlIGNhbGxiYWNrIGNhbiBiZSBjYWxsZWQgZm9yIHRoZSBldmVudC4gT25jZSB0aGUgbWF4aW11bSBudW1iZXIgaXMgcmVhY2hlZCwgdGhlIGNhbGxiYWNrIHdpbGwgbm8gbG9uZ2VyIGJlIGNhbGxlZC5cbiAqIEByZXR1cm5zIEEgZnVuY3Rpb24gdGhhdCwgd2hlbiBjYWxsZWQsIHdpbGwgdW5yZWdpc3RlciB0aGUgY2FsbGJhY2sgZnJvbSB0aGUgZXZlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPbk11bHRpcGxlPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihldmVudE5hbWU6IEUsIGNhbGxiYWNrOiBXYWlsc0V2ZW50Q2FsbGJhY2s8RT4sIG1heENhbGxiYWNrczogbnVtYmVyKSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpIHx8IFtdO1xuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xuICAgIGxpc3RlbmVycy5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50TmFtZSwgbGlzdGVuZXJzKTtcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBleGVjdXRlZCB3aGVuIHRoZSBzcGVjaWZpZWQgZXZlbnQgb2NjdXJzLlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgZXZlbnQgdG8gcmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZvci5cbiAqIEBwYXJhbSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBjYWxsZWQgd2hlbiB0aGUgZXZlbnQgaXMgdHJpZ2dlcmVkLlxuICogQHJldHVybnMgQSBmdW5jdGlvbiB0aGF0LCB3aGVuIGNhbGxlZCwgd2lsbCB1bnJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmcm9tIHRoZSBldmVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihldmVudE5hbWU6IEUsIGNhbGxiYWNrOiBXYWlsc0V2ZW50Q2FsbGJhY2s8RT4pOiAoKSA9PiB2b2lkIHtcbiAgICByZXR1cm4gT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAtMSk7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGEgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgZXhlY3V0ZWQgb25seSBvbmNlIGZvciB0aGUgc3BlY2lmaWVkIGV2ZW50LlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgZXZlbnQgdG8gcmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZvci5cbiAqIEBwYXJhbSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBjYWxsZWQgd2hlbiB0aGUgZXZlbnQgaXMgdHJpZ2dlcmVkLlxuICogQHJldHVybnMgQSBmdW5jdGlvbiB0aGF0LCB3aGVuIGNhbGxlZCwgd2lsbCB1bnJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmcm9tIHRoZSBldmVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uY2U8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KGV2ZW50TmFtZTogRSwgY2FsbGJhY2s6IFdhaWxzRXZlbnRDYWxsYmFjazxFPik6ICgpID0+IHZvaWQge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG4vKipcbiAqIFJlbW92ZXMgZXZlbnQgbGlzdGVuZXJzIGZvciB0aGUgc3BlY2lmaWVkIGV2ZW50IG5hbWVzLlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWVzIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50cyB0byByZW1vdmUgbGlzdGVuZXJzIGZvci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9mZiguLi5ldmVudE5hbWVzOiBbV2FpbHNFdmVudE5hbWUsIC4uLldhaWxzRXZlbnROYW1lW11dKTogdm9pZCB7XG4gICAgZXZlbnROYW1lcy5mb3JFYWNoKGV2ZW50TmFtZSA9PiBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKSk7XG59XG5cbi8qKlxuICogUmVtb3ZlcyBhbGwgZXZlbnQgbGlzdGVuZXJzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmQWxsKCk6IHZvaWQge1xuICAgIGV2ZW50TGlzdGVuZXJzLmNsZWFyKCk7XG59XG5cbi8qKlxuICogRW1pdHMgYW4gZXZlbnQuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgd2lsbCBiZSBmdWxmaWxsZWQgb25jZSB0aGUgZXZlbnQgaGFzIGJlZW4gZW1pdHRlZC4gIFJlc29sdmVzIHRvIHRydWUgaWYgdGhlIGV2ZW50IHdhcyBjYW5jZWxsZWQuXG4gKiBAcGFyYW0gbmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byBlbWl0XG4gKiBAcGFyYW0gZGF0YSAtIFRoZSBkYXRhIHRoYXQgd2lsbCBiZSBzZW50IHdpdGggdGhlIGV2ZW50XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbWl0PEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihuYW1lOiBFLCBkYXRhOiBXYWlsc0V2ZW50RGF0YTxFPik6IFByb21pc2U8Ym9vbGVhbj5cbmV4cG9ydCBmdW5jdGlvbiBFbWl0PEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihuYW1lOiBXYWlsc0V2ZW50RGF0YTxFPiBleHRlbmRzIG51bGwgfCB2b2lkID8gRSA6IG5ldmVyKTogUHJvbWlzZTxib29sZWFuPlxuZXhwb3J0IGZ1bmN0aW9uIEVtaXQ8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KG5hbWU6IFdhaWxzRXZlbnREYXRhPEU+LCBkYXRhPzogYW55KTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgcmV0dXJuIGNhbGwoRW1pdE1ldGhvZCwgIG5ldyBXYWlsc0V2ZW50KG5hbWUsIGRhdGEpKVxufVxuXG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIFRoZSBmb2xsb3dpbmcgdXRpbGl0aWVzIGhhdmUgYmVlbiBmYWN0b3JlZCBvdXQgb2YgLi9ldmVudHMudHNcbi8vIGZvciB0ZXN0aW5nIHB1cnBvc2VzLlxuXG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnMgPSBuZXcgTWFwPHN0cmluZywgTGlzdGVuZXJbXT4oKTtcblxuZXhwb3J0IGNsYXNzIExpc3RlbmVyIHtcbiAgICBldmVudE5hbWU6IHN0cmluZztcbiAgICBjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gdm9pZDtcbiAgICBtYXhDYWxsYmFja3M6IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZTogc3RyaW5nLCBjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gdm9pZCwgbWF4Q2FsbGJhY2tzOiBudW1iZXIpIHtcbiAgICAgICAgdGhpcy5ldmVudE5hbWUgPSBldmVudE5hbWU7XG4gICAgICAgIHRoaXMuY2FsbGJhY2sgPSBjYWxsYmFjaztcbiAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgPSBtYXhDYWxsYmFja3MgfHwgLTE7XG4gICAgfVxuXG4gICAgZGlzcGF0Y2goZGF0YTogYW55KTogYm9vbGVhbiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLmNhbGxiYWNrKGRhdGEpO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHJldHVybiBmYWxzZTtcbiAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgcmV0dXJuIHRoaXMubWF4Q2FsbGJhY2tzID09PSAwO1xuICAgIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpc3RlbmVyT2ZmKGxpc3RlbmVyOiBMaXN0ZW5lcik6IHZvaWQge1xuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQobGlzdGVuZXIuZXZlbnROYW1lKTtcbiAgICBpZiAoIWxpc3RlbmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcbiAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUobGlzdGVuZXIuZXZlbnROYW1lKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBldmVudExpc3RlbmVycy5zZXQobGlzdGVuZXIuZXZlbnROYW1lLCBsaXN0ZW5lcnMpO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoqXG4gKiBBbnkgaXMgYSBkdW1teSBjcmVhdGlvbiBmdW5jdGlvbiBmb3Igc2ltcGxlIG9yIHVua25vd24gdHlwZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBBbnk8VCA9IGFueT4oc291cmNlOiBhbnkpOiBUIHtcbiAgICByZXR1cm4gc291cmNlO1xufVxuXG4vKipcbiAqIEJ5dGVTbGljZSBpcyBhIGNyZWF0aW9uIGZ1bmN0aW9uIHRoYXQgcmVwbGFjZXNcbiAqIG51bGwgc3RyaW5ncyB3aXRoIGVtcHR5IHN0cmluZ3MuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCeXRlU2xpY2Uoc291cmNlOiBhbnkpOiBzdHJpbmcge1xuICAgIHJldHVybiAoKHNvdXJjZSA9PSBudWxsKSA/IFwiXCIgOiBzb3VyY2UpO1xufVxuXG4vKipcbiAqIEFycmF5IHRha2VzIGEgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIGFyYml0cmFyeSB0eXBlXG4gKiBhbmQgcmV0dXJucyBhbiBpbi1wbGFjZSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYW4gYXJyYXlcbiAqIHdob3NlIGVsZW1lbnRzIGFyZSBvZiB0aGF0IHR5cGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBBcnJheTxUID0gYW55PihlbGVtZW50OiAoc291cmNlOiBhbnkpID0+IFQpOiAoc291cmNlOiBhbnkpID0+IFRbXSB7XG4gICAgaWYgKGVsZW1lbnQgPT09IEFueSkge1xuICAgICAgICByZXR1cm4gKHNvdXJjZSkgPT4gKHNvdXJjZSA9PT0gbnVsbCA/IFtdIDogc291cmNlKTtcbiAgICB9XG5cbiAgICByZXR1cm4gKHNvdXJjZSkgPT4ge1xuICAgICAgICBpZiAoc291cmNlID09PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIH1cbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzb3VyY2UubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHNvdXJjZVtpXSA9IGVsZW1lbnQoc291cmNlW2ldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc291cmNlO1xuICAgIH07XG59XG5cbi8qKlxuICogTWFwIHRha2VzIGNyZWF0aW9uIGZ1bmN0aW9ucyBmb3IgdHdvIGFyYml0cmFyeSB0eXBlc1xuICogYW5kIHJldHVybnMgYW4gaW4tcGxhY2UgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIG9iamVjdFxuICogd2hvc2Uga2V5cyBhbmQgdmFsdWVzIGFyZSBvZiB0aG9zZSB0eXBlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE1hcDxWID0gYW55PihrZXk6IChzb3VyY2U6IGFueSkgPT4gc3RyaW5nLCB2YWx1ZTogKHNvdXJjZTogYW55KSA9PiBWKTogKHNvdXJjZTogYW55KSA9PiBSZWNvcmQ8c3RyaW5nLCBWPiB7XG4gICAgaWYgKHZhbHVlID09PSBBbnkpIHtcbiAgICAgICAgcmV0dXJuIChzb3VyY2UpID0+IChzb3VyY2UgPT09IG51bGwgPyB7fSA6IHNvdXJjZSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChzb3VyY2UpID0+IHtcbiAgICAgICAgaWYgKHNvdXJjZSA9PT0gbnVsbCkge1xuICAgICAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgICB9XG4gICAgICAgIGZvciAoY29uc3Qga2V5IGluIHNvdXJjZSkge1xuICAgICAgICAgICAgc291cmNlW2tleV0gPSB2YWx1ZShzb3VyY2Vba2V5XSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHNvdXJjZTtcbiAgICB9O1xufVxuXG4vKipcbiAqIE51bGxhYmxlIHRha2VzIGEgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIGFyYml0cmFyeSB0eXBlXG4gKiBhbmQgcmV0dXJucyBhIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhIG51bGxhYmxlIHZhbHVlIG9mIHRoYXQgdHlwZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE51bGxhYmxlPFQgPSBhbnk+KGVsZW1lbnQ6IChzb3VyY2U6IGFueSkgPT4gVCk6IChzb3VyY2U6IGFueSkgPT4gKFQgfCBudWxsKSB7XG4gICAgaWYgKGVsZW1lbnQgPT09IEFueSkge1xuICAgICAgICByZXR1cm4gQW55O1xuICAgIH1cblxuICAgIHJldHVybiAoc291cmNlKSA9PiAoc291cmNlID09PSBudWxsID8gbnVsbCA6IGVsZW1lbnQoc291cmNlKSk7XG59XG5cbi8qKlxuICogU3RydWN0IHRha2VzIGFuIG9iamVjdCBtYXBwaW5nIGZpZWxkIG5hbWVzIHRvIGNyZWF0aW9uIGZ1bmN0aW9uc1xuICogYW5kIHJldHVybnMgYW4gaW4tcGxhY2UgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGEgc3RydWN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gU3RydWN0KGNyZWF0ZUZpZWxkOiBSZWNvcmQ8c3RyaW5nLCAoc291cmNlOiBhbnkpID0+IGFueT4pOlxuICAgIDxVIGV4dGVuZHMgUmVjb3JkPHN0cmluZywgYW55PiA9IGFueT4oc291cmNlOiBhbnkpID0+IFVcbntcbiAgICBsZXQgYWxsQW55ID0gdHJ1ZTtcbiAgICBmb3IgKGNvbnN0IG5hbWUgaW4gY3JlYXRlRmllbGQpIHtcbiAgICAgICAgaWYgKGNyZWF0ZUZpZWxkW25hbWVdICE9PSBBbnkpIHtcbiAgICAgICAgICAgIGFsbEFueSA9IGZhbHNlO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKGFsbEFueSkge1xuICAgICAgICByZXR1cm4gQW55O1xuICAgIH1cblxuICAgIHJldHVybiAoc291cmNlKSA9PiB7XG4gICAgICAgIGZvciAoY29uc3QgbmFtZSBpbiBjcmVhdGVGaWVsZCkge1xuICAgICAgICAgICAgaWYgKG5hbWUgaW4gc291cmNlKSB7XG4gICAgICAgICAgICAgICAgc291cmNlW25hbWVdID0gY3JlYXRlRmllbGRbbmFtZV0oc291cmNlW25hbWVdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc291cmNlO1xuICAgIH07XG59XG5cbi8qKlxuICogTWFwcyBrbm93biBldmVudCBuYW1lcyB0byBjcmVhdGlvbiBmdW5jdGlvbnMgZm9yIHRoZWlyIGRhdGEgdHlwZXMuXG4gKiBXaWxsIGJlIG1vbmtleS1wYXRjaGVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqL1xuZXhwb3J0IGNvbnN0IEV2ZW50czogUmVjb3JkPHN0cmluZywgKHNvdXJjZTogYW55KSA9PiBhbnk+ID0ge307XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIEN5bmh5cmNod3lkIHkgZmZlaWwgaG9uIHluIGF3dG9tYXRpZy4gUEVJRElXQ0ggXHUwMEMyIE1PRElXTFxuLy8gVGhpcyBmaWxlIGlzIGF1dG9tYXRpY2FsbHkgZ2VuZXJhdGVkLiBETyBOT1QgRURJVFxuXG5leHBvcnQgY29uc3QgVHlwZXMgPSBPYmplY3QuZnJlZXplKHtcblx0V2luZG93czogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QVBNUG93ZXJTZXR0aW5nQ2hhbmdlOiBcIndpbmRvd3M6QVBNUG93ZXJTZXR0aW5nQ2hhbmdlXCIsXG5cdFx0QVBNUG93ZXJTdGF0dXNDaGFuZ2U6IFwid2luZG93czpBUE1Qb3dlclN0YXR1c0NoYW5nZVwiLFxuXHRcdEFQTVJlc3VtZUF1dG9tYXRpYzogXCJ3aW5kb3dzOkFQTVJlc3VtZUF1dG9tYXRpY1wiLFxuXHRcdEFQTVJlc3VtZVN1c3BlbmQ6IFwid2luZG93czpBUE1SZXN1bWVTdXNwZW5kXCIsXG5cdFx0QVBNU3VzcGVuZDogXCJ3aW5kb3dzOkFQTVN1c3BlbmRcIixcblx0XHRBcHBsaWNhdGlvblN0YXJ0ZWQ6IFwid2luZG93czpBcHBsaWNhdGlvblN0YXJ0ZWRcIixcblx0XHRTeXN0ZW1UaGVtZUNoYW5nZWQ6IFwid2luZG93czpTeXN0ZW1UaGVtZUNoYW5nZWRcIixcblx0XHRXZWJWaWV3TmF2aWdhdGlvbkNvbXBsZXRlZDogXCJ3aW5kb3dzOldlYlZpZXdOYXZpZ2F0aW9uQ29tcGxldGVkXCIsXG5cdFx0V2luZG93QWN0aXZlOiBcIndpbmRvd3M6V2luZG93QWN0aXZlXCIsXG5cdFx0V2luZG93QmFja2dyb3VuZEVyYXNlOiBcIndpbmRvd3M6V2luZG93QmFja2dyb3VuZEVyYXNlXCIsXG5cdFx0V2luZG93Q2xpY2tBY3RpdmU6IFwid2luZG93czpXaW5kb3dDbGlja0FjdGl2ZVwiLFxuXHRcdFdpbmRvd0Nsb3Npbmc6IFwid2luZG93czpXaW5kb3dDbG9zaW5nXCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJ3aW5kb3dzOldpbmRvd0RpZE1vdmVcIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwid2luZG93czpXaW5kb3dEaWRSZXNpemVcIixcblx0XHRXaW5kb3dEUElDaGFuZ2VkOiBcIndpbmRvd3M6V2luZG93RFBJQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd0RyYWdEcm9wOiBcIndpbmRvd3M6V2luZG93RHJhZ0Ryb3BcIixcblx0XHRXaW5kb3dEcmFnRW50ZXI6IFwid2luZG93czpXaW5kb3dEcmFnRW50ZXJcIixcblx0XHRXaW5kb3dEcmFnTGVhdmU6IFwid2luZG93czpXaW5kb3dEcmFnTGVhdmVcIixcblx0XHRXaW5kb3dEcmFnT3ZlcjogXCJ3aW5kb3dzOldpbmRvd0RyYWdPdmVyXCIsXG5cdFx0V2luZG93RW5kTW92ZTogXCJ3aW5kb3dzOldpbmRvd0VuZE1vdmVcIixcblx0XHRXaW5kb3dFbmRSZXNpemU6IFwid2luZG93czpXaW5kb3dFbmRSZXNpemVcIixcblx0XHRXaW5kb3dGdWxsc2NyZWVuOiBcIndpbmRvd3M6V2luZG93RnVsbHNjcmVlblwiLFxuXHRcdFdpbmRvd0hpZGU6IFwid2luZG93czpXaW5kb3dIaWRlXCIsXG5cdFx0V2luZG93SW5hY3RpdmU6IFwid2luZG93czpXaW5kb3dJbmFjdGl2ZVwiLFxuXHRcdFdpbmRvd0tleURvd246IFwid2luZG93czpXaW5kb3dLZXlEb3duXCIsXG5cdFx0V2luZG93S2V5VXA6IFwid2luZG93czpXaW5kb3dLZXlVcFwiLFxuXHRcdFdpbmRvd0tpbGxGb2N1czogXCJ3aW5kb3dzOldpbmRvd0tpbGxGb2N1c1wiLFxuXHRcdFdpbmRvd05vbkNsaWVudEhpdDogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudEhpdFwiLFxuXHRcdFdpbmRvd05vbkNsaWVudE1vdXNlRG93bjogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudE1vdXNlRG93blwiLFxuXHRcdFdpbmRvd05vbkNsaWVudE1vdXNlTGVhdmU6IFwid2luZG93czpXaW5kb3dOb25DbGllbnRNb3VzZUxlYXZlXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VNb3ZlOiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50TW91c2VNb3ZlXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VVcDogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudE1vdXNlVXBcIixcblx0XHRXaW5kb3dQYWludDogXCJ3aW5kb3dzOldpbmRvd1BhaW50XCIsXG5cdFx0V2luZG93UmVzdG9yZTogXCJ3aW5kb3dzOldpbmRvd1Jlc3RvcmVcIixcblx0XHRXaW5kb3dTZXRGb2N1czogXCJ3aW5kb3dzOldpbmRvd1NldEZvY3VzXCIsXG5cdFx0V2luZG93U2hvdzogXCJ3aW5kb3dzOldpbmRvd1Nob3dcIixcblx0XHRXaW5kb3dTdGFydE1vdmU6IFwid2luZG93czpXaW5kb3dTdGFydE1vdmVcIixcblx0XHRXaW5kb3dTdGFydFJlc2l6ZTogXCJ3aW5kb3dzOldpbmRvd1N0YXJ0UmVzaXplXCIsXG5cdFx0V2luZG93VW5GdWxsc2NyZWVuOiBcIndpbmRvd3M6V2luZG93VW5GdWxsc2NyZWVuXCIsXG5cdFx0V2luZG93Wk9yZGVyQ2hhbmdlZDogXCJ3aW5kb3dzOldpbmRvd1pPcmRlckNoYW5nZWRcIixcblx0XHRXaW5kb3dNaW5pbWlzZTogXCJ3aW5kb3dzOldpbmRvd01pbmltaXNlXCIsXG5cdFx0V2luZG93VW5NaW5pbWlzZTogXCJ3aW5kb3dzOldpbmRvd1VuTWluaW1pc2VcIixcblx0XHRXaW5kb3dNYXhpbWlzZTogXCJ3aW5kb3dzOldpbmRvd01heGltaXNlXCIsXG5cdFx0V2luZG93VW5NYXhpbWlzZTogXCJ3aW5kb3dzOldpbmRvd1VuTWF4aW1pc2VcIixcblx0fSksXG5cdE1hYzogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQmVjb21lQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VCYWNraW5nUHJvcGVydGllczogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VCYWNraW5nUHJvcGVydGllc1wiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlRWZmZWN0aXZlQXBwZWFyYW5jZTogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VJY29uOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZUljb25cIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZU9jY2x1c2lvblN0YXRlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZU9jY2x1c2lvblN0YXRlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VTY3JlZW5QYXJhbWV0ZXJzOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZVNjcmVlblBhcmFtZXRlcnNcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0JhckZyYW1lOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0JhckZyYW1lXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VTdGF0dXNCYXJPcmllbnRhdGlvbjogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VTdGF0dXNCYXJPcmllbnRhdGlvblwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlVGhlbWU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlVGhlbWVcIixcblx0XHRBcHBsaWNhdGlvbkRpZEZpbmlzaExhdW5jaGluZzogXCJtYWM6QXBwbGljYXRpb25EaWRGaW5pc2hMYXVuY2hpbmdcIixcblx0XHRBcHBsaWNhdGlvbkRpZEhpZGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkSGlkZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkUmVzaWduQWN0aXZlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZFJlc2lnbkFjdGl2ZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkVW5oaWRlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZFVuaGlkZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkVXBkYXRlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZFVwZGF0ZVwiLFxuXHRcdEFwcGxpY2F0aW9uU2hvdWxkSGFuZGxlUmVvcGVuOiBcIm1hYzpBcHBsaWNhdGlvblNob3VsZEhhbmRsZVJlb3BlblwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbEJlY29tZUFjdGl2ZTogXCJtYWM6QXBwbGljYXRpb25XaWxsQmVjb21lQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsRmluaXNoTGF1bmNoaW5nOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxGaW5pc2hMYXVuY2hpbmdcIixcblx0XHRBcHBsaWNhdGlvbldpbGxIaWRlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxIaWRlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsUmVzaWduQWN0aXZlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxUZXJtaW5hdGU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZVwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbFVuaGlkZTogXCJtYWM6QXBwbGljYXRpb25XaWxsVW5oaWRlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsVXBkYXRlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxVcGRhdGVcIixcblx0XHRNZW51RGlkQWRkSXRlbTogXCJtYWM6TWVudURpZEFkZEl0ZW1cIixcblx0XHRNZW51RGlkQmVnaW5UcmFja2luZzogXCJtYWM6TWVudURpZEJlZ2luVHJhY2tpbmdcIixcblx0XHRNZW51RGlkQ2xvc2U6IFwibWFjOk1lbnVEaWRDbG9zZVwiLFxuXHRcdE1lbnVEaWREaXNwbGF5SXRlbTogXCJtYWM6TWVudURpZERpc3BsYXlJdGVtXCIsXG5cdFx0TWVudURpZEVuZFRyYWNraW5nOiBcIm1hYzpNZW51RGlkRW5kVHJhY2tpbmdcIixcblx0XHRNZW51RGlkSGlnaGxpZ2h0SXRlbTogXCJtYWM6TWVudURpZEhpZ2hsaWdodEl0ZW1cIixcblx0XHRNZW51RGlkT3BlbjogXCJtYWM6TWVudURpZE9wZW5cIixcblx0XHRNZW51RGlkUG9wVXA6IFwibWFjOk1lbnVEaWRQb3BVcFwiLFxuXHRcdE1lbnVEaWRSZW1vdmVJdGVtOiBcIm1hYzpNZW51RGlkUmVtb3ZlSXRlbVwiLFxuXHRcdE1lbnVEaWRTZW5kQWN0aW9uOiBcIm1hYzpNZW51RGlkU2VuZEFjdGlvblwiLFxuXHRcdE1lbnVEaWRTZW5kQWN0aW9uVG9JdGVtOiBcIm1hYzpNZW51RGlkU2VuZEFjdGlvblRvSXRlbVwiLFxuXHRcdE1lbnVEaWRVcGRhdGU6IFwibWFjOk1lbnVEaWRVcGRhdGVcIixcblx0XHRNZW51V2lsbEFkZEl0ZW06IFwibWFjOk1lbnVXaWxsQWRkSXRlbVwiLFxuXHRcdE1lbnVXaWxsQmVnaW5UcmFja2luZzogXCJtYWM6TWVudVdpbGxCZWdpblRyYWNraW5nXCIsXG5cdFx0TWVudVdpbGxEaXNwbGF5SXRlbTogXCJtYWM6TWVudVdpbGxEaXNwbGF5SXRlbVwiLFxuXHRcdE1lbnVXaWxsRW5kVHJhY2tpbmc6IFwibWFjOk1lbnVXaWxsRW5kVHJhY2tpbmdcIixcblx0XHRNZW51V2lsbEhpZ2hsaWdodEl0ZW06IFwibWFjOk1lbnVXaWxsSGlnaGxpZ2h0SXRlbVwiLFxuXHRcdE1lbnVXaWxsT3BlbjogXCJtYWM6TWVudVdpbGxPcGVuXCIsXG5cdFx0TWVudVdpbGxQb3BVcDogXCJtYWM6TWVudVdpbGxQb3BVcFwiLFxuXHRcdE1lbnVXaWxsUmVtb3ZlSXRlbTogXCJtYWM6TWVudVdpbGxSZW1vdmVJdGVtXCIsXG5cdFx0TWVudVdpbGxTZW5kQWN0aW9uOiBcIm1hYzpNZW51V2lsbFNlbmRBY3Rpb25cIixcblx0XHRNZW51V2lsbFNlbmRBY3Rpb25Ub0l0ZW06IFwibWFjOk1lbnVXaWxsU2VuZEFjdGlvblRvSXRlbVwiLFxuXHRcdE1lbnVXaWxsVXBkYXRlOiBcIm1hYzpNZW51V2lsbFVwZGF0ZVwiLFxuXHRcdFdlYlZpZXdEaWRDb21taXROYXZpZ2F0aW9uOiBcIm1hYzpXZWJWaWV3RGlkQ29tbWl0TmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uOiBcIm1hYzpXZWJWaWV3RGlkRmluaXNoTmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRSZWNlaXZlU2VydmVyUmVkaXJlY3RGb3JQcm92aXNpb25hbE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRSZWNlaXZlU2VydmVyUmVkaXJlY3RGb3JQcm92aXNpb25hbE5hdmlnYXRpb25cIixcblx0XHRXZWJWaWV3RGlkU3RhcnRQcm92aXNpb25hbE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRTdGFydFByb3Zpc2lvbmFsTmF2aWdhdGlvblwiLFxuXHRcdFdpbmRvd0RpZEJlY29tZUtleTogXCJtYWM6V2luZG93RGlkQmVjb21lS2V5XCIsXG5cdFx0V2luZG93RGlkQmVjb21lTWFpbjogXCJtYWM6V2luZG93RGlkQmVjb21lTWFpblwiLFxuXHRcdFdpbmRvd0RpZEJlZ2luU2hlZXQ6IFwibWFjOldpbmRvd0RpZEJlZ2luU2hlZXRcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VBbHBoYTogXCJtYWM6V2luZG93RGlkQ2hhbmdlQWxwaGFcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VCYWNraW5nTG9jYXRpb246IFwibWFjOldpbmRvd0RpZENoYW5nZUJhY2tpbmdMb2NhdGlvblwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VCYWNraW5nUHJvcGVydGllc1wiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUNvbGxlY3Rpb25CZWhhdmlvcjogXCJtYWM6V2luZG93RGlkQ2hhbmdlQ29sbGVjdGlvbkJlaGF2aW9yXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlRWZmZWN0aXZlQXBwZWFyYW5jZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlRWZmZWN0aXZlQXBwZWFyYW5jZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZU9jY2x1c2lvblN0YXRlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VPY2NsdXNpb25TdGF0ZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZU9yZGVyaW5nTW9kZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlT3JkZXJpbmdNb2RlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5cIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTY3JlZW5QYXJhbWV0ZXJzOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5QYXJhbWV0ZXJzXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuUHJvZmlsZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU2NyZWVuUHJvZmlsZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZVNjcmVlblNwYWNlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5TcGFjZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZVNjcmVlblNwYWNlUHJvcGVydGllczogXCJtYWM6V2luZG93RGlkQ2hhbmdlU2NyZWVuU3BhY2VQcm9wZXJ0aWVzXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2hhcmluZ1R5cGU6IFwibWFjOldpbmRvd0RpZENoYW5nZVNoYXJpbmdUeXBlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU3BhY2U6IFwibWFjOldpbmRvd0RpZENoYW5nZVNwYWNlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU3BhY2VPcmRlcmluZ01vZGU6IFwibWFjOldpbmRvd0RpZENoYW5nZVNwYWNlT3JkZXJpbmdNb2RlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlVGl0bGU6IFwibWFjOldpbmRvd0RpZENoYW5nZVRpdGxlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlVG9vbGJhcjogXCJtYWM6V2luZG93RGlkQ2hhbmdlVG9vbGJhclwiLFxuXHRcdFdpbmRvd0RpZERlbWluaWF0dXJpemU6IFwibWFjOldpbmRvd0RpZERlbWluaWF0dXJpemVcIixcblx0XHRXaW5kb3dEaWRFbmRTaGVldDogXCJtYWM6V2luZG93RGlkRW5kU2hlZXRcIixcblx0XHRXaW5kb3dEaWRFbnRlckZ1bGxTY3JlZW46IFwibWFjOldpbmRvd0RpZEVudGVyRnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZEVudGVyVmVyc2lvbkJyb3dzZXI6IFwibWFjOldpbmRvd0RpZEVudGVyVmVyc2lvbkJyb3dzZXJcIixcblx0XHRXaW5kb3dEaWRFeGl0RnVsbFNjcmVlbjogXCJtYWM6V2luZG93RGlkRXhpdEZ1bGxTY3JlZW5cIixcblx0XHRXaW5kb3dEaWRFeGl0VmVyc2lvbkJyb3dzZXI6IFwibWFjOldpbmRvd0RpZEV4aXRWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd0RpZEV4cG9zZTogXCJtYWM6V2luZG93RGlkRXhwb3NlXCIsXG5cdFx0V2luZG93RGlkRm9jdXM6IFwibWFjOldpbmRvd0RpZEZvY3VzXCIsXG5cdFx0V2luZG93RGlkTWluaWF0dXJpemU6IFwibWFjOldpbmRvd0RpZE1pbmlhdHVyaXplXCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJtYWM6V2luZG93RGlkTW92ZVwiLFxuXHRcdFdpbmRvd0RpZE9yZGVyT2ZmU2NyZWVuOiBcIm1hYzpXaW5kb3dEaWRPcmRlck9mZlNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZE9yZGVyT25TY3JlZW46IFwibWFjOldpbmRvd0RpZE9yZGVyT25TY3JlZW5cIixcblx0XHRXaW5kb3dEaWRSZXNpZ25LZXk6IFwibWFjOldpbmRvd0RpZFJlc2lnbktleVwiLFxuXHRcdFdpbmRvd0RpZFJlc2lnbk1haW46IFwibWFjOldpbmRvd0RpZFJlc2lnbk1haW5cIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwibWFjOldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZTogXCJtYWM6V2luZG93RGlkVXBkYXRlXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlQWxwaGE6IFwibWFjOldpbmRvd0RpZFVwZGF0ZUFscGhhXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlQ29sbGVjdGlvbkJlaGF2aW9yOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uQmVoYXZpb3JcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uUHJvcGVydGllczogXCJtYWM6V2luZG93RGlkVXBkYXRlQ29sbGVjdGlvblByb3BlcnRpZXNcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVTaGFkb3c6IFwibWFjOldpbmRvd0RpZFVwZGF0ZVNoYWRvd1wiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZVRpdGxlOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVUaXRsZVwiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZVRvb2xiYXI6IFwibWFjOldpbmRvd0RpZFVwZGF0ZVRvb2xiYXJcIixcblx0XHRXaW5kb3dEaWRab29tOiBcIm1hYzpXaW5kb3dEaWRab29tXCIsXG5cdFx0V2luZG93RmlsZURyYWdnaW5nRW50ZXJlZDogXCJtYWM6V2luZG93RmlsZURyYWdnaW5nRW50ZXJlZFwiLFxuXHRcdFdpbmRvd0ZpbGVEcmFnZ2luZ0V4aXRlZDogXCJtYWM6V2luZG93RmlsZURyYWdnaW5nRXhpdGVkXCIsXG5cdFx0V2luZG93RmlsZURyYWdnaW5nUGVyZm9ybWVkOiBcIm1hYzpXaW5kb3dGaWxlRHJhZ2dpbmdQZXJmb3JtZWRcIixcblx0XHRXaW5kb3dIaWRlOiBcIm1hYzpXaW5kb3dIaWRlXCIsXG5cdFx0V2luZG93TWF4aW1pc2U6IFwibWFjOldpbmRvd01heGltaXNlXCIsXG5cdFx0V2luZG93VW5NYXhpbWlzZTogXCJtYWM6V2luZG93VW5NYXhpbWlzZVwiLFxuXHRcdFdpbmRvd01pbmltaXNlOiBcIm1hYzpXaW5kb3dNaW5pbWlzZVwiLFxuXHRcdFdpbmRvd1VuTWluaW1pc2U6IFwibWFjOldpbmRvd1VuTWluaW1pc2VcIixcblx0XHRXaW5kb3dTaG91bGRDbG9zZTogXCJtYWM6V2luZG93U2hvdWxkQ2xvc2VcIixcblx0XHRXaW5kb3dTaG93OiBcIm1hYzpXaW5kb3dTaG93XCIsXG5cdFx0V2luZG93V2lsbEJlY29tZUtleTogXCJtYWM6V2luZG93V2lsbEJlY29tZUtleVwiLFxuXHRcdFdpbmRvd1dpbGxCZWNvbWVNYWluOiBcIm1hYzpXaW5kb3dXaWxsQmVjb21lTWFpblwiLFxuXHRcdFdpbmRvd1dpbGxCZWdpblNoZWV0OiBcIm1hYzpXaW5kb3dXaWxsQmVnaW5TaGVldFwiLFxuXHRcdFdpbmRvd1dpbGxDaGFuZ2VPcmRlcmluZ01vZGU6IFwibWFjOldpbmRvd1dpbGxDaGFuZ2VPcmRlcmluZ01vZGVcIixcblx0XHRXaW5kb3dXaWxsQ2xvc2U6IFwibWFjOldpbmRvd1dpbGxDbG9zZVwiLFxuXHRcdFdpbmRvd1dpbGxEZW1pbmlhdHVyaXplOiBcIm1hYzpXaW5kb3dXaWxsRGVtaW5pYXR1cml6ZVwiLFxuXHRcdFdpbmRvd1dpbGxFbnRlckZ1bGxTY3JlZW46IFwibWFjOldpbmRvd1dpbGxFbnRlckZ1bGxTY3JlZW5cIixcblx0XHRXaW5kb3dXaWxsRW50ZXJWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93V2lsbEVudGVyVmVyc2lvbkJyb3dzZXJcIixcblx0XHRXaW5kb3dXaWxsRXhpdEZ1bGxTY3JlZW46IFwibWFjOldpbmRvd1dpbGxFeGl0RnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxFeGl0VmVyc2lvbkJyb3dzZXI6IFwibWFjOldpbmRvd1dpbGxFeGl0VmVyc2lvbkJyb3dzZXJcIixcblx0XHRXaW5kb3dXaWxsRm9jdXM6IFwibWFjOldpbmRvd1dpbGxGb2N1c1wiLFxuXHRcdFdpbmRvd1dpbGxNaW5pYXR1cml6ZTogXCJtYWM6V2luZG93V2lsbE1pbmlhdHVyaXplXCIsXG5cdFx0V2luZG93V2lsbE1vdmU6IFwibWFjOldpbmRvd1dpbGxNb3ZlXCIsXG5cdFx0V2luZG93V2lsbE9yZGVyT2ZmU2NyZWVuOiBcIm1hYzpXaW5kb3dXaWxsT3JkZXJPZmZTY3JlZW5cIixcblx0XHRXaW5kb3dXaWxsT3JkZXJPblNjcmVlbjogXCJtYWM6V2luZG93V2lsbE9yZGVyT25TY3JlZW5cIixcblx0XHRXaW5kb3dXaWxsUmVzaWduTWFpbjogXCJtYWM6V2luZG93V2lsbFJlc2lnbk1haW5cIixcblx0XHRXaW5kb3dXaWxsUmVzaXplOiBcIm1hYzpXaW5kb3dXaWxsUmVzaXplXCIsXG5cdFx0V2luZG93V2lsbFVuZm9jdXM6IFwibWFjOldpbmRvd1dpbGxVbmZvY3VzXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVwiLFxuXHRcdFdpbmRvd1dpbGxVcGRhdGVBbHBoYTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZUFscGhhXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25CZWhhdmlvcjogXCJtYWM6V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25CZWhhdmlvclwiLFxuXHRcdFdpbmRvd1dpbGxVcGRhdGVDb2xsZWN0aW9uUHJvcGVydGllczogXCJtYWM6V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25Qcm9wZXJ0aWVzXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVNoYWRvdzogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVNoYWRvd1wiLFxuXHRcdFdpbmRvd1dpbGxVcGRhdGVUaXRsZTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVRpdGxlXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVRvb2xiYXI6IFwibWFjOldpbmRvd1dpbGxVcGRhdGVUb29sYmFyXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVZpc2liaWxpdHk6IFwibWFjOldpbmRvd1dpbGxVcGRhdGVWaXNpYmlsaXR5XCIsXG5cdFx0V2luZG93V2lsbFVzZVN0YW5kYXJkRnJhbWU6IFwibWFjOldpbmRvd1dpbGxVc2VTdGFuZGFyZEZyYW1lXCIsXG5cdFx0V2luZG93Wm9vbUluOiBcIm1hYzpXaW5kb3dab29tSW5cIixcblx0XHRXaW5kb3dab29tT3V0OiBcIm1hYzpXaW5kb3dab29tT3V0XCIsXG5cdFx0V2luZG93Wm9vbVJlc2V0OiBcIm1hYzpXaW5kb3dab29tUmVzZXRcIixcblx0fSksXG5cdExpbnV4OiBPYmplY3QuZnJlZXplKHtcblx0XHRBcHBsaWNhdGlvblN0YXJ0dXA6IFwibGludXg6QXBwbGljYXRpb25TdGFydHVwXCIsXG5cdFx0U3lzdGVtVGhlbWVDaGFuZ2VkOiBcImxpbnV4OlN5c3RlbVRoZW1lQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd0RlbGV0ZUV2ZW50OiBcImxpbnV4OldpbmRvd0RlbGV0ZUV2ZW50XCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJsaW51eDpXaW5kb3dEaWRNb3ZlXCIsXG5cdFx0V2luZG93RGlkUmVzaXplOiBcImxpbnV4OldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0ZvY3VzSW46IFwibGludXg6V2luZG93Rm9jdXNJblwiLFxuXHRcdFdpbmRvd0ZvY3VzT3V0OiBcImxpbnV4OldpbmRvd0ZvY3VzT3V0XCIsXG5cdFx0V2luZG93TG9hZFN0YXJ0ZWQ6IFwibGludXg6V2luZG93TG9hZFN0YXJ0ZWRcIixcblx0XHRXaW5kb3dMb2FkUmVkaXJlY3RlZDogXCJsaW51eDpXaW5kb3dMb2FkUmVkaXJlY3RlZFwiLFxuXHRcdFdpbmRvd0xvYWRDb21taXR0ZWQ6IFwibGludXg6V2luZG93TG9hZENvbW1pdHRlZFwiLFxuXHRcdFdpbmRvd0xvYWRGaW5pc2hlZDogXCJsaW51eDpXaW5kb3dMb2FkRmluaXNoZWRcIixcblx0fSksXG5cdGlPUzogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmU6IFwiaW9zOkFwcGxpY2F0aW9uRGlkQmVjb21lQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRFbnRlckJhY2tncm91bmQ6IFwiaW9zOkFwcGxpY2F0aW9uRGlkRW50ZXJCYWNrZ3JvdW5kXCIsXG5cdFx0QXBwbGljYXRpb25EaWRGaW5pc2hMYXVuY2hpbmc6IFwiaW9zOkFwcGxpY2F0aW9uRGlkRmluaXNoTGF1bmNoaW5nXCIsXG5cdFx0QXBwbGljYXRpb25EaWRSZWNlaXZlTWVtb3J5V2FybmluZzogXCJpb3M6QXBwbGljYXRpb25EaWRSZWNlaXZlTWVtb3J5V2FybmluZ1wiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbEVudGVyRm9yZWdyb3VuZDogXCJpb3M6QXBwbGljYXRpb25XaWxsRW50ZXJGb3JlZ3JvdW5kXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsUmVzaWduQWN0aXZlOiBcImlvczpBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxUZXJtaW5hdGU6IFwiaW9zOkFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZVwiLFxuXHRcdFdpbmRvd0RpZExvYWQ6IFwiaW9zOldpbmRvd0RpZExvYWRcIixcblx0XHRXaW5kb3dXaWxsQXBwZWFyOiBcImlvczpXaW5kb3dXaWxsQXBwZWFyXCIsXG5cdFx0V2luZG93RGlkQXBwZWFyOiBcImlvczpXaW5kb3dEaWRBcHBlYXJcIixcblx0XHRXaW5kb3dXaWxsRGlzYXBwZWFyOiBcImlvczpXaW5kb3dXaWxsRGlzYXBwZWFyXCIsXG5cdFx0V2luZG93RGlkRGlzYXBwZWFyOiBcImlvczpXaW5kb3dEaWREaXNhcHBlYXJcIixcblx0XHRXaW5kb3dTYWZlQXJlYUluc2V0c0NoYW5nZWQ6IFwiaW9zOldpbmRvd1NhZmVBcmVhSW5zZXRzQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd09yaWVudGF0aW9uQ2hhbmdlZDogXCJpb3M6V2luZG93T3JpZW50YXRpb25DaGFuZ2VkXCIsXG5cdFx0V2luZG93VG91Y2hCZWdhbjogXCJpb3M6V2luZG93VG91Y2hCZWdhblwiLFxuXHRcdFdpbmRvd1RvdWNoTW92ZWQ6IFwiaW9zOldpbmRvd1RvdWNoTW92ZWRcIixcblx0XHRXaW5kb3dUb3VjaEVuZGVkOiBcImlvczpXaW5kb3dUb3VjaEVuZGVkXCIsXG5cdFx0V2luZG93VG91Y2hDYW5jZWxsZWQ6IFwiaW9zOldpbmRvd1RvdWNoQ2FuY2VsbGVkXCIsXG5cdFx0V2ViVmlld0RpZFN0YXJ0TmF2aWdhdGlvbjogXCJpb3M6V2ViVmlld0RpZFN0YXJ0TmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uOiBcImlvczpXZWJWaWV3RGlkRmluaXNoTmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRGYWlsTmF2aWdhdGlvbjogXCJpb3M6V2ViVmlld0RpZEZhaWxOYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RlY2lkZVBvbGljeUZvck5hdmlnYXRpb25BY3Rpb246IFwiaW9zOldlYlZpZXdEZWNpZGVQb2xpY3lGb3JOYXZpZ2F0aW9uQWN0aW9uXCIsXG5cdH0pLFxuXHRDb21tb246IE9iamVjdC5mcmVlemUoe1xuXHRcdEFwcGxpY2F0aW9uT3BlbmVkV2l0aEZpbGU6IFwiY29tbW9uOkFwcGxpY2F0aW9uT3BlbmVkV2l0aEZpbGVcIixcblx0XHRBcHBsaWNhdGlvblN0YXJ0ZWQ6IFwiY29tbW9uOkFwcGxpY2F0aW9uU3RhcnRlZFwiLFxuXHRcdEFwcGxpY2F0aW9uTGF1bmNoZWRXaXRoVXJsOiBcImNvbW1vbjpBcHBsaWNhdGlvbkxhdW5jaGVkV2l0aFVybFwiLFxuXHRcdFRoZW1lQ2hhbmdlZDogXCJjb21tb246VGhlbWVDaGFuZ2VkXCIsXG5cdFx0V2luZG93Q2xvc2luZzogXCJjb21tb246V2luZG93Q2xvc2luZ1wiLFxuXHRcdFdpbmRvd0RpZE1vdmU6IFwiY29tbW9uOldpbmRvd0RpZE1vdmVcIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwiY29tbW9uOldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0RQSUNoYW5nZWQ6IFwiY29tbW9uOldpbmRvd0RQSUNoYW5nZWRcIixcblx0XHRXaW5kb3dGaWxlc0Ryb3BwZWQ6IFwiY29tbW9uOldpbmRvd0ZpbGVzRHJvcHBlZFwiLFxuXHRcdFdpbmRvd0ZvY3VzOiBcImNvbW1vbjpXaW5kb3dGb2N1c1wiLFxuXHRcdFdpbmRvd0Z1bGxzY3JlZW46IFwiY29tbW9uOldpbmRvd0Z1bGxzY3JlZW5cIixcblx0XHRXaW5kb3dIaWRlOiBcImNvbW1vbjpXaW5kb3dIaWRlXCIsXG5cdFx0V2luZG93TG9zdEZvY3VzOiBcImNvbW1vbjpXaW5kb3dMb3N0Rm9jdXNcIixcblx0XHRXaW5kb3dNYXhpbWlzZTogXCJjb21tb246V2luZG93TWF4aW1pc2VcIixcblx0XHRXaW5kb3dNaW5pbWlzZTogXCJjb21tb246V2luZG93TWluaW1pc2VcIixcblx0XHRXaW5kb3dUb2dnbGVGcmFtZWxlc3M6IFwiY29tbW9uOldpbmRvd1RvZ2dsZUZyYW1lbGVzc1wiLFxuXHRcdFdpbmRvd1Jlc3RvcmU6IFwiY29tbW9uOldpbmRvd1Jlc3RvcmVcIixcblx0XHRXaW5kb3dSdW50aW1lUmVhZHk6IFwiY29tbW9uOldpbmRvd1J1bnRpbWVSZWFkeVwiLFxuXHRcdFdpbmRvd1Nob3c6IFwiY29tbW9uOldpbmRvd1Nob3dcIixcblx0XHRXaW5kb3dVbkZ1bGxzY3JlZW46IFwiY29tbW9uOldpbmRvd1VuRnVsbHNjcmVlblwiLFxuXHRcdFdpbmRvd1VuTWF4aW1pc2U6IFwiY29tbW9uOldpbmRvd1VuTWF4aW1pc2VcIixcblx0XHRXaW5kb3dVbk1pbmltaXNlOiBcImNvbW1vbjpXaW5kb3dVbk1pbmltaXNlXCIsXG5cdFx0V2luZG93Wm9vbTogXCJjb21tb246V2luZG93Wm9vbVwiLFxuXHRcdFdpbmRvd1pvb21JbjogXCJjb21tb246V2luZG93Wm9vbUluXCIsXG5cdFx0V2luZG93Wm9vbU91dDogXCJjb21tb246V2luZG93Wm9vbU91dFwiLFxuXHRcdFdpbmRvd1pvb21SZXNldDogXCJjb21tb246V2luZG93Wm9vbVJlc2V0XCIsXG5cdFx0V2luZG93RHJvcFpvbmVGaWxlc0Ryb3BwZWQ6IFwiY29tbW9uOldpbmRvd0Ryb3Bab25lRmlsZXNEcm9wcGVkXCIsXG5cdH0pLFxufSk7XG4iLCAiLypcbiBfICAgICBfXyAgICAgXyBfX1xufCB8ICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qKlxuICogTG9ncyBhIG1lc3NhZ2UgdG8gdGhlIGNvbnNvbGUgd2l0aCBjdXN0b20gZm9ybWF0dGluZy5cbiAqXG4gKiBAcGFyYW0gbWVzc2FnZSAtIFRoZSBtZXNzYWdlIHRvIGJlIGxvZ2dlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlYnVnTG9nKG1lc3NhZ2U6IGFueSkge1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZVxuICAgIGNvbnNvbGUubG9nKFxuICAgICAgICAnJWMgd2FpbHMzICVjICcgKyBtZXNzYWdlICsgJyAnLFxuICAgICAgICAnYmFja2dyb3VuZDogI2FhMDAwMDsgY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDNweCAwcHggMHB4IDNweDsgcGFkZGluZzogMXB4OyBmb250LXNpemU6IDAuN3JlbScsXG4gICAgICAgICdiYWNrZ3JvdW5kOiAjMDA5OTAwOyBjb2xvcjogI2ZmZjsgYm9yZGVyLXJhZGl1czogMHB4IDNweCAzcHggMHB4OyBwYWRkaW5nOiAxcHg7IGZvbnQtc2l6ZTogMC43cmVtJ1xuICAgICk7XG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgdGhlIHdlYnZpZXcgc3VwcG9ydHMgdGhlIHtAbGluayBNb3VzZUV2ZW50I2J1dHRvbnN9IHByb3BlcnR5LlxuICogTG9va2luZyBhdCB5b3UgbWFjT1MgSGlnaCBTaWVycmEhXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjYW5UcmFja0J1dHRvbnMoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIChuZXcgTW91c2VFdmVudCgnbW91c2Vkb3duJykpLmJ1dHRvbnMgPT09IDA7XG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgdGhlIGJyb3dzZXIgc3VwcG9ydHMgcmVtb3ZpbmcgbGlzdGVuZXJzIGJ5IHRyaWdnZXJpbmcgYW4gQWJvcnRTaWduYWxcbiAqIChzZWUgaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvQVBJL0V2ZW50VGFyZ2V0L2FkZEV2ZW50TGlzdGVuZXIjc2lnbmFsKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbkFib3J0TGlzdGVuZXJzKCkge1xuICAgIGlmICghRXZlbnRUYXJnZXQgfHwgIUFib3J0U2lnbmFsIHx8ICFBYm9ydENvbnRyb2xsZXIpXG4gICAgICAgIHJldHVybiBmYWxzZTtcblxuICAgIGxldCByZXN1bHQgPSB0cnVlO1xuXG4gICAgY29uc3QgdGFyZ2V0ID0gbmV3IEV2ZW50VGFyZ2V0KCk7XG4gICAgY29uc3QgY29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICB0YXJnZXQuYWRkRXZlbnRMaXN0ZW5lcigndGVzdCcsICgpID0+IHsgcmVzdWx0ID0gZmFsc2U7IH0sIHsgc2lnbmFsOiBjb250cm9sbGVyLnNpZ25hbCB9KTtcbiAgICBjb250cm9sbGVyLmFib3J0KCk7XG4gICAgdGFyZ2V0LmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCd0ZXN0JykpO1xuXG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgY2xvc2VzdCBIVE1MRWxlbWVudCBhbmNlc3RvciBvZiBhbiBldmVudCdzIHRhcmdldC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV2ZW50VGFyZ2V0KGV2ZW50OiBFdmVudCk6IEhUTUxFbGVtZW50IHtcbiAgICBpZiAoZXZlbnQudGFyZ2V0IGluc3RhbmNlb2YgSFRNTEVsZW1lbnQpIHtcbiAgICAgICAgcmV0dXJuIGV2ZW50LnRhcmdldDtcbiAgICB9IGVsc2UgaWYgKCEoZXZlbnQudGFyZ2V0IGluc3RhbmNlb2YgSFRNTEVsZW1lbnQpICYmIGV2ZW50LnRhcmdldCBpbnN0YW5jZW9mIE5vZGUpIHtcbiAgICAgICAgcmV0dXJuIGV2ZW50LnRhcmdldC5wYXJlbnRFbGVtZW50ID8/IGRvY3VtZW50LmJvZHk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGRvY3VtZW50LmJvZHk7XG4gICAgfVxufVxuXG4vKioqXG4gVGhpcyB0ZWNobmlxdWUgZm9yIHByb3BlciBsb2FkIGRldGVjdGlvbiBpcyB0YWtlbiBmcm9tIEhUTVg6XG5cbiBCU0QgMi1DbGF1c2UgTGljZW5zZVxuXG4gQ29weXJpZ2h0IChjKSAyMDIwLCBCaWcgU2t5IFNvZnR3YXJlXG4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cblxuIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dFxuIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUgbWV0OlxuXG4gMS4gUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLCB0aGlzXG4gbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuXG5cbiAyLiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsXG4gdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvblxuIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLlxuXG4gVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUyBcIkFTIElTXCJcbiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFXG4gSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFXG4gRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRVxuIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMXG4gREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1JcbiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUlxuIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksXG4gT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRIRSBVU0VcbiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLlxuXG4gKioqL1xuXG5sZXQgaXNSZWFkeSA9IGZhbHNlO1xuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsICgpID0+IHsgaXNSZWFkeSA9IHRydWUgfSk7XG5cbmV4cG9ydCBmdW5jdGlvbiB3aGVuUmVhZHkoY2FsbGJhY2s6ICgpID0+IHZvaWQpIHtcbiAgICBpZiAoaXNSZWFkeSB8fCBkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnY29tcGxldGUnKSB7XG4gICAgICAgIGNhbGxiYWNrKCk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGNhbGxiYWNrKTtcbiAgICB9XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7bmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcbmltcG9ydCB0eXBlIHsgU2NyZWVuIH0gZnJvbSBcIi4vc2NyZWVucy5qc1wiO1xuXG4vLyBORVc6IERyb3B6b25lIGNvbnN0YW50c1xuY29uc3QgRFJPUFpPTkVfQVRUUklCVVRFID0gJ2RhdGEtd2FpbHMtZHJvcHpvbmUnO1xuY29uc3QgRFJPUFpPTkVfSE9WRVJfQ0xBU1MgPSAnd2FpbHMtZHJvcHpvbmUtaG92ZXInOyAvLyBVc2VyIGNhbiBzdHlsZSB0aGlzIGNsYXNzXG5sZXQgY3VycmVudEhvdmVyZWREcm9wem9uZTogRWxlbWVudCB8IG51bGwgPSBudWxsO1xuXG5jb25zdCBQb3NpdGlvbk1ldGhvZCAgICAgICAgICAgICAgICAgICAgPSAwO1xuY29uc3QgQ2VudGVyTWV0aG9kICAgICAgICAgICAgICAgICAgICAgID0gMTtcbmNvbnN0IENsb3NlTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICA9IDI7XG5jb25zdCBEaXNhYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kICAgICAgPSAzO1xuY29uc3QgRW5hYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kICAgICAgID0gNDtcbmNvbnN0IEZvY3VzTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICA9IDU7XG5jb25zdCBGb3JjZVJlbG9hZE1ldGhvZCAgICAgICAgICAgICAgICAgPSA2O1xuY29uc3QgRnVsbHNjcmVlbk1ldGhvZCAgICAgICAgICAgICAgICAgID0gNztcbmNvbnN0IEdldFNjcmVlbk1ldGhvZCAgICAgICAgICAgICAgICAgICA9IDg7XG5jb25zdCBHZXRab29tTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSA5O1xuY29uc3QgSGVpZ2h0TWV0aG9kICAgICAgICAgICAgICAgICAgICAgID0gMTA7XG5jb25zdCBIaWRlTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSAxMTtcbmNvbnN0IElzRm9jdXNlZE1ldGhvZCAgICAgICAgICAgICAgICAgICA9IDEyO1xuY29uc3QgSXNGdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgICAgID0gMTM7XG5jb25zdCBJc01heGltaXNlZE1ldGhvZCAgICAgICAgICAgICAgICAgPSAxNDtcbmNvbnN0IElzTWluaW1pc2VkTWV0aG9kICAgICAgICAgICAgICAgICA9IDE1O1xuY29uc3QgTWF4aW1pc2VNZXRob2QgICAgICAgICAgICAgICAgICAgID0gMTY7XG5jb25zdCBNaW5pbWlzZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgPSAxNztcbmNvbnN0IE5hbWVNZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDE4O1xuY29uc3QgT3BlbkRldlRvb2xzTWV0aG9kICAgICAgICAgICAgICAgID0gMTk7XG5jb25zdCBSZWxhdGl2ZVBvc2l0aW9uTWV0aG9kICAgICAgICAgICAgPSAyMDtcbmNvbnN0IFJlbG9hZE1ldGhvZCAgICAgICAgICAgICAgICAgICAgICA9IDIxO1xuY29uc3QgUmVzaXphYmxlTWV0aG9kICAgICAgICAgICAgICAgICAgID0gMjI7XG5jb25zdCBSZXN0b3JlTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSAyMztcbmNvbnN0IFNldFBvc2l0aW9uTWV0aG9kICAgICAgICAgICAgICAgICA9IDI0O1xuY29uc3QgU2V0QWx3YXlzT25Ub3BNZXRob2QgICAgICAgICAgICAgID0gMjU7XG5jb25zdCBTZXRCYWNrZ3JvdW5kQ29sb3VyTWV0aG9kICAgICAgICAgPSAyNjtcbmNvbnN0IFNldEZyYW1lbGVzc01ldGhvZCAgICAgICAgICAgICAgICA9IDI3O1xuY29uc3QgU2V0RnVsbHNjcmVlbkJ1dHRvbkVuYWJsZWRNZXRob2QgID0gMjg7XG5jb25zdCBTZXRNYXhTaXplTWV0aG9kICAgICAgICAgICAgICAgICAgPSAyOTtcbmNvbnN0IFNldE1pblNpemVNZXRob2QgICAgICAgICAgICAgICAgICA9IDMwO1xuY29uc3QgU2V0UmVsYXRpdmVQb3NpdGlvbk1ldGhvZCAgICAgICAgID0gMzE7XG5jb25zdCBTZXRSZXNpemFibGVNZXRob2QgICAgICAgICAgICAgICAgPSAzMjtcbmNvbnN0IFNldFNpemVNZXRob2QgICAgICAgICAgICAgICAgICAgICA9IDMzO1xuY29uc3QgU2V0VGl0bGVNZXRob2QgICAgICAgICAgICAgICAgICAgID0gMzQ7XG5jb25zdCBTZXRab29tTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSAzNTtcbmNvbnN0IFNob3dNZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDM2O1xuY29uc3QgU2l6ZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgID0gMzc7XG5jb25zdCBUb2dnbGVGdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgPSAzODtcbmNvbnN0IFRvZ2dsZU1heGltaXNlTWV0aG9kICAgICAgICAgICAgICA9IDM5O1xuY29uc3QgVG9nZ2xlRnJhbWVsZXNzTWV0aG9kICAgICAgICAgICAgID0gNDA7IFxuY29uc3QgVW5GdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgICAgID0gNDE7XG5jb25zdCBVbk1heGltaXNlTWV0aG9kICAgICAgICAgICAgICAgICAgPSA0MjtcbmNvbnN0IFVuTWluaW1pc2VNZXRob2QgICAgICAgICAgICAgICAgICA9IDQzO1xuY29uc3QgV2lkdGhNZXRob2QgICAgICAgICAgICAgICAgICAgICAgID0gNDQ7XG5jb25zdCBab29tTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSA0NTtcbmNvbnN0IFpvb21Jbk1ldGhvZCAgICAgICAgICAgICAgICAgICAgICA9IDQ2O1xuY29uc3QgWm9vbU91dE1ldGhvZCAgICAgICAgICAgICAgICAgICAgID0gNDc7XG5jb25zdCBab29tUmVzZXRNZXRob2QgICAgICAgICAgICAgICAgICAgPSA0ODtcbmNvbnN0IFNuYXBBc3Npc3RNZXRob2QgICAgICAgICAgICAgICAgICA9IDQ5O1xuY29uc3QgV2luZG93RHJvcFpvbmVEcm9wcGVkICAgICAgICAgICAgID0gNTA7XG5jb25zdCBQcmludE1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgPSA1MTtcblxuZnVuY3Rpb24gZ2V0RHJvcHpvbmVFbGVtZW50KGVsZW1lbnQ6IEVsZW1lbnQgfCBudWxsKTogRWxlbWVudCB8IG51bGwge1xuICAgIGlmICghZWxlbWVudCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgLy8gQWxsb3cgZHJvcHpvbmUgYXR0cmlidXRlIHRvIGJlIG9uIHRoZSBlbGVtZW50IGl0c2VsZiBvciBhbnkgcGFyZW50XG4gICAgcmV0dXJuIGVsZW1lbnQuY2xvc2VzdChgWyR7RFJPUFpPTkVfQVRUUklCVVRFfV1gKTtcbn1cblxuLyoqXG4gKiBBIHJlY29yZCBkZXNjcmliaW5nIHRoZSBwb3NpdGlvbiBvZiBhIHdpbmRvdy5cbiAqL1xuaW50ZXJmYWNlIFBvc2l0aW9uIHtcbiAgICAvKiogVGhlIGhvcml6b250YWwgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy4gKi9cbiAgICB4OiBudW1iZXI7XG4gICAgLyoqIFRoZSB2ZXJ0aWNhbCBwb3NpdGlvbiBvZiB0aGUgd2luZG93LiAqL1xuICAgIHk6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBBIHJlY29yZCBkZXNjcmliaW5nIHRoZSBzaXplIG9mIGEgd2luZG93LlxuICovXG5pbnRlcmZhY2UgU2l6ZSB7XG4gICAgLyoqIFRoZSB3aWR0aCBvZiB0aGUgd2luZG93LiAqL1xuICAgIHdpZHRoOiBudW1iZXI7XG4gICAgLyoqIFRoZSBoZWlnaHQgb2YgdGhlIHdpbmRvdy4gKi9cbiAgICBoZWlnaHQ6IG51bWJlcjtcbn1cblxuLy8gUHJpdmF0ZSBmaWVsZCBuYW1lcy5cbmNvbnN0IGNhbGxlclN5bSA9IFN5bWJvbChcImNhbGxlclwiKTtcblxuY2xhc3MgV2luZG93IHtcbiAgICAvLyBQcml2YXRlIGZpZWxkcy5cbiAgICBwcml2YXRlIFtjYWxsZXJTeW1dOiAobWVzc2FnZTogbnVtYmVyLCBhcmdzPzogYW55KSA9PiBQcm9taXNlPGFueT47XG5cbiAgICAvKipcbiAgICAgKiBJbml0aWFsaXNlcyBhIHdpbmRvdyBvYmplY3Qgd2l0aCB0aGUgc3BlY2lmaWVkIG5hbWUuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEBwYXJhbSBuYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHRhcmdldCB3aW5kb3cuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IobmFtZTogc3RyaW5nID0gJycpIHtcbiAgICAgICAgdGhpc1tjYWxsZXJTeW1dID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5XaW5kb3csIG5hbWUpXG5cbiAgICAgICAgLy8gYmluZCBpbnN0YW5jZSBtZXRob2QgdG8gbWFrZSB0aGVtIGVhc2lseSB1c2FibGUgaW4gZXZlbnQgaGFuZGxlcnNcbiAgICAgICAgZm9yIChjb25zdCBtZXRob2Qgb2YgT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoV2luZG93LnByb3RvdHlwZSkpIHtcbiAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICBtZXRob2QgIT09IFwiY29uc3RydWN0b3JcIlxuICAgICAgICAgICAgICAgICYmIHR5cGVvZiAodGhpcyBhcyBhbnkpW21ldGhvZF0gPT09IFwiZnVuY3Rpb25cIlxuICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgKHRoaXMgYXMgYW55KVttZXRob2RdID0gKHRoaXMgYXMgYW55KVttZXRob2RdLmJpbmQodGhpcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXRzIHRoZSBzcGVjaWZpZWQgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIG5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgd2luZG93IHRvIGdldC5cbiAgICAgKiBAcmV0dXJucyBUaGUgY29ycmVzcG9uZGluZyB3aW5kb3cgb2JqZWN0LlxuICAgICAqL1xuICAgIEdldChuYW1lOiBzdHJpbmcpOiBXaW5kb3cge1xuICAgICAgICByZXR1cm4gbmV3IFdpbmRvdyhuYW1lKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgYWJzb2x1dGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBQb3NpdGlvbigpOiBQcm9taXNlPFBvc2l0aW9uPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUG9zaXRpb25NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENlbnRlcnMgdGhlIHdpbmRvdyBvbiB0aGUgc2NyZWVuLlxuICAgICAqL1xuICAgIENlbnRlcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShDZW50ZXJNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENsb3NlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIENsb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKENsb3NlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEaXNhYmxlcyBtaW4vbWF4IHNpemUgY29uc3RyYWludHMuXG4gICAgICovXG4gICAgRGlzYWJsZVNpemVDb25zdHJhaW50cygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShEaXNhYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBFbmFibGVzIG1pbi9tYXggc2l6ZSBjb25zdHJhaW50cy5cbiAgICAgKi9cbiAgICBFbmFibGVTaXplQ29uc3RyYWludHMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRW5hYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGb2N1c2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgRm9jdXMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRm9jdXNNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZvcmNlcyB0aGUgd2luZG93IHRvIHJlbG9hZCB0aGUgcGFnZSBhc3NldHMuXG4gICAgICovXG4gICAgRm9yY2VSZWxvYWQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRm9yY2VSZWxvYWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFN3aXRjaGVzIHRoZSB3aW5kb3cgdG8gZnVsbHNjcmVlbiBtb2RlLlxuICAgICAqL1xuICAgIEZ1bGxzY3JlZW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRnVsbHNjcmVlbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgc2NyZWVuIHRoYXQgdGhlIHdpbmRvdyBpcyBvbi5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBzY3JlZW4gdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgb24uXG4gICAgICovXG4gICAgR2V0U2NyZWVuKCk6IFByb21pc2U8U2NyZWVuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oR2V0U2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBjdXJyZW50IHpvb20gbGV2ZWwgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IHpvb20gbGV2ZWwuXG4gICAgICovXG4gICAgR2V0Wm9vbSgpOiBQcm9taXNlPG51bWJlcj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEdldFpvb21NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgSGVpZ2h0KCk6IFByb21pc2U8bnVtYmVyPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSGVpZ2h0TWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBIaWRlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIEhpZGUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSGlkZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0cnVlIGlmIHRoZSB3aW5kb3cgaXMgZm9jdXNlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgZm9jdXNlZC5cbiAgICAgKi9cbiAgICBJc0ZvY3VzZWQoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSXNGb2N1c2VkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyBmdWxsc2NyZWVuLlxuICAgICAqXG4gICAgICogQHJldHVybnMgV2hldGhlciB0aGUgd2luZG93IGlzIGN1cnJlbnRseSBmdWxsc2NyZWVuLlxuICAgICAqL1xuICAgIElzRnVsbHNjcmVlbigpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShJc0Z1bGxzY3JlZW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIG1heGltaXNlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgbWF4aW1pc2VkLlxuICAgICAqL1xuICAgIElzTWF4aW1pc2VkKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKElzTWF4aW1pc2VkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyBtaW5pbWlzZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBXaGV0aGVyIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IG1pbmltaXNlZC5cbiAgICAgKi9cbiAgICBJc01pbmltaXNlZCgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShJc01pbmltaXNlZE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWF4aW1pc2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgTWF4aW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oTWF4aW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE1pbmltaXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIE1pbmltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKE1pbmltaXNlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBuYW1lIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgbmFtZSBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIE5hbWUoKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShOYW1lTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBPcGVucyB0aGUgZGV2ZWxvcG1lbnQgdG9vbHMgcGFuZS5cbiAgICAgKi9cbiAgICBPcGVuRGV2VG9vbHMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oT3BlbkRldlRvb2xzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93IHRvIHRoZSBzY3JlZW4uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFJlbGF0aXZlUG9zaXRpb24oKTogUHJvbWlzZTxQb3NpdGlvbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFJlbGF0aXZlUG9zaXRpb25NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbG9hZHMgdGhlIHBhZ2UgYXNzZXRzLlxuICAgICAqL1xuICAgIFJlbG9hZCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZWxvYWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIHJlc2l6YWJsZS5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgcmVzaXphYmxlLlxuICAgICAqL1xuICAgIFJlc2l6YWJsZSgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZXNpemFibGVNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlc3RvcmVzIHRoZSB3aW5kb3cgdG8gaXRzIHByZXZpb3VzIHN0YXRlIGlmIGl0IHdhcyBwcmV2aW91c2x5IG1pbmltaXNlZCwgbWF4aW1pc2VkIG9yIGZ1bGxzY3JlZW4uXG4gICAgICovXG4gICAgUmVzdG9yZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZXN0b3JlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHggLSBUaGUgZGVzaXJlZCBob3Jpem9udGFsIGFic29sdXRlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIHkgLSBUaGUgZGVzaXJlZCB2ZXJ0aWNhbCBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldFBvc2l0aW9uKHg6IG51bWJlciwgeTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0UG9zaXRpb25NZXRob2QsIHsgeCwgeSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSB3aW5kb3cgdG8gYmUgYWx3YXlzIG9uIHRvcC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBhbHdheXNPblRvcCAtIFdoZXRoZXIgdGhlIHdpbmRvdyBzaG91bGQgc3RheSBvbiB0b3AuXG4gICAgICovXG4gICAgU2V0QWx3YXlzT25Ub3AoYWx3YXlzT25Ub3A6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRBbHdheXNPblRvcE1ldGhvZCwgeyBhbHdheXNPblRvcCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBiYWNrZ3JvdW5kIGNvbG91ciBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHIgLSBUaGUgZGVzaXJlZCByZWQgY29tcG9uZW50IG9mIHRoZSB3aW5kb3cgYmFja2dyb3VuZC5cbiAgICAgKiBAcGFyYW0gZyAtIFRoZSBkZXNpcmVkIGdyZWVuIGNvbXBvbmVudCBvZiB0aGUgd2luZG93IGJhY2tncm91bmQuXG4gICAgICogQHBhcmFtIGIgLSBUaGUgZGVzaXJlZCBibHVlIGNvbXBvbmVudCBvZiB0aGUgd2luZG93IGJhY2tncm91bmQuXG4gICAgICogQHBhcmFtIGEgLSBUaGUgZGVzaXJlZCBhbHBoYSBjb21wb25lbnQgb2YgdGhlIHdpbmRvdyBiYWNrZ3JvdW5kLlxuICAgICAqL1xuICAgIFNldEJhY2tncm91bmRDb2xvdXIocjogbnVtYmVyLCBnOiBudW1iZXIsIGI6IG51bWJlciwgYTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0QmFja2dyb3VuZENvbG91ck1ldGhvZCwgeyByLCBnLCBiLCBhIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgdGhlIHdpbmRvdyBmcmFtZSBhbmQgdGl0bGUgYmFyLlxuICAgICAqXG4gICAgICogQHBhcmFtIGZyYW1lbGVzcyAtIFdoZXRoZXIgdGhlIHdpbmRvdyBzaG91bGQgYmUgZnJhbWVsZXNzLlxuICAgICAqL1xuICAgIFNldEZyYW1lbGVzcyhmcmFtZWxlc3M6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRGcmFtZWxlc3NNZXRob2QsIHsgZnJhbWVsZXNzIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERpc2FibGVzIHRoZSBzeXN0ZW0gZnVsbHNjcmVlbiBidXR0b24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZW5hYmxlZCAtIFdoZXRoZXIgdGhlIGZ1bGxzY3JlZW4gYnV0dG9uIHNob3VsZCBiZSBlbmFibGVkLlxuICAgICAqL1xuICAgIFNldEZ1bGxzY3JlZW5CdXR0b25FbmFibGVkKGVuYWJsZWQ6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRGdWxsc2NyZWVuQnV0dG9uRW5hYmxlZE1ldGhvZCwgeyBlbmFibGVkIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIG1heGltdW0gc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHdpZHRoIC0gVGhlIGRlc2lyZWQgbWF4aW11bSB3aWR0aCBvZiB0aGUgd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgLSBUaGUgZGVzaXJlZCBtYXhpbXVtIGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldE1heFNpemUod2lkdGg6IG51bWJlciwgaGVpZ2h0OiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRNYXhTaXplTWV0aG9kLCB7IHdpZHRoLCBoZWlnaHQgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgbWluaW11bSBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gd2lkdGggLSBUaGUgZGVzaXJlZCBtaW5pbXVtIHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIGhlaWdodCAtIFRoZSBkZXNpcmVkIG1pbmltdW0gaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0TWluU2l6ZSh3aWR0aDogbnVtYmVyLCBoZWlnaHQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldE1pblNpemVNZXRob2QsIHsgd2lkdGgsIGhlaWdodCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93IHRvIHRoZSBzY3JlZW4uXG4gICAgICpcbiAgICAgKiBAcGFyYW0geCAtIFRoZSBkZXNpcmVkIGhvcml6b250YWwgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0geSAtIFRoZSBkZXNpcmVkIHZlcnRpY2FsIHJlbGF0aXZlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0UmVsYXRpdmVQb3NpdGlvbih4OiBudW1iZXIsIHk6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFJlbGF0aXZlUG9zaXRpb25NZXRob2QsIHsgeCwgeSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyByZXNpemFibGUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gcmVzaXphYmxlIC0gV2hldGhlciB0aGUgd2luZG93IHNob3VsZCBiZSByZXNpemFibGUuXG4gICAgICovXG4gICAgU2V0UmVzaXphYmxlKHJlc2l6YWJsZTogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFJlc2l6YWJsZU1ldGhvZCwgeyByZXNpemFibGUgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHdpZHRoIC0gVGhlIGRlc2lyZWQgd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0gaGVpZ2h0IC0gVGhlIGRlc2lyZWQgaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0U2l6ZSh3aWR0aDogbnVtYmVyLCBoZWlnaHQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFNpemVNZXRob2QsIHsgd2lkdGgsIGhlaWdodCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSB0aXRsZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHRpdGxlIC0gVGhlIGRlc2lyZWQgdGl0bGUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRUaXRsZSh0aXRsZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0VGl0bGVNZXRob2QsIHsgdGl0bGUgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHpvb20gLSBUaGUgZGVzaXJlZCB6b29tIGxldmVsLlxuICAgICAqL1xuICAgIFNldFpvb20oem9vbTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0Wm9vbU1ldGhvZCwgeyB6b29tIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNob3dzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2hvdygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTaG93TWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2l6ZSgpOiBQcm9taXNlPFNpemU+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTaXplTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUb2dnbGVzIHRoZSB3aW5kb3cgYmV0d2VlbiBmdWxsc2NyZWVuIGFuZCBub3JtYWwuXG4gICAgICovXG4gICAgVG9nZ2xlRnVsbHNjcmVlbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShUb2dnbGVGdWxsc2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUb2dnbGVzIHRoZSB3aW5kb3cgYmV0d2VlbiBtYXhpbWlzZWQgYW5kIG5vcm1hbC5cbiAgICAgKi9cbiAgICBUb2dnbGVNYXhpbWlzZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShUb2dnbGVNYXhpbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVG9nZ2xlcyB0aGUgd2luZG93IGJldHdlZW4gZnJhbWVsZXNzIGFuZCBub3JtYWwuXG4gICAgICovXG4gICAgVG9nZ2xlRnJhbWVsZXNzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFRvZ2dsZUZyYW1lbGVzc01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVW4tZnVsbHNjcmVlbnMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBVbkZ1bGxzY3JlZW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVW5GdWxsc2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBVbi1tYXhpbWlzZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBVbk1heGltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFVuTWF4aW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVuLW1pbmltaXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFVuTWluaW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVW5NaW5pbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgV2lkdGgoKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShXaWR0aE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogWm9vbXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBab29tKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFpvb21NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEluY3JlYXNlcyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2VidmlldyBjb250ZW50LlxuICAgICAqL1xuICAgIFpvb21JbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShab29tSW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERlY3JlYXNlcyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2VidmlldyBjb250ZW50LlxuICAgICAqL1xuICAgIFpvb21PdXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oWm9vbU91dE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVzZXRzIHRoZSB6b29tIGxldmVsIG9mIHRoZSB3ZWJ2aWV3IGNvbnRlbnQuXG4gICAgICovXG4gICAgWm9vbVJlc2V0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFpvb21SZXNldE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSGFuZGxlcyBmaWxlIGRyb3BzIG9yaWdpbmF0aW5nIGZyb20gcGxhdGZvcm0tc3BlY2lmaWMgY29kZSAoZS5nLiwgbWFjT1MgbmF0aXZlIGRyYWctYW5kLWRyb3ApLlxuICAgICAqIEdhdGhlcnMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRyb3AgdGFyZ2V0IGVsZW1lbnQgYW5kIHNlbmRzIGl0IGJhY2sgdG8gdGhlIEdvIGJhY2tlbmQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZmlsZW5hbWVzIC0gQW4gYXJyYXkgb2YgZmlsZSBwYXRocyAoc3RyaW5ncykgdGhhdCB3ZXJlIGRyb3BwZWQuXG4gICAgICogQHBhcmFtIHggLSBUaGUgeC1jb29yZGluYXRlIG9mIHRoZSBkcm9wIGV2ZW50LlxuICAgICAqIEBwYXJhbSB5IC0gVGhlIHktY29vcmRpbmF0ZSBvZiB0aGUgZHJvcCBldmVudC5cbiAgICAgKi9cbiAgICBIYW5kbGVQbGF0Zm9ybUZpbGVEcm9wKGZpbGVuYW1lczogc3RyaW5nW10sIHg6IG51bWJlciwgeTogbnVtYmVyKTogdm9pZCB7XG4gICAgICAgIGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHgsIHkpO1xuXG4gICAgICAgIC8vIE5FVzogQ2hlY2sgaWYgdGhlIGRyb3AgdGFyZ2V0IGlzIGEgdmFsaWQgZHJvcHpvbmVcbiAgICAgICAgY29uc3QgZHJvcHpvbmVUYXJnZXQgPSBnZXREcm9wem9uZUVsZW1lbnQoZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKCFkcm9wem9uZVRhcmdldCkge1xuICAgICAgICAgICAgY29uc29sZS5sb2coYFdhaWxzIFJ1bnRpbWU6IERyb3Agb24gZWxlbWVudCAob3Igbm8gZWxlbWVudCkgYXQgJHt4fSwke3l9IHdoaWNoIGlzIG5vdCBhIGRlc2lnbmF0ZWQgZHJvcHpvbmUuIElnbm9yaW5nLiBFbGVtZW50OmAsIGVsZW1lbnQpO1xuICAgICAgICAgICAgLy8gTm8gbmVlZCB0byBjYWxsIGJhY2tlbmQgaWYgbm90IGEgdmFsaWQgZHJvcHpvbmUgdGFyZ2V0XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjb25zb2xlLmxvZyhgV2FpbHMgUnVudGltZTogRHJvcCBvbiBkZXNpZ25hdGVkIGRyb3B6b25lLiBFbGVtZW50IGF0ICgke3h9LCAke3l9KTpgLCBlbGVtZW50LCAnRWZmZWN0aXZlIGRyb3B6b25lOicsIGRyb3B6b25lVGFyZ2V0KTtcbiAgICAgICAgY29uc3QgZWxlbWVudERldGFpbHMgPSB7XG4gICAgICAgICAgICBpZDogZHJvcHpvbmVUYXJnZXQuaWQsXG4gICAgICAgICAgICBjbGFzc0xpc3Q6IEFycmF5LmZyb20oZHJvcHpvbmVUYXJnZXQuY2xhc3NMaXN0KSxcbiAgICAgICAgICAgIGF0dHJpYnV0ZXM6IHt9IGFzIHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH0sXG4gICAgICAgIH07XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZHJvcHpvbmVUYXJnZXQuYXR0cmlidXRlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgY29uc3QgYXR0ciA9IGRyb3B6b25lVGFyZ2V0LmF0dHJpYnV0ZXNbaV07XG4gICAgICAgICAgICBlbGVtZW50RGV0YWlscy5hdHRyaWJ1dGVzW2F0dHIubmFtZV0gPSBhdHRyLnZhbHVlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgIGZpbGVuYW1lcyxcbiAgICAgICAgICAgIHgsXG4gICAgICAgICAgICB5LFxuICAgICAgICAgICAgZWxlbWVudERldGFpbHMsXG4gICAgICAgIH07XG5cbiAgICAgICAgdGhpc1tjYWxsZXJTeW1dKFdpbmRvd0Ryb3Bab25lRHJvcHBlZCwgcGF5bG9hZCk7XG4gICAgfVxuICBcbiAgICAvKiBUcmlnZ2VycyBXaW5kb3dzIDExIFNuYXAgQXNzaXN0IGZlYXR1cmUgKFdpbmRvd3Mgb25seSkuXG4gICAgICogVGhpcyBpcyBlcXVpdmFsZW50IHRvIHByZXNzaW5nIFdpbitaIGFuZCBzaG93cyBzbmFwIGxheW91dCBvcHRpb25zLlxuICAgICAqL1xuICAgIFNuYXBBc3Npc3QoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU25hcEFzc2lzdE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogT3BlbnMgdGhlIHByaW50IGRpYWxvZyBmb3IgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBQcmludCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShQcmludE1ldGhvZCk7XG4gICAgfVxufVxuXG4vKipcbiAqIFRoZSB3aW5kb3cgd2l0aGluIHdoaWNoIHRoZSBzY3JpcHQgaXMgcnVubmluZy5cbiAqL1xuY29uc3QgdGhpc1dpbmRvdyA9IG5ldyBXaW5kb3coJycpO1xuXG4vLyBORVc6IEdsb2JhbCBEcmFnIEV2ZW50IExpc3RlbmVyc1xuZnVuY3Rpb24gc2V0dXBHbG9iYWxEcm9wem9uZUxpc3RlbmVycygpIHtcbiAgICBjb25zdCBkb2NFbGVtZW50ID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50O1xuICAgIGxldCBkcmFnRW50ZXJDb3VudGVyID0gMDsgLy8gVG8gaGFuZGxlIGRyYWdlbnRlci9kcmFnbGVhdmUgb24gY2hpbGQgZWxlbWVudHNcblxuICAgIGRvY0VsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignZHJhZ2VudGVyJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGlmIChldmVudC5kYXRhVHJhbnNmZXIgJiYgZXZlbnQuZGF0YVRyYW5zZmVyLnR5cGVzLmluY2x1ZGVzKCdGaWxlcycpKSB7XG4gICAgICAgICAgICBkcmFnRW50ZXJDb3VudGVyKys7XG4gICAgICAgICAgICBjb25zdCB0YXJnZXRFbGVtZW50ID0gZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZKTtcbiAgICAgICAgICAgIGNvbnN0IGRyb3B6b25lID0gZ2V0RHJvcHpvbmVFbGVtZW50KHRhcmdldEVsZW1lbnQpO1xuXG4gICAgICAgICAgICAvLyBDbGVhciBwcmV2aW91cyBob3ZlciByZWdhcmRsZXNzLCB0aGVuIGFwcGx5IG5ldyBpZiB2YWxpZFxuICAgICAgICAgICAgaWYgKGN1cnJlbnRIb3ZlcmVkRHJvcHpvbmUgJiYgY3VycmVudEhvdmVyZWREcm9wem9uZSAhPT0gZHJvcHpvbmUpIHtcbiAgICAgICAgICAgICAgICBjdXJyZW50SG92ZXJlZERyb3B6b25lLmNsYXNzTGlzdC5yZW1vdmUoRFJPUFpPTkVfSE9WRVJfQ0xBU1MpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoZHJvcHpvbmUpIHtcbiAgICAgICAgICAgICAgICBkcm9wem9uZS5jbGFzc0xpc3QuYWRkKERST1BaT05FX0hPVkVSX0NMQVNTKTtcbiAgICAgICAgICAgICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdjb3B5JztcbiAgICAgICAgICAgICAgICBjdXJyZW50SG92ZXJlZERyb3B6b25lID0gZHJvcHpvbmU7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGV2ZW50LmRhdGFUcmFuc2Zlci5kcm9wRWZmZWN0ID0gJ25vbmUnO1xuICAgICAgICAgICAgICAgIGN1cnJlbnRIb3ZlcmVkRHJvcHpvbmUgPSBudWxsOyAvLyBFbnN1cmUgaXQncyBjbGVhcmVkIGlmIG5vIGRyb3B6b25lIGZvdW5kXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9LCBmYWxzZSk7XG5cbiAgICBkb2NFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2RyYWdvdmVyJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7IC8vIE5lY2Vzc2FyeSB0byBhbGxvdyBkcm9wXG4gICAgICAgIGlmIChldmVudC5kYXRhVHJhbnNmZXIgJiYgZXZlbnQuZGF0YVRyYW5zZmVyLnR5cGVzLmluY2x1ZGVzKCdGaWxlcycpKSB7XG4gICAgICAgICAgICAvLyBObyBuZWVkIHRvIHF1ZXJ5IGVsZW1lbnRGcm9tUG9pbnQgYWdhaW4gaWYgYWxyZWFkeSBoYW5kbGVkIGJ5IGRyYWdlbnRlciBjb3JyZWN0bHlcbiAgICAgICAgICAgIC8vIEp1c3QgZW5zdXJlIGRyb3BFZmZlY3QgaXMgY29udGludW91c2x5IHNldCBiYXNlZCBvbiBjdXJyZW50SG92ZXJlZERyb3B6b25lXG4gICAgICAgICAgICBpZiAoY3VycmVudEhvdmVyZWREcm9wem9uZSkge1xuICAgICAgICAgICAgICAgICAvLyBSZS1hcHBseSBjbGFzcyBqdXN0IGluIGNhc2UgaXQgd2FzIHJlbW92ZWQgYnkgc29tZSBvdGhlciBKU1xuICAgICAgICAgICAgICAgIGlmKCFjdXJyZW50SG92ZXJlZERyb3B6b25lLmNsYXNzTGlzdC5jb250YWlucyhEUk9QWk9ORV9IT1ZFUl9DTEFTUykpIHtcbiAgICAgICAgICAgICAgICAgICAgY3VycmVudEhvdmVyZWREcm9wem9uZS5jbGFzc0xpc3QuYWRkKERST1BaT05FX0hPVkVSX0NMQVNTKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnY29weSc7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGV2ZW50LmRhdGFUcmFuc2Zlci5kcm9wRWZmZWN0ID0gJ25vbmUnO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSwgZmFsc2UpO1xuXG4gICAgZG9jRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCAoZXZlbnQpID0+IHtcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgaWYgKGV2ZW50LmRhdGFUcmFuc2ZlciAmJiBldmVudC5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoJ0ZpbGVzJykpIHtcbiAgICAgICAgICAgIGRyYWdFbnRlckNvdW50ZXItLTtcbiAgICAgICAgICAgIC8vIE9ubHkgcmVtb3ZlIGhvdmVyIGlmIGRyYWcgdHJ1bHkgbGVmdCB0aGUgd2luZG93IG9yIHRoZSBsYXN0IGRyb3B6b25lXG4gICAgICAgICAgICBpZiAoZHJhZ0VudGVyQ291bnRlciA9PT0gMCB8fCBldmVudC5yZWxhdGVkVGFyZ2V0ID09PSBudWxsIHx8IChjdXJyZW50SG92ZXJlZERyb3B6b25lICYmICFjdXJyZW50SG92ZXJlZERyb3B6b25lLmNvbnRhaW5zKGV2ZW50LnJlbGF0ZWRUYXJnZXQgYXMgTm9kZSkpKSB7XG4gICAgICAgICAgICAgICAgaWYgKGN1cnJlbnRIb3ZlcmVkRHJvcHpvbmUpIHtcbiAgICAgICAgICAgICAgICAgICAgY3VycmVudEhvdmVyZWREcm9wem9uZS5jbGFzc0xpc3QucmVtb3ZlKERST1BaT05FX0hPVkVSX0NMQVNTKTtcbiAgICAgICAgICAgICAgICAgICAgY3VycmVudEhvdmVyZWREcm9wem9uZSA9IG51bGw7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGRyYWdFbnRlckNvdW50ZXIgPSAwOyAvLyBSZXNldCBjb3VudGVyIGlmIGl0IHdlbnQgbmVnYXRpdmUgb3IgbGVmdCB3aW5kb3dcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sIGZhbHNlKTtcblxuICAgIGRvY0VsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignZHJvcCcsIChldmVudCkgPT4ge1xuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpOyAvLyBQcmV2ZW50IGRlZmF1bHQgYnJvd3NlciBmaWxlIGhhbmRsaW5nXG4gICAgICAgIGRyYWdFbnRlckNvdW50ZXIgPSAwOyAvLyBSZXNldCBjb3VudGVyXG4gICAgICAgIGlmIChjdXJyZW50SG92ZXJlZERyb3B6b25lKSB7XG4gICAgICAgICAgICBjdXJyZW50SG92ZXJlZERyb3B6b25lLmNsYXNzTGlzdC5yZW1vdmUoRFJPUFpPTkVfSE9WRVJfQ0xBU1MpO1xuICAgICAgICAgICAgY3VycmVudEhvdmVyZWREcm9wem9uZSA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgLy8gVGhlIGFjdHVhbCBkcm9wIHByb2Nlc3NpbmcgaXMgaW5pdGlhdGVkIGJ5IHRoZSBuYXRpdmUgc2lkZSBjYWxsaW5nIEhhbmRsZVBsYXRmb3JtRmlsZURyb3BcbiAgICAgICAgLy8gSGFuZGxlUGxhdGZvcm1GaWxlRHJvcCB3aWxsIHRoZW4gY2hlY2sgaWYgdGhlIGRyb3Agd2FzIG9uIGEgdmFsaWQgem9uZS5cbiAgICB9LCBmYWxzZSk7XG59XG5cbi8vIEluaXRpYWxpemUgbGlzdGVuZXJzIHdoZW4gdGhlIHNjcmlwdCBsb2Fkc1xuaWYgKHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgJiYgdHlwZW9mIGRvY3VtZW50ICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgc2V0dXBHbG9iYWxEcm9wem9uZUxpc3RlbmVycygpO1xufVxuXG5leHBvcnQgZGVmYXVsdCB0aGlzV2luZG93O1xuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgKiBhcyBSdW50aW1lIGZyb20gXCIuLi9Ad2FpbHNpby9ydW50aW1lL3NyY1wiO1xuXG4vLyBOT1RFOiB0aGUgZm9sbG93aW5nIG1ldGhvZHMgTVVTVCBiZSBpbXBvcnRlZCBleHBsaWNpdGx5IGJlY2F1c2Ugb2YgaG93IGVzYnVpbGQgaW5qZWN0aW9uIHdvcmtzXG5pbXBvcnQgeyBFbmFibGUgYXMgRW5hYmxlV01MIH0gZnJvbSBcIi4uL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3dtbFwiO1xuaW1wb3J0IHsgZGVidWdMb2cgfSBmcm9tIFwiLi4vQHdhaWxzaW8vcnVudGltZS9zcmMvdXRpbHNcIjtcblxud2luZG93LndhaWxzID0gUnVudGltZTtcbkVuYWJsZVdNTCgpO1xuXG5pZiAoREVCVUcpIHtcbiAgICBkZWJ1Z0xvZyhcIldhaWxzIFJ1bnRpbWUgTG9hZGVkXCIpXG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5TeXN0ZW0pO1xuXG5jb25zdCBTeXN0ZW1Jc0RhcmtNb2RlID0gMDtcbmNvbnN0IFN5c3RlbUVudmlyb25tZW50ID0gMTtcbmNvbnN0IFN5c3RlbUNhcGFiaWxpdGllcyA9IDI7XG5jb25zdCBBcHBsaWNhdGlvbkZpbGVzRHJvcHBlZFdpdGhDb250ZXh0ID0gMTAwOyAvLyBOZXcgbWV0aG9kIElEIGZvciBlbnJpY2hlZCBkcm9wIGV2ZW50XG5cbmNvbnN0IF9pbnZva2UgPSAoZnVuY3Rpb24gKCkge1xuICAgIHRyeSB7XG4gICAgICAgIC8vIFdpbmRvd3MgV2ViVmlldzJcbiAgICAgICAgaWYgKCh3aW5kb3cgYXMgYW55KS5jaHJvbWU/LndlYnZpZXc/LnBvc3RNZXNzYWdlKSB7XG4gICAgICAgICAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLmNocm9tZS53ZWJ2aWV3LnBvc3RNZXNzYWdlLmJpbmQoKHdpbmRvdyBhcyBhbnkpLmNocm9tZS53ZWJ2aWV3KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBtYWNPUy9pT1MgV0tXZWJWaWV3XG4gICAgICAgIGVsc2UgaWYgKCh3aW5kb3cgYXMgYW55KS53ZWJraXQ/Lm1lc3NhZ2VIYW5kbGVycz8uWydleHRlcm5hbCddPy5wb3N0TWVzc2FnZSkge1xuICAgICAgICAgICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS53ZWJraXQubWVzc2FnZUhhbmRsZXJzWydleHRlcm5hbCddLnBvc3RNZXNzYWdlLmJpbmQoKHdpbmRvdyBhcyBhbnkpLndlYmtpdC5tZXNzYWdlSGFuZGxlcnNbJ2V4dGVybmFsJ10pO1xuICAgICAgICB9XG4gICAgICAgIC8vIEFuZHJvaWQgV2ViVmlldyAtIHVzZXMgYWRkSmF2YXNjcmlwdEludGVyZmFjZSB3aGljaCBleHBvc2VzIHdpbmRvdy53YWlscy5pbnZva2VcbiAgICAgICAgZWxzZSBpZiAoKHdpbmRvdyBhcyBhbnkpLndhaWxzPy5pbnZva2UpIHtcbiAgICAgICAgICAgIHJldHVybiAobXNnOiBhbnkpID0+ICh3aW5kb3cgYXMgYW55KS53YWlscy5pbnZva2UodHlwZW9mIG1zZyA9PT0gJ3N0cmluZycgPyBtc2cgOiBKU09OLnN0cmluZ2lmeShtc2cpKTtcbiAgICAgICAgfVxuICAgIH0gY2F0Y2goZSkge31cblxuICAgIGNvbnNvbGUud2FybignXFxuJWNcdTI2QTBcdUZFMEYgQnJvd3NlciBFbnZpcm9ubWVudCBEZXRlY3RlZCAlY1xcblxcbiVjT25seSBVSSBwcmV2aWV3cyBhcmUgYXZhaWxhYmxlIGluIHRoZSBicm93c2VyLiBGb3IgZnVsbCBmdW5jdGlvbmFsaXR5LCBwbGVhc2UgcnVuIHRoZSBhcHBsaWNhdGlvbiBpbiBkZXNrdG9wIG1vZGUuXFxuTW9yZSBpbmZvcm1hdGlvbiBhdDogaHR0cHM6Ly92My53YWlscy5pby9sZWFybi9idWlsZC8jdXNpbmctYS1icm93c2VyLWZvci1kZXZlbG9wbWVudFxcbicsXG4gICAgICAgICdiYWNrZ3JvdW5kOiAjZmZmZmZmOyBjb2xvcjogIzAwMDAwMDsgZm9udC13ZWlnaHQ6IGJvbGQ7IHBhZGRpbmc6IDRweCA4cHg7IGJvcmRlci1yYWRpdXM6IDRweDsgYm9yZGVyOiAycHggc29saWQgIzAwMDAwMDsnLFxuICAgICAgICAnYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7JyxcbiAgICAgICAgJ2NvbG9yOiAjZmZmZmZmOyBmb250LXN0eWxlOiBpdGFsaWM7IGZvbnQtd2VpZ2h0OiBib2xkOycpO1xuICAgIHJldHVybiBudWxsO1xufSkoKTtcblxuZXhwb3J0IGZ1bmN0aW9uIGludm9rZShtc2c6IGFueSk6IHZvaWQge1xuICAgIF9pbnZva2U/Lihtc2cpO1xufVxuXG4vKipcbiAqIFJldHJpZXZlcyB0aGUgc3lzdGVtIGRhcmsgbW9kZSBzdGF0dXMuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gYSBib29sZWFuIHZhbHVlIGluZGljYXRpbmcgaWYgdGhlIHN5c3RlbSBpcyBpbiBkYXJrIG1vZGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0RhcmtNb2RlKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgIHJldHVybiBjYWxsKFN5c3RlbUlzRGFya01vZGUpO1xufVxuXG4vKipcbiAqIEZldGNoZXMgdGhlIGNhcGFiaWxpdGllcyBvZiB0aGUgYXBwbGljYXRpb24gZnJvbSB0aGUgc2VydmVyLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGFuIG9iamVjdCBjb250YWluaW5nIHRoZSBjYXBhYmlsaXRpZXMuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBDYXBhYmlsaXRpZXMoKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBhbnk+PiB7XG4gICAgcmV0dXJuIGNhbGwoU3lzdGVtQ2FwYWJpbGl0aWVzKTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBPU0luZm8ge1xuICAgIC8qKiBUaGUgYnJhbmRpbmcgb2YgdGhlIE9TLiAqL1xuICAgIEJyYW5kaW5nOiBzdHJpbmc7XG4gICAgLyoqIFRoZSBJRCBvZiB0aGUgT1MuICovXG4gICAgSUQ6IHN0cmluZztcbiAgICAvKiogVGhlIG5hbWUgb2YgdGhlIE9TLiAqL1xuICAgIE5hbWU6IHN0cmluZztcbiAgICAvKiogVGhlIHZlcnNpb24gb2YgdGhlIE9TLiAqL1xuICAgIFZlcnNpb246IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBFbnZpcm9ubWVudEluZm8ge1xuICAgIC8qKiBUaGUgYXJjaGl0ZWN0dXJlIG9mIHRoZSBzeXN0ZW0uICovXG4gICAgQXJjaDogc3RyaW5nO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBhcHBsaWNhdGlvbiBpcyBydW5uaW5nIGluIGRlYnVnIG1vZGUsIG90aGVyd2lzZSBmYWxzZS4gKi9cbiAgICBEZWJ1ZzogYm9vbGVhbjtcbiAgICAvKiogVGhlIG9wZXJhdGluZyBzeXN0ZW0gaW4gdXNlLiAqL1xuICAgIE9TOiBzdHJpbmc7XG4gICAgLyoqIERldGFpbHMgb2YgdGhlIG9wZXJhdGluZyBzeXN0ZW0uICovXG4gICAgT1NJbmZvOiBPU0luZm87XG4gICAgLyoqIEFkZGl0aW9uYWwgcGxhdGZvcm0gaW5mb3JtYXRpb24uICovXG4gICAgUGxhdGZvcm1JbmZvOiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xufVxuXG4vKipcbiAqIFJldHJpZXZlcyBlbnZpcm9ubWVudCBkZXRhaWxzLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGFuIG9iamVjdCBjb250YWluaW5nIE9TIGFuZCBzeXN0ZW0gYXJjaGl0ZWN0dXJlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gRW52aXJvbm1lbnQoKTogUHJvbWlzZTxFbnZpcm9ubWVudEluZm8+IHtcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1FbnZpcm9ubWVudCk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IG9wZXJhdGluZyBzeXN0ZW0gaXMgV2luZG93cy5cbiAqXG4gKiBAcmV0dXJuIFRydWUgaWYgdGhlIG9wZXJhdGluZyBzeXN0ZW0gaXMgV2luZG93cywgb3RoZXJ3aXNlIGZhbHNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNXaW5kb3dzKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uT1MgPT09IFwid2luZG93c1wiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBvcGVyYXRpbmcgc3lzdGVtIGlzIExpbnV4LlxuICpcbiAqIEByZXR1cm5zIFJldHVybnMgdHJ1ZSBpZiB0aGUgY3VycmVudCBvcGVyYXRpbmcgc3lzdGVtIGlzIExpbnV4LCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0xpbnV4KCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uT1MgPT09IFwibGludXhcIjtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgZW52aXJvbm1lbnQgaXMgYSBtYWNPUyBvcGVyYXRpbmcgc3lzdGVtLlxuICpcbiAqIEByZXR1cm5zIFRydWUgaWYgdGhlIGVudmlyb25tZW50IGlzIG1hY09TLCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc01hYygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/Lk9TID09PSBcImRhcndpblwiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBhcmNoaXRlY3R1cmUgaXMgQU1ENjQuXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBhcmNoaXRlY3R1cmUgaXMgQU1ENjQsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzQU1ENjQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5BcmNoID09PSBcImFtZDY0XCI7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IGFyY2hpdGVjdHVyZSBpcyBBUk0uXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgY3VycmVudCBhcmNoaXRlY3R1cmUgaXMgQVJNLCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0FSTSgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/LkFyY2ggPT09IFwiYXJtXCI7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IGVudmlyb25tZW50IGlzIEFSTTY0IGFyY2hpdGVjdHVyZS5cbiAqXG4gKiBAcmV0dXJucyBSZXR1cm5zIHRydWUgaWYgdGhlIGVudmlyb25tZW50IGlzIEFSTTY0IGFyY2hpdGVjdHVyZSwgb3RoZXJ3aXNlIHJldHVybnMgZmFsc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0FSTTY0KCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uQXJjaCA9PT0gXCJhcm02NFwiO1xufVxuXG4vKipcbiAqIFJlcG9ydHMgd2hldGhlciB0aGUgYXBwIGlzIGJlaW5nIHJ1biBpbiBkZWJ1ZyBtb2RlLlxuICpcbiAqIEByZXR1cm5zIFRydWUgaWYgdGhlIGFwcCBpcyBiZWluZyBydW4gaW4gZGVidWcgbW9kZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzRGVidWcoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIEJvb2xlYW4oKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/LkRlYnVnKTtcbn1cblxuLyoqXG4gKiBIYW5kbGVzIGZpbGUgZHJvcHMgb3JpZ2luYXRpbmcgZnJvbSBwbGF0Zm9ybS1zcGVjaWZpYyBjb2RlIChlLmcuLCBtYWNPUyBuYXRpdmUgZHJhZy1hbmQtZHJvcCkuXG4gKiBHYXRoZXJzIGluZm9ybWF0aW9uIGFib3V0IHRoZSBkcm9wIHRhcmdldCBlbGVtZW50IGFuZCBzZW5kcyBpdCBiYWNrIHRvIHRoZSBHbyBiYWNrZW5kLlxuICpcbiAqIEBwYXJhbSBmaWxlbmFtZXMgLSBBbiBhcnJheSBvZiBmaWxlIHBhdGhzIChzdHJpbmdzKSB0aGF0IHdlcmUgZHJvcHBlZC5cbiAqIEBwYXJhbSB4IC0gVGhlIHgtY29vcmRpbmF0ZSBvZiB0aGUgZHJvcCBldmVudC5cbiAqIEBwYXJhbSB5IC0gVGhlIHktY29vcmRpbmF0ZSBvZiB0aGUgZHJvcCBldmVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEhhbmRsZVBsYXRmb3JtRmlsZURyb3AoZmlsZW5hbWVzOiBzdHJpbmdbXSwgeDogbnVtYmVyLCB5OiBudW1iZXIpOiB2b2lkIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludCh4LCB5KTtcbiAgICBjb25zdCBlbGVtZW50SWQgPSBlbGVtZW50ID8gZWxlbWVudC5pZCA6ICcnO1xuICAgIGNvbnN0IGNsYXNzTGlzdCA9IGVsZW1lbnQgPyBBcnJheS5mcm9tKGVsZW1lbnQuY2xhc3NMaXN0KSA6IFtdO1xuXG4gICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgZmlsZW5hbWVzLFxuICAgICAgICB4LFxuICAgICAgICB5LFxuICAgICAgICBlbGVtZW50SWQsXG4gICAgICAgIGNsYXNzTGlzdCxcbiAgICB9O1xuXG4gICAgY2FsbChBcHBsaWNhdGlvbkZpbGVzRHJvcHBlZFdpdGhDb250ZXh0LCBwYXlsb2FkKVxuICAgICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgICAgICAvLyBPcHRpb25hbDogTG9nIHN1Y2Nlc3Mgb3IgaGFuZGxlIGlmIG5lZWRlZFxuICAgICAgICAgICAgY29uc29sZS5sb2coXCJQbGF0Zm9ybSBmaWxlIGRyb3AgcHJvY2Vzc2VkIGFuZCBzZW50IHRvIEdvLlwiKTtcbiAgICAgICAgfSlcbiAgICAgICAgLmNhdGNoKGVyciA9PiB7XG4gICAgICAgICAgICAvLyBPcHRpb25hbDogTG9nIGVycm9yXG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiRXJyb3Igc2VuZGluZyBwbGF0Zm9ybSBmaWxlIGRyb3AgdG8gR286XCIsIGVycik7XG4gICAgICAgIH0pO1xufVxuXG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgSXNEZWJ1ZyB9IGZyb20gXCIuL3N5c3RlbS5qc1wiO1xuaW1wb3J0IHsgZXZlbnRUYXJnZXQgfSBmcm9tIFwiLi91dGlscy5qc1wiO1xuXG4vLyBzZXR1cFxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2NvbnRleHRtZW51JywgY29udGV4dE1lbnVIYW5kbGVyKTtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQ29udGV4dE1lbnUpO1xuXG5jb25zdCBDb250ZXh0TWVudU9wZW4gPSAwO1xuXG5mdW5jdGlvbiBvcGVuQ29udGV4dE1lbnUoaWQ6IHN0cmluZywgeDogbnVtYmVyLCB5OiBudW1iZXIsIGRhdGE6IGFueSk6IHZvaWQge1xuICAgIHZvaWQgY2FsbChDb250ZXh0TWVudU9wZW4sIHtpZCwgeCwgeSwgZGF0YX0pO1xufVxuXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQ6IE1vdXNlRXZlbnQpIHtcbiAgICBjb25zdCB0YXJnZXQgPSBldmVudFRhcmdldChldmVudCk7XG5cbiAgICAvLyBDaGVjayBmb3IgY3VzdG9tIGNvbnRleHQgbWVudVxuICAgIGNvbnN0IGN1c3RvbUNvbnRleHRNZW51ID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUodGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1jdXN0b20tY29udGV4dG1lbnVcIikudHJpbSgpO1xuXG4gICAgaWYgKGN1c3RvbUNvbnRleHRNZW51KSB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGNvbnN0IGRhdGEgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0YXJnZXQpLmdldFByb3BlcnR5VmFsdWUoXCItLWN1c3RvbS1jb250ZXh0bWVudS1kYXRhXCIpO1xuICAgICAgICBvcGVuQ29udGV4dE1lbnUoY3VzdG9tQ29udGV4dE1lbnUsIGV2ZW50LmNsaWVudFgsIGV2ZW50LmNsaWVudFksIGRhdGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQsIHRhcmdldCk7XG4gICAgfVxufVxuXG5cbi8qXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGF1dG87IChkZWZhdWx0KSB3aWxsIHNob3cgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51IGlmIGNvbnRlbnRFZGl0YWJsZSBpcyB0cnVlIE9SIHRleHQgaGFzIGJlZW4gc2VsZWN0ZWQgT1IgZWxlbWVudCBpcyBpbnB1dCBvciB0ZXh0YXJlYVxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBzaG93OyB3aWxsIGFsd2F5cyBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBoaWRlOyB3aWxsIGFsd2F5cyBoaWRlIHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuXG5UaGlzIHJ1bGUgaXMgaW5oZXJpdGVkIGxpa2Ugbm9ybWFsIENTUyBydWxlcywgc28gbmVzdGluZyB3b3JrcyBhcyBleHBlY3RlZFxuKi9cbmZ1bmN0aW9uIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQ6IE1vdXNlRXZlbnQsIHRhcmdldDogSFRNTEVsZW1lbnQpIHtcbiAgICAvLyBEZWJ1ZyBidWlsZHMgYWx3YXlzIHNob3cgdGhlIG1lbnVcbiAgICBpZiAoSXNEZWJ1ZygpKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XG4gICAgc3dpdGNoICh3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0YXJnZXQpLmdldFByb3BlcnR5VmFsdWUoXCItLWRlZmF1bHQtY29udGV4dG1lbnVcIikudHJpbSgpKSB7XG4gICAgICAgIGNhc2UgJ3Nob3cnOlxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjYXNlICdoaWRlJzpcbiAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgaWYgY29udGVudEVkaXRhYmxlIGlzIHRydWVcbiAgICBpZiAodGFyZ2V0LmlzQ29udGVudEVkaXRhYmxlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkXG4gICAgY29uc3Qgc2VsZWN0aW9uID0gd2luZG93LmdldFNlbGVjdGlvbigpO1xuICAgIGNvbnN0IGhhc1NlbGVjdGlvbiA9IHNlbGVjdGlvbiAmJiBzZWxlY3Rpb24udG9TdHJpbmcoKS5sZW5ndGggPiAwO1xuICAgIGlmIChoYXNTZWxlY3Rpb24pIHtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzZWxlY3Rpb24ucmFuZ2VDb3VudDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCByYW5nZSA9IHNlbGVjdGlvbi5nZXRSYW5nZUF0KGkpO1xuICAgICAgICAgICAgY29uc3QgcmVjdHMgPSByYW5nZS5nZXRDbGllbnRSZWN0cygpO1xuICAgICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCByZWN0cy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlY3QgPSByZWN0c1tqXTtcbiAgICAgICAgICAgICAgICBpZiAoZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChyZWN0LmxlZnQsIHJlY3QudG9wKSA9PT0gdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiB0YWcgaXMgaW5wdXQgb3IgdGV4dGFyZWEuXG4gICAgaWYgKHRhcmdldCBpbnN0YW5jZW9mIEhUTUxJbnB1dEVsZW1lbnQgfHwgdGFyZ2V0IGluc3RhbmNlb2YgSFRNTFRleHRBcmVhRWxlbWVudCkge1xuICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghdGFyZ2V0LnJlYWRPbmx5ICYmICF0YXJnZXQuZGlzYWJsZWQpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBoaWRlIGRlZmF1bHQgY29udGV4dCBtZW51XG4gICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoqXG4gKiBSZXRyaWV2ZXMgdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGUgc3BlY2lmaWVkIGtleSBmcm9tIHRoZSBmbGFnIG1hcC5cbiAqXG4gKiBAcGFyYW0ga2V5IC0gVGhlIGtleSB0byByZXRyaWV2ZSB0aGUgdmFsdWUgZm9yLlxuICogQHJldHVybiBUaGUgdmFsdWUgYXNzb2NpYXRlZCB3aXRoIHRoZSBzcGVjaWZpZWQga2V5LlxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0RmxhZyhrZXk6IHN0cmluZyk6IGFueSB7XG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIHdpbmRvdy5fd2FpbHMuZmxhZ3Nba2V5XTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIlVuYWJsZSB0byByZXRyaWV2ZSBmbGFnICdcIiArIGtleSArIFwiJzogXCIgKyBlLCB7IGNhdXNlOiBlIH0pO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgaW52b2tlLCBJc1dpbmRvd3MgfSBmcm9tIFwiLi9zeXN0ZW0uanNcIjtcbmltcG9ydCB7IEdldEZsYWcgfSBmcm9tIFwiLi9mbGFncy5qc1wiO1xuaW1wb3J0IHsgY2FuVHJhY2tCdXR0b25zLCBldmVudFRhcmdldCB9IGZyb20gXCIuL3V0aWxzLmpzXCI7XG5cbi8vIFNldHVwXG5sZXQgY2FuRHJhZyA9IGZhbHNlO1xubGV0IGRyYWdnaW5nID0gZmFsc2U7XG5cbmxldCByZXNpemFibGUgPSBmYWxzZTtcbmxldCBjYW5SZXNpemUgPSBmYWxzZTtcbmxldCByZXNpemluZyA9IGZhbHNlO1xubGV0IHJlc2l6ZUVkZ2U6IHN0cmluZyA9IFwiXCI7XG5sZXQgZGVmYXVsdEN1cnNvciA9IFwiYXV0b1wiO1xuXG5sZXQgYnV0dG9ucyA9IDA7XG5jb25zdCBidXR0b25zVHJhY2tlZCA9IGNhblRyYWNrQnV0dG9ucygpO1xuXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcbndpbmRvdy5fd2FpbHMuc2V0UmVzaXphYmxlID0gKHZhbHVlOiBib29sZWFuKTogdm9pZCA9PiB7XG4gICAgcmVzaXphYmxlID0gdmFsdWU7XG4gICAgaWYgKCFyZXNpemFibGUpIHtcbiAgICAgICAgLy8gU3RvcCByZXNpemluZyBpZiBpbiBwcm9ncmVzcy5cbiAgICAgICAgY2FuUmVzaXplID0gcmVzaXppbmcgPSBmYWxzZTtcbiAgICAgICAgc2V0UmVzaXplKCk7XG4gICAgfVxufTtcblxuLy8gRGVmZXIgYXR0YWNoaW5nIG1vdXNlIGxpc3RlbmVycyB1bnRpbCB3ZSBrbm93IHdlJ3JlIG5vdCBvbiBtb2JpbGUuXG5sZXQgZHJhZ0luaXREb25lID0gZmFsc2U7XG5mdW5jdGlvbiBpc01vYmlsZSgpOiBib29sZWFuIHtcbiAgICBjb25zdCBvcyA9ICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5PUztcbiAgICBpZiAob3MgPT09IFwiaW9zXCIgfHwgb3MgPT09IFwiYW5kcm9pZFwiKSByZXR1cm4gdHJ1ZTtcbiAgICAvLyBGYWxsYmFjayBoZXVyaXN0aWMgaWYgZW52aXJvbm1lbnQgbm90IHlldCBzZXRcbiAgICBjb25zdCB1YSA9IG5hdmlnYXRvci51c2VyQWdlbnQgfHwgbmF2aWdhdG9yLnZlbmRvciB8fCAod2luZG93IGFzIGFueSkub3BlcmEgfHwgXCJcIjtcbiAgICByZXR1cm4gL2FuZHJvaWR8aXBob25lfGlwYWR8aXBvZHxpZW1vYmlsZXx3cGRlc2t0b3AvaS50ZXN0KHVhKTtcbn1cbmZ1bmN0aW9uIHRyeUluaXREcmFnSGFuZGxlcnMoKTogdm9pZCB7XG4gICAgaWYgKGRyYWdJbml0RG9uZSkgcmV0dXJuO1xuICAgIGlmIChpc01vYmlsZSgpKSByZXR1cm47XG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlZG93bicsIHVwZGF0ZSwgeyBjYXB0dXJlOiB0cnVlIH0pO1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCB1cGRhdGUsIHsgY2FwdHVyZTogdHJ1ZSB9KTtcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2V1cCcsIHVwZGF0ZSwgeyBjYXB0dXJlOiB0cnVlIH0pO1xuICAgIGZvciAoY29uc3QgZXYgb2YgWydjbGljaycsICdjb250ZXh0bWVudScsICdkYmxjbGljayddKSB7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKGV2LCBzdXBwcmVzc0V2ZW50LCB7IGNhcHR1cmU6IHRydWUgfSk7XG4gICAgfVxuICAgIGRyYWdJbml0RG9uZSA9IHRydWU7XG59XG4vLyBBdHRlbXB0IGltbWVkaWF0ZSBpbml0IChpbiBjYXNlIGVudmlyb25tZW50IGFscmVhZHkgcHJlc2VudClcbnRyeUluaXREcmFnSGFuZGxlcnMoKTtcbi8vIEFsc28gYXR0ZW1wdCBvbiBET00gcmVhZHlcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCB0cnlJbml0RHJhZ0hhbmRsZXJzLCB7IG9uY2U6IHRydWUgfSk7XG4vLyBBcyBhIGxhc3QgcmVzb3J0LCBwb2xsIGZvciBlbnZpcm9ubWVudCBmb3IgYSBzaG9ydCBwZXJpb2RcbmxldCBkcmFnRW52UG9sbHMgPSAwO1xuY29uc3QgZHJhZ0VudlBvbGwgPSB3aW5kb3cuc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgIGlmIChkcmFnSW5pdERvbmUpIHsgd2luZG93LmNsZWFySW50ZXJ2YWwoZHJhZ0VudlBvbGwpOyByZXR1cm47IH1cbiAgICB0cnlJbml0RHJhZ0hhbmRsZXJzKCk7XG4gICAgaWYgKCsrZHJhZ0VudlBvbGxzID4gMTAwKSB7IHdpbmRvdy5jbGVhckludGVydmFsKGRyYWdFbnZQb2xsKTsgfVxufSwgNTApO1xuXG5mdW5jdGlvbiBzdXBwcmVzc0V2ZW50KGV2ZW50OiBFdmVudCkge1xuICAgIC8vIFN1cHByZXNzIGNsaWNrIGV2ZW50cyB3aGlsZSByZXNpemluZyBvciBkcmFnZ2luZy5cbiAgICBpZiAoZHJhZ2dpbmcgfHwgcmVzaXppbmcpIHtcbiAgICAgICAgZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH1cbn1cblxuLy8gVXNlIGNvbnN0YW50cyB0byBhdm9pZCBjb21wYXJpbmcgc3RyaW5ncyBtdWx0aXBsZSB0aW1lcy5cbmNvbnN0IE1vdXNlRG93biA9IDA7XG5jb25zdCBNb3VzZVVwICAgPSAxO1xuY29uc3QgTW91c2VNb3ZlID0gMjtcblxuZnVuY3Rpb24gdXBkYXRlKGV2ZW50OiBNb3VzZUV2ZW50KSB7XG4gICAgLy8gV2luZG93cyBzdXBwcmVzc2VzIG1vdXNlIGV2ZW50cyBhdCB0aGUgZW5kIG9mIGRyYWdnaW5nIG9yIHJlc2l6aW5nLFxuICAgIC8vIHNvIHdlIG5lZWQgdG8gYmUgc21hcnQgYW5kIHN5bnRoZXNpemUgYnV0dG9uIGV2ZW50cy5cblxuICAgIGxldCBldmVudFR5cGU6IG51bWJlciwgZXZlbnRCdXR0b25zID0gZXZlbnQuYnV0dG9ucztcbiAgICBzd2l0Y2ggKGV2ZW50LnR5cGUpIHtcbiAgICAgICAgY2FzZSAnbW91c2Vkb3duJzpcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IE1vdXNlRG93bjtcbiAgICAgICAgICAgIGlmICghYnV0dG9uc1RyYWNrZWQpIHsgZXZlbnRCdXR0b25zID0gYnV0dG9ucyB8ICgxIDw8IGV2ZW50LmJ1dHRvbik7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdtb3VzZXVwJzpcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IE1vdXNlVXA7XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnMgJiB+KDEgPDwgZXZlbnQuYnV0dG9uKTsgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICBldmVudFR5cGUgPSBNb3VzZU1vdmU7XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnM7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIGxldCByZWxlYXNlZCA9IGJ1dHRvbnMgJiB+ZXZlbnRCdXR0b25zO1xuICAgIGxldCBwcmVzc2VkID0gZXZlbnRCdXR0b25zICYgfmJ1dHRvbnM7XG5cbiAgICBidXR0b25zID0gZXZlbnRCdXR0b25zO1xuXG4gICAgLy8gU3ludGhlc2l6ZSBhIHJlbGVhc2UtcHJlc3Mgc2VxdWVuY2UgaWYgd2UgZGV0ZWN0IGEgcHJlc3Mgb2YgYW4gYWxyZWFkeSBwcmVzc2VkIGJ1dHRvbi5cbiAgICBpZiAoZXZlbnRUeXBlID09PSBNb3VzZURvd24gJiYgIShwcmVzc2VkICYgZXZlbnQuYnV0dG9uKSkge1xuICAgICAgICByZWxlYXNlZCB8PSAoMSA8PCBldmVudC5idXR0b24pO1xuICAgICAgICBwcmVzc2VkIHw9ICgxIDw8IGV2ZW50LmJ1dHRvbik7XG4gICAgfVxuXG4gICAgLy8gU3VwcHJlc3MgYWxsIGJ1dHRvbiBldmVudHMgZHVyaW5nIGRyYWdnaW5nIGFuZCByZXNpemluZyxcbiAgICAvLyB1bmxlc3MgdGhpcyBpcyBhIG1vdXNldXAgZXZlbnQgdGhhdCBpcyBlbmRpbmcgYSBkcmFnIGFjdGlvbi5cbiAgICBpZiAoXG4gICAgICAgIGV2ZW50VHlwZSAhPT0gTW91c2VNb3ZlIC8vIEZhc3QgcGF0aCBmb3IgbW91c2Vtb3ZlXG4gICAgICAgICYmIHJlc2l6aW5nXG4gICAgICAgIHx8IChcbiAgICAgICAgICAgIGRyYWdnaW5nXG4gICAgICAgICAgICAmJiAoXG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlID09PSBNb3VzZURvd25cbiAgICAgICAgICAgICAgICB8fCBldmVudC5idXR0b24gIT09IDBcbiAgICAgICAgICAgIClcbiAgICAgICAgKVxuICAgICkge1xuICAgICAgICBldmVudC5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKTtcbiAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgfVxuXG4gICAgLy8gSGFuZGxlIHJlbGVhc2VzXG4gICAgaWYgKHJlbGVhc2VkICYgMSkgeyBwcmltYXJ5VXAoZXZlbnQpOyB9XG4gICAgLy8gSGFuZGxlIHByZXNzZXNcbiAgICBpZiAocHJlc3NlZCAmIDEpIHsgcHJpbWFyeURvd24oZXZlbnQpOyB9XG5cbiAgICAvLyBIYW5kbGUgbW91c2Vtb3ZlXG4gICAgaWYgKGV2ZW50VHlwZSA9PT0gTW91c2VNb3ZlKSB7IG9uTW91c2VNb3ZlKGV2ZW50KTsgfTtcbn1cblxuZnVuY3Rpb24gcHJpbWFyeURvd24oZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICAvLyBSZXNldCByZWFkaW5lc3Mgc3RhdGUuXG4gICAgY2FuRHJhZyA9IGZhbHNlO1xuICAgIGNhblJlc2l6ZSA9IGZhbHNlO1xuXG4gICAgLy8gSWdub3JlIHJlcGVhdGVkIGNsaWNrcyBvbiBtYWNPUyBhbmQgTGludXguXG4gICAgaWYgKCFJc1dpbmRvd3MoKSkge1xuICAgICAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ21vdXNlZG93bicgJiYgZXZlbnQuYnV0dG9uID09PSAwICYmIGV2ZW50LmRldGFpbCAhPT0gMSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgLy8gUmVhZHkgdG8gcmVzaXplIGlmIHRoZSBwcmltYXJ5IGJ1dHRvbiB3YXMgcHJlc3NlZCBmb3IgdGhlIGZpcnN0IHRpbWUuXG4gICAgICAgIGNhblJlc2l6ZSA9IHRydWU7XG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnIG9wZXJhdGlvbnMgd2hlbiBvbiByZXNpemUgZWRnZXMuXG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBSZXRyaWV2ZSB0YXJnZXQgZWxlbWVudFxuICAgIGNvbnN0IHRhcmdldCA9IGV2ZW50VGFyZ2V0KGV2ZW50KTtcblxuICAgIC8vIFJlYWR5IHRvIGRyYWcgaWYgdGhlIHByaW1hcnkgYnV0dG9uIHdhcyBwcmVzc2VkIGZvciB0aGUgZmlyc3QgdGltZSBvbiBhIGRyYWdnYWJsZSBlbGVtZW50LlxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gdGhlIHNjcm9sbGJhci5cbiAgICBjb25zdCBzdHlsZSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRhcmdldCk7XG4gICAgY2FuRHJhZyA9IChcbiAgICAgICAgc3R5bGUuZ2V0UHJvcGVydHlWYWx1ZShcIi0td2FpbHMtZHJhZ2dhYmxlXCIpLnRyaW0oKSA9PT0gXCJkcmFnXCJcbiAgICAgICAgJiYgKFxuICAgICAgICAgICAgZXZlbnQub2Zmc2V0WCAtIHBhcnNlRmxvYXQoc3R5bGUucGFkZGluZ0xlZnQpIDwgdGFyZ2V0LmNsaWVudFdpZHRoXG4gICAgICAgICAgICAmJiBldmVudC5vZmZzZXRZIC0gcGFyc2VGbG9hdChzdHlsZS5wYWRkaW5nVG9wKSA8IHRhcmdldC5jbGllbnRIZWlnaHRcbiAgICAgICAgKVxuICAgICk7XG59XG5cbmZ1bmN0aW9uIHByaW1hcnlVcChldmVudDogTW91c2VFdmVudCkge1xuICAgIC8vIFN0b3AgZHJhZ2dpbmcgYW5kIHJlc2l6aW5nLlxuICAgIGNhbkRyYWcgPSBmYWxzZTtcbiAgICBkcmFnZ2luZyA9IGZhbHNlO1xuICAgIGNhblJlc2l6ZSA9IGZhbHNlO1xuICAgIHJlc2l6aW5nID0gZmFsc2U7XG59XG5cbmNvbnN0IGN1cnNvckZvckVkZ2UgPSBPYmplY3QuZnJlZXplKHtcbiAgICBcInNlLXJlc2l6ZVwiOiBcIm53c2UtcmVzaXplXCIsXG4gICAgXCJzdy1yZXNpemVcIjogXCJuZXN3LXJlc2l6ZVwiLFxuICAgIFwibnctcmVzaXplXCI6IFwibndzZS1yZXNpemVcIixcbiAgICBcIm5lLXJlc2l6ZVwiOiBcIm5lc3ctcmVzaXplXCIsXG4gICAgXCJ3LXJlc2l6ZVwiOiBcImV3LXJlc2l6ZVwiLFxuICAgIFwibi1yZXNpemVcIjogXCJucy1yZXNpemVcIixcbiAgICBcInMtcmVzaXplXCI6IFwibnMtcmVzaXplXCIsXG4gICAgXCJlLXJlc2l6ZVwiOiBcImV3LXJlc2l6ZVwiLFxufSlcblxuZnVuY3Rpb24gc2V0UmVzaXplKGVkZ2U/OiBrZXlvZiB0eXBlb2YgY3Vyc29yRm9yRWRnZSk6IHZvaWQge1xuICAgIGlmIChlZGdlKSB7XG4gICAgICAgIGlmICghcmVzaXplRWRnZSkgeyBkZWZhdWx0Q3Vyc29yID0gZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3I7IH1cbiAgICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSBjdXJzb3JGb3JFZGdlW2VkZ2VdO1xuICAgIH0gZWxzZSBpZiAoIWVkZ2UgJiYgcmVzaXplRWRnZSkge1xuICAgICAgICBkb2N1bWVudC5ib2R5LnN0eWxlLmN1cnNvciA9IGRlZmF1bHRDdXJzb3I7XG4gICAgfVxuXG4gICAgcmVzaXplRWRnZSA9IGVkZ2UgfHwgXCJcIjtcbn1cblxuZnVuY3Rpb24gb25Nb3VzZU1vdmUoZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAoY2FuUmVzaXplICYmIHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgLy8gU3RhcnQgcmVzaXppbmcuXG4gICAgICAgIHJlc2l6aW5nID0gdHJ1ZTtcbiAgICAgICAgaW52b2tlKFwid2FpbHM6cmVzaXplOlwiICsgcmVzaXplRWRnZSk7XG4gICAgfSBlbHNlIGlmIChjYW5EcmFnKSB7XG4gICAgICAgIC8vIFN0YXJ0IGRyYWdnaW5nLlxuICAgICAgICBkcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIGludm9rZShcIndhaWxzOmRyYWdcIik7XG4gICAgfVxuXG4gICAgaWYgKGRyYWdnaW5nIHx8IHJlc2l6aW5nKSB7XG4gICAgICAgIC8vIEVpdGhlciBkcmFnIG9yIHJlc2l6ZSBpcyBvbmdvaW5nLFxuICAgICAgICAvLyByZXNldCByZWFkaW5lc3MgYW5kIHN0b3AgcHJvY2Vzc2luZy5cbiAgICAgICAgY2FuRHJhZyA9IGNhblJlc2l6ZSA9IGZhbHNlO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKCFyZXNpemFibGUgfHwgIUlzV2luZG93cygpKSB7XG4gICAgICAgIGlmIChyZXNpemVFZGdlKSB7IHNldFJlc2l6ZSgpOyB9XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCByZXNpemVIYW5kbGVIZWlnaHQgPSBHZXRGbGFnKFwic3lzdGVtLnJlc2l6ZUhhbmRsZUhlaWdodFwiKSB8fCA1O1xuICAgIGNvbnN0IHJlc2l6ZUhhbmRsZVdpZHRoID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVXaWR0aFwiKSB8fCA1O1xuXG4gICAgLy8gRXh0cmEgcGl4ZWxzIGZvciB0aGUgY29ybmVyIGFyZWFzLlxuICAgIGNvbnN0IGNvcm5lckV4dHJhID0gR2V0RmxhZyhcInJlc2l6ZUNvcm5lckV4dHJhXCIpIHx8IDEwO1xuXG4gICAgY29uc3QgcmlnaHRCb3JkZXIgPSAod2luZG93Lm91dGVyV2lkdGggLSBldmVudC5jbGllbnRYKSA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xuICAgIGNvbnN0IGxlZnRCb3JkZXIgPSBldmVudC5jbGllbnRYIDwgcmVzaXplSGFuZGxlV2lkdGg7XG4gICAgY29uc3QgdG9wQm9yZGVyID0gZXZlbnQuY2xpZW50WSA8IHJlc2l6ZUhhbmRsZUhlaWdodDtcbiAgICBjb25zdCBib3R0b21Cb3JkZXIgPSAod2luZG93Lm91dGVySGVpZ2h0IC0gZXZlbnQuY2xpZW50WSkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XG5cbiAgICAvLyBBZGp1c3QgZm9yIGNvcm5lciBhcmVhcy5cbiAgICBjb25zdCByaWdodENvcm5lciA9ICh3aW5kb3cub3V0ZXJXaWR0aCAtIGV2ZW50LmNsaWVudFgpIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IGxlZnRDb3JuZXIgPSBldmVudC5jbGllbnRYIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IHRvcENvcm5lciA9IGV2ZW50LmNsaWVudFkgPCAocmVzaXplSGFuZGxlSGVpZ2h0ICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IGJvdHRvbUNvcm5lciA9ICh3aW5kb3cub3V0ZXJIZWlnaHQgLSBldmVudC5jbGllbnRZKSA8IChyZXNpemVIYW5kbGVIZWlnaHQgKyBjb3JuZXJFeHRyYSk7XG5cbiAgICBpZiAoIWxlZnRDb3JuZXIgJiYgIXRvcENvcm5lciAmJiAhYm90dG9tQ29ybmVyICYmICFyaWdodENvcm5lcikge1xuICAgICAgICAvLyBPcHRpbWlzYXRpb246IG91dCBvZiBhbGwgY29ybmVyIGFyZWFzIGltcGxpZXMgb3V0IG9mIGJvcmRlcnMuXG4gICAgICAgIHNldFJlc2l6ZSgpO1xuICAgIH1cbiAgICAvLyBEZXRlY3QgY29ybmVycy5cbiAgICBlbHNlIGlmIChyaWdodENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIGJvdHRvbUNvcm5lcikgc2V0UmVzaXplKFwic3ctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKGxlZnRDb3JuZXIgJiYgdG9wQ29ybmVyKSBzZXRSZXNpemUoXCJudy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQ29ybmVyICYmIHJpZ2h0Q29ybmVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XG4gICAgLy8gRGV0ZWN0IGJvcmRlcnMuXG4gICAgZWxzZSBpZiAobGVmdEJvcmRlcikgc2V0UmVzaXplKFwidy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyKSBzZXRSZXNpemUoXCJuLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJlLXJlc2l6ZVwiKTtcbiAgICAvLyBPdXQgb2YgYm9yZGVyIGFyZWEuXG4gICAgZWxzZSBzZXRSZXNpemUoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5BcHBsaWNhdGlvbik7XG5cbmNvbnN0IEhpZGVNZXRob2QgPSAwO1xuY29uc3QgU2hvd01ldGhvZCA9IDE7XG5jb25zdCBRdWl0TWV0aG9kID0gMjtcblxuLyoqXG4gKiBIaWRlcyBhIGNlcnRhaW4gbWV0aG9kIGJ5IGNhbGxpbmcgdGhlIEhpZGVNZXRob2QgZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBIaWRlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKEhpZGVNZXRob2QpO1xufVxuXG4vKipcbiAqIENhbGxzIHRoZSBTaG93TWV0aG9kIGFuZCByZXR1cm5zIHRoZSByZXN1bHQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTaG93KCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKFNob3dNZXRob2QpO1xufVxuXG4vKipcbiAqIENhbGxzIHRoZSBRdWl0TWV0aG9kIHRvIHRlcm1pbmF0ZSB0aGUgcHJvZ3JhbS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoUXVpdE1ldGhvZCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IENhbmNlbGxhYmxlUHJvbWlzZSwgdHlwZSBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzIH0gZnJvbSBcIi4vY2FuY2VsbGFibGUuanNcIjtcbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSBcIi4vbmFub2lkLmpzXCI7XG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcblxudHlwZSBQcm9taXNlUmVzb2x2ZXJzID0gT21pdDxDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPGFueT4sIFwicHJvbWlzZVwiIHwgXCJvbmNhbmNlbGxlZFwiPlxuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5DYWxsKTtcbmNvbnN0IGNhbmNlbENhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkNhbmNlbENhbGwpO1xuY29uc3QgY2FsbFJlc3BvbnNlcyA9IG5ldyBNYXA8c3RyaW5nLCBQcm9taXNlUmVzb2x2ZXJzPigpO1xuXG5jb25zdCBDYWxsQmluZGluZyA9IDA7XG5jb25zdCBDYW5jZWxNZXRob2QgPSAwXG5cbi8qKlxuICogSG9sZHMgYWxsIHJlcXVpcmVkIGluZm9ybWF0aW9uIGZvciBhIGJpbmRpbmcgY2FsbC5cbiAqIE1heSBwcm92aWRlIGVpdGhlciBhIG1ldGhvZCBJRCBvciBhIG1ldGhvZCBuYW1lLCBidXQgbm90IGJvdGguXG4gKi9cbmV4cG9ydCB0eXBlIENhbGxPcHRpb25zID0ge1xuICAgIC8qKiBUaGUgbnVtZXJpYyBJRCBvZiB0aGUgYm91bmQgbWV0aG9kIHRvIGNhbGwuICovXG4gICAgbWV0aG9kSUQ6IG51bWJlcjtcbiAgICAvKiogVGhlIGZ1bGx5IHF1YWxpZmllZCBuYW1lIG9mIHRoZSBib3VuZCBtZXRob2QgdG8gY2FsbC4gKi9cbiAgICBtZXRob2ROYW1lPzogbmV2ZXI7XG4gICAgLyoqIEFyZ3VtZW50cyB0byBiZSBwYXNzZWQgaW50byB0aGUgYm91bmQgbWV0aG9kLiAqL1xuICAgIGFyZ3M6IGFueVtdO1xufSB8IHtcbiAgICAvKiogVGhlIG51bWVyaWMgSUQgb2YgdGhlIGJvdW5kIG1ldGhvZCB0byBjYWxsLiAqL1xuICAgIG1ldGhvZElEPzogbmV2ZXI7XG4gICAgLyoqIFRoZSBmdWxseSBxdWFsaWZpZWQgbmFtZSBvZiB0aGUgYm91bmQgbWV0aG9kIHRvIGNhbGwuICovXG4gICAgbWV0aG9kTmFtZTogc3RyaW5nO1xuICAgIC8qKiBBcmd1bWVudHMgdG8gYmUgcGFzc2VkIGludG8gdGhlIGJvdW5kIG1ldGhvZC4gKi9cbiAgICBhcmdzOiBhbnlbXTtcbn07XG5cbi8qKlxuICogRXhjZXB0aW9uIGNsYXNzIHRoYXQgd2lsbCBiZSB0aHJvd24gaW4gY2FzZSB0aGUgYm91bmQgbWV0aG9kIHJldHVybnMgYW4gZXJyb3IuXG4gKiBUaGUgdmFsdWUgb2YgdGhlIHtAbGluayBSdW50aW1lRXJyb3IjbmFtZX0gcHJvcGVydHkgaXMgXCJSdW50aW1lRXJyb3JcIi5cbiAqL1xuZXhwb3J0IGNsYXNzIFJ1bnRpbWVFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IFJ1bnRpbWVFcnJvciBpbnN0YW5jZS5cbiAgICAgKiBAcGFyYW0gbWVzc2FnZSAtIFRoZSBlcnJvciBtZXNzYWdlLlxuICAgICAqIEBwYXJhbSBvcHRpb25zIC0gT3B0aW9ucyB0byBiZSBmb3J3YXJkZWQgdG8gdGhlIEVycm9yIGNvbnN0cnVjdG9yLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG1lc3NhZ2U/OiBzdHJpbmcsIG9wdGlvbnM/OiBFcnJvck9wdGlvbnMpIHtcbiAgICAgICAgc3VwZXIobWVzc2FnZSwgb3B0aW9ucyk7XG4gICAgICAgIHRoaXMubmFtZSA9IFwiUnVudGltZUVycm9yXCI7XG4gICAgfVxufVxuXG4vKipcbiAqIEdlbmVyYXRlcyBhIHVuaXF1ZSBJRCB1c2luZyB0aGUgbmFub2lkIGxpYnJhcnkuXG4gKlxuICogQHJldHVybnMgQSB1bmlxdWUgSUQgdGhhdCBkb2VzIG5vdCBleGlzdCBpbiB0aGUgY2FsbFJlc3BvbnNlcyBzZXQuXG4gKi9cbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKTogc3RyaW5nIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGRvIHtcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XG4gICAgfSB3aGlsZSAoY2FsbFJlc3BvbnNlcy5oYXMocmVzdWx0KSk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLyoqXG4gKiBDYWxsIGEgYm91bmQgbWV0aG9kIGFjY29yZGluZyB0byB0aGUgZ2l2ZW4gY2FsbCBvcHRpb25zLlxuICpcbiAqIEluIGNhc2Ugb2YgZmFpbHVyZSwgdGhlIHJldHVybmVkIHByb21pc2Ugd2lsbCByZWplY3Qgd2l0aCBhbiBleGNlcHRpb25cbiAqIGFtb25nIFJlZmVyZW5jZUVycm9yICh1bmtub3duIG1ldGhvZCksIFR5cGVFcnJvciAod3JvbmcgYXJndW1lbnQgY291bnQgb3IgdHlwZSksXG4gKiB7QGxpbmsgUnVudGltZUVycm9yfSAobWV0aG9kIHJldHVybmVkIGFuIGVycm9yKSwgb3Igb3RoZXIgKG5ldHdvcmsgb3IgaW50ZXJuYWwgZXJyb3JzKS5cbiAqIFRoZSBleGNlcHRpb24gbWlnaHQgaGF2ZSBhIFwiY2F1c2VcIiBmaWVsZCB3aXRoIHRoZSB2YWx1ZSByZXR1cm5lZFxuICogYnkgdGhlIGFwcGxpY2F0aW9uLSBvciBzZXJ2aWNlLWxldmVsIGVycm9yIG1hcnNoYWxpbmcgZnVuY3Rpb25zLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gQSBtZXRob2QgY2FsbCBkZXNjcmlwdG9yLlxuICogQHJldHVybnMgVGhlIHJlc3VsdCBvZiB0aGUgY2FsbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENhbGwob3B0aW9uczogQ2FsbE9wdGlvbnMpOiBDYW5jZWxsYWJsZVByb21pc2U8YW55PiB7XG4gICAgY29uc3QgaWQgPSBnZW5lcmF0ZUlEKCk7XG5cbiAgICBjb25zdCByZXN1bHQgPSBDYW5jZWxsYWJsZVByb21pc2Uud2l0aFJlc29sdmVyczxhbnk+KCk7XG4gICAgY2FsbFJlc3BvbnNlcy5zZXQoaWQsIHsgcmVzb2x2ZTogcmVzdWx0LnJlc29sdmUsIHJlamVjdDogcmVzdWx0LnJlamVjdCB9KTtcblxuICAgIGNvbnN0IHJlcXVlc3QgPSBjYWxsKENhbGxCaW5kaW5nLCBPYmplY3QuYXNzaWduKHsgXCJjYWxsLWlkXCI6IGlkIH0sIG9wdGlvbnMpKTtcbiAgICBsZXQgcnVubmluZyA9IHRydWU7XG5cbiAgICByZXF1ZXN0LnRoZW4oKHJlcykgPT4ge1xuICAgICAgICBydW5uaW5nID0gZmFsc2U7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcbiAgICAgICAgcmVzdWx0LnJlc29sdmUocmVzKTtcbiAgICB9LCAoZXJyKSA9PiB7XG4gICAgICAgIHJ1bm5pbmcgPSBmYWxzZTtcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICByZXN1bHQucmVqZWN0KGVycik7XG4gICAgfSk7XG5cbiAgICBjb25zdCBjYW5jZWwgPSAoKSA9PiB7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcbiAgICAgICAgcmV0dXJuIGNhbmNlbENhbGwoQ2FuY2VsTWV0aG9kLCB7XCJjYWxsLWlkXCI6IGlkfSkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcihcIkVycm9yIHdoaWxlIHJlcXVlc3RpbmcgYmluZGluZyBjYWxsIGNhbmNlbGxhdGlvbjpcIiwgZXJyKTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIHJlc3VsdC5vbmNhbmNlbGxlZCA9ICgpID0+IHtcbiAgICAgICAgaWYgKHJ1bm5pbmcpIHtcbiAgICAgICAgICAgIHJldHVybiBjYW5jZWwoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiByZXF1ZXN0LnRoZW4oY2FuY2VsKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICByZXR1cm4gcmVzdWx0LnByb21pc2U7XG59XG5cbi8qKlxuICogQ2FsbHMgYSBib3VuZCBtZXRob2QgYnkgbmFtZSB3aXRoIHRoZSBzcGVjaWZpZWQgYXJndW1lbnRzLlxuICogU2VlIHtAbGluayBDYWxsfSBmb3IgZGV0YWlscy5cbiAqXG4gKiBAcGFyYW0gbWV0aG9kTmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBtZXRob2QgaW4gdGhlIGZvcm1hdCAncGFja2FnZS5zdHJ1Y3QubWV0aG9kJy5cbiAqIEBwYXJhbSBhcmdzIC0gVGhlIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2QuXG4gKiBAcmV0dXJucyBUaGUgcmVzdWx0IG9mIHRoZSBtZXRob2QgY2FsbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEJ5TmFtZShtZXRob2ROYW1lOiBzdHJpbmcsIC4uLmFyZ3M6IGFueVtdKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBDYWxsKHsgbWV0aG9kTmFtZSwgYXJncyB9KTtcbn1cblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBieSBpdHMgbnVtZXJpYyBJRCB3aXRoIHRoZSBzcGVjaWZpZWQgYXJndW1lbnRzLlxuICogU2VlIHtAbGluayBDYWxsfSBmb3IgZGV0YWlscy5cbiAqXG4gKiBAcGFyYW0gbWV0aG9kSUQgLSBUaGUgSUQgb2YgdGhlIG1ldGhvZCB0byBjYWxsLlxuICogQHBhcmFtIGFyZ3MgLSBUaGUgYXJndW1lbnRzIHRvIHBhc3MgdG8gdGhlIG1ldGhvZC5cbiAqIEByZXR1cm4gVGhlIHJlc3VsdCBvZiB0aGUgbWV0aG9kIGNhbGwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCeUlEKG1ldGhvZElEOiBudW1iZXIsIC4uLmFyZ3M6IGFueVtdKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBDYWxsKHsgbWV0aG9kSUQsIGFyZ3MgfSk7XG59XG4iLCAiLy8gU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vaW5zcGVjdC1qcy9pcy1jYWxsYWJsZVxuXG4vLyBUaGUgTUlUIExpY2Vuc2UgKE1JVClcbi8vXG4vLyBDb3B5cmlnaHQgKGMpIDIwMTUgSm9yZGFuIEhhcmJhbmRcbi8vXG4vLyBQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5XG4vLyBvZiB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSBcIlNvZnR3YXJlXCIpLCB0byBkZWFsXG4vLyBpbiB0aGUgU29mdHdhcmUgd2l0aG91dCByZXN0cmljdGlvbiwgaW5jbHVkaW5nIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzXG4vLyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsXG4vLyBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXNcbi8vIGZ1cm5pc2hlZCB0byBkbyBzbywgc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsXG4vLyBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1Jcbi8vIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLFxuLy8gRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFXG4vLyBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSXG4vLyBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLFxuLy8gT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEVcbi8vIFNPRlRXQVJFLlxuXG52YXIgZm5Ub1N0ciA9IEZ1bmN0aW9uLnByb3RvdHlwZS50b1N0cmluZztcbnZhciByZWZsZWN0QXBwbHk6IHR5cGVvZiBSZWZsZWN0LmFwcGx5IHwgZmFsc2UgfCBudWxsID0gdHlwZW9mIFJlZmxlY3QgPT09ICdvYmplY3QnICYmIFJlZmxlY3QgIT09IG51bGwgJiYgUmVmbGVjdC5hcHBseTtcbnZhciBiYWRBcnJheUxpa2U6IGFueTtcbnZhciBpc0NhbGxhYmxlTWFya2VyOiBhbnk7XG5pZiAodHlwZW9mIHJlZmxlY3RBcHBseSA9PT0gJ2Z1bmN0aW9uJyAmJiB0eXBlb2YgT2JqZWN0LmRlZmluZVByb3BlcnR5ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgYmFkQXJyYXlMaWtlID0gT2JqZWN0LmRlZmluZVByb3BlcnR5KHt9LCAnbGVuZ3RoJywge1xuICAgICAgICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgaXNDYWxsYWJsZU1hcmtlcjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIGlzQ2FsbGFibGVNYXJrZXIgPSB7fTtcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLXRocm93LWxpdGVyYWxcbiAgICAgICAgcmVmbGVjdEFwcGx5KGZ1bmN0aW9uICgpIHsgdGhyb3cgNDI7IH0sIG51bGwsIGJhZEFycmF5TGlrZSk7XG4gICAgfSBjYXRjaCAoXykge1xuICAgICAgICBpZiAoXyAhPT0gaXNDYWxsYWJsZU1hcmtlcikge1xuICAgICAgICAgICAgcmVmbGVjdEFwcGx5ID0gbnVsbDtcbiAgICAgICAgfVxuICAgIH1cbn0gZWxzZSB7XG4gICAgcmVmbGVjdEFwcGx5ID0gbnVsbDtcbn1cblxudmFyIGNvbnN0cnVjdG9yUmVnZXggPSAvXlxccypjbGFzc1xcYi87XG52YXIgaXNFUzZDbGFzc0ZuID0gZnVuY3Rpb24gaXNFUzZDbGFzc0Z1bmN0aW9uKHZhbHVlOiBhbnkpOiBib29sZWFuIHtcbiAgICB0cnkge1xuICAgICAgICB2YXIgZm5TdHIgPSBmblRvU3RyLmNhbGwodmFsdWUpO1xuICAgICAgICByZXR1cm4gY29uc3RydWN0b3JSZWdleC50ZXN0KGZuU3RyKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTsgLy8gbm90IGEgZnVuY3Rpb25cbiAgICB9XG59O1xuXG52YXIgdHJ5RnVuY3Rpb25PYmplY3QgPSBmdW5jdGlvbiB0cnlGdW5jdGlvblRvU3RyKHZhbHVlOiBhbnkpOiBib29sZWFuIHtcbiAgICB0cnkge1xuICAgICAgICBpZiAoaXNFUzZDbGFzc0ZuKHZhbHVlKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICAgICAgZm5Ub1N0ci5jYWxsKHZhbHVlKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufTtcbnZhciB0b1N0ciA9IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmc7XG52YXIgb2JqZWN0Q2xhc3MgPSAnW29iamVjdCBPYmplY3RdJztcbnZhciBmbkNsYXNzID0gJ1tvYmplY3QgRnVuY3Rpb25dJztcbnZhciBnZW5DbGFzcyA9ICdbb2JqZWN0IEdlbmVyYXRvckZ1bmN0aW9uXSc7XG52YXIgZGRhQ2xhc3MgPSAnW29iamVjdCBIVE1MQWxsQ29sbGVjdGlvbl0nOyAvLyBJRSAxMVxudmFyIGRkYUNsYXNzMiA9ICdbb2JqZWN0IEhUTUwgZG9jdW1lbnQuYWxsIGNsYXNzXSc7XG52YXIgZGRhQ2xhc3MzID0gJ1tvYmplY3QgSFRNTENvbGxlY3Rpb25dJzsgLy8gSUUgOS0xMFxudmFyIGhhc1RvU3RyaW5nVGFnID0gdHlwZW9mIFN5bWJvbCA9PT0gJ2Z1bmN0aW9uJyAmJiAhIVN5bWJvbC50b1N0cmluZ1RhZzsgLy8gYmV0dGVyOiB1c2UgYGhhcy10b3N0cmluZ3RhZ2BcblxudmFyIGlzSUU2OCA9ICEoMCBpbiBbLF0pOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLXNwYXJzZS1hcnJheXMsIGNvbW1hLXNwYWNpbmdcblxudmFyIGlzRERBOiAodmFsdWU6IGFueSkgPT4gYm9vbGVhbiA9IGZ1bmN0aW9uIGlzRG9jdW1lbnREb3RBbGwoKSB7IHJldHVybiBmYWxzZTsgfTtcbmlmICh0eXBlb2YgZG9jdW1lbnQgPT09ICdvYmplY3QnKSB7XG4gICAgLy8gRmlyZWZveCAzIGNhbm9uaWNhbGl6ZXMgRERBIHRvIHVuZGVmaW5lZCB3aGVuIGl0J3Mgbm90IGFjY2Vzc2VkIGRpcmVjdGx5XG4gICAgdmFyIGFsbCA9IGRvY3VtZW50LmFsbDtcbiAgICBpZiAodG9TdHIuY2FsbChhbGwpID09PSB0b1N0ci5jYWxsKGRvY3VtZW50LmFsbCkpIHtcbiAgICAgICAgaXNEREEgPSBmdW5jdGlvbiBpc0RvY3VtZW50RG90QWxsKHZhbHVlKSB7XG4gICAgICAgICAgICAvKiBnbG9iYWxzIGRvY3VtZW50OiBmYWxzZSAqL1xuICAgICAgICAgICAgLy8gaW4gSUUgNi04LCB0eXBlb2YgZG9jdW1lbnQuYWxsIGlzIFwib2JqZWN0XCIgYW5kIGl0J3MgdHJ1dGh5XG4gICAgICAgICAgICBpZiAoKGlzSUU2OCB8fCAhdmFsdWUpICYmICh0eXBlb2YgdmFsdWUgPT09ICd1bmRlZmluZWQnIHx8IHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcpKSB7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIHN0ciA9IHRvU3RyLmNhbGwodmFsdWUpO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RyID09PSBkZGFDbGFzc1xuICAgICAgICAgICAgICAgICAgICAgICAgfHwgc3RyID09PSBkZGFDbGFzczJcbiAgICAgICAgICAgICAgICAgICAgICAgIHx8IHN0ciA9PT0gZGRhQ2xhc3MzIC8vIG9wZXJhIDEyLjE2XG4gICAgICAgICAgICAgICAgICAgICAgICB8fCBzdHIgPT09IG9iamVjdENsYXNzIC8vIElFIDYtOFxuICAgICAgICAgICAgICAgICAgICApICYmIHZhbHVlKCcnKSA9PSBudWxsOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGVxZXFlcVxuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHsgLyoqLyB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5mdW5jdGlvbiBpc0NhbGxhYmxlUmVmQXBwbHk8VD4odmFsdWU6IFQgfCB1bmtub3duKTogdmFsdWUgaXMgKC4uLmFyZ3M6IGFueVtdKSA9PiBhbnkgIHtcbiAgICBpZiAoaXNEREEodmFsdWUpKSB7IHJldHVybiB0cnVlOyB9XG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnZnVuY3Rpb24nICYmIHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgdHJ5IHtcbiAgICAgICAgKHJlZmxlY3RBcHBseSBhcyBhbnkpKHZhbHVlLCBudWxsLCBiYWRBcnJheUxpa2UpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgaWYgKGUgIT09IGlzQ2FsbGFibGVNYXJrZXIpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgfVxuICAgIHJldHVybiAhaXNFUzZDbGFzc0ZuKHZhbHVlKSAmJiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7XG59XG5cbmZ1bmN0aW9uIGlzQ2FsbGFibGVOb1JlZkFwcGx5PFQ+KHZhbHVlOiBUIHwgdW5rbm93bik6IHZhbHVlIGlzICguLi5hcmdzOiBhbnlbXSkgPT4gYW55IHtcbiAgICBpZiAoaXNEREEodmFsdWUpKSB7IHJldHVybiB0cnVlOyB9XG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnZnVuY3Rpb24nICYmIHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgaWYgKGhhc1RvU3RyaW5nVGFnKSB7IHJldHVybiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7IH1cbiAgICBpZiAoaXNFUzZDbGFzc0ZuKHZhbHVlKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICB2YXIgc3RyQ2xhc3MgPSB0b1N0ci5jYWxsKHZhbHVlKTtcbiAgICBpZiAoc3RyQ2xhc3MgIT09IGZuQ2xhc3MgJiYgc3RyQ2xhc3MgIT09IGdlbkNsYXNzICYmICEoL15cXFtvYmplY3QgSFRNTC8pLnRlc3Qoc3RyQ2xhc3MpKSB7IHJldHVybiBmYWxzZTsgfVxuICAgIHJldHVybiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7XG59O1xuXG5leHBvcnQgZGVmYXVsdCByZWZsZWN0QXBwbHkgPyBpc0NhbGxhYmxlUmVmQXBwbHkgOiBpc0NhbGxhYmxlTm9SZWZBcHBseTtcbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IGlzQ2FsbGFibGUgZnJvbSBcIi4vY2FsbGFibGUuanNcIjtcblxuLyoqXG4gKiBFeGNlcHRpb24gY2xhc3MgdGhhdCB3aWxsIGJlIHVzZWQgYXMgcmVqZWN0aW9uIHJlYXNvblxuICogaW4gY2FzZSBhIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2V9IGlzIGNhbmNlbGxlZCBzdWNjZXNzZnVsbHkuXG4gKlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgbmFtZX0gcHJvcGVydHkgaXMgdGhlIHN0cmluZyBgXCJDYW5jZWxFcnJvclwiYC5cbiAqIFRoZSB2YWx1ZSBvZiB0aGUge0BsaW5rIGNhdXNlfSBwcm9wZXJ0eSBpcyB0aGUgY2F1c2UgcGFzc2VkIHRvIHRoZSBjYW5jZWwgbWV0aG9kLCBpZiBhbnkuXG4gKi9cbmV4cG9ydCBjbGFzcyBDYW5jZWxFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IGBDYW5jZWxFcnJvcmAgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIG1lc3NhZ2UgLSBUaGUgZXJyb3IgbWVzc2FnZS5cbiAgICAgKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgdG8gYmUgZm9yd2FyZGVkIHRvIHRoZSBFcnJvciBjb25zdHJ1Y3Rvci5cbiAgICAgKi9cbiAgICBjb25zdHJ1Y3RvcihtZXNzYWdlPzogc3RyaW5nLCBvcHRpb25zPzogRXJyb3JPcHRpb25zKSB7XG4gICAgICAgIHN1cGVyKG1lc3NhZ2UsIG9wdGlvbnMpO1xuICAgICAgICB0aGlzLm5hbWUgPSBcIkNhbmNlbEVycm9yXCI7XG4gICAgfVxufVxuXG4vKipcbiAqIEV4Y2VwdGlvbiBjbGFzcyB0aGF0IHdpbGwgYmUgcmVwb3J0ZWQgYXMgYW4gdW5oYW5kbGVkIHJlamVjdGlvblxuICogaW4gY2FzZSBhIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2V9IHJlamVjdHMgYWZ0ZXIgYmVpbmcgY2FuY2VsbGVkLFxuICogb3Igd2hlbiB0aGUgYG9uY2FuY2VsbGVkYCBjYWxsYmFjayB0aHJvd3Mgb3IgcmVqZWN0cy5cbiAqXG4gKiBUaGUgdmFsdWUgb2YgdGhlIHtAbGluayBuYW1lfSBwcm9wZXJ0eSBpcyB0aGUgc3RyaW5nIGBcIkNhbmNlbGxlZFJlamVjdGlvbkVycm9yXCJgLlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgY2F1c2V9IHByb3BlcnR5IGlzIHRoZSByZWFzb24gdGhlIHByb21pc2UgcmVqZWN0ZWQgd2l0aC5cbiAqXG4gKiBCZWNhdXNlIHRoZSBvcmlnaW5hbCBwcm9taXNlIHdhcyBjYW5jZWxsZWQsXG4gKiBhIHdyYXBwZXIgcHJvbWlzZSB3aWxsIGJlIHBhc3NlZCB0byB0aGUgdW5oYW5kbGVkIHJlamVjdGlvbiBsaXN0ZW5lciBpbnN0ZWFkLlxuICogVGhlIHtAbGluayBwcm9taXNlfSBwcm9wZXJ0eSBob2xkcyBhIHJlZmVyZW5jZSB0byB0aGUgb3JpZ2luYWwgcHJvbWlzZS5cbiAqL1xuZXhwb3J0IGNsYXNzIENhbmNlbGxlZFJlamVjdGlvbkVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICAgIC8qKlxuICAgICAqIEhvbGRzIGEgcmVmZXJlbmNlIHRvIHRoZSBwcm9taXNlIHRoYXQgd2FzIGNhbmNlbGxlZCBhbmQgdGhlbiByZWplY3RlZC5cbiAgICAgKi9cbiAgICBwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj47XG5cbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IGBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcmAgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIHByb21pc2UgLSBUaGUgcHJvbWlzZSB0aGF0IGNhdXNlZCB0aGUgZXJyb3Igb3JpZ2luYWxseS5cbiAgICAgKiBAcGFyYW0gcmVhc29uIC0gVGhlIHJlamVjdGlvbiByZWFzb24uXG4gICAgICogQHBhcmFtIGluZm8gLSBBbiBvcHRpb25hbCBpbmZvcm1hdGl2ZSBtZXNzYWdlIHNwZWNpZnlpbmcgdGhlIGNpcmN1bXN0YW5jZXMgaW4gd2hpY2ggdGhlIGVycm9yIHdhcyB0aHJvd24uXG4gICAgICogICAgICAgICAgICAgICBEZWZhdWx0cyB0byB0aGUgc3RyaW5nIGBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gY2FuY2VsbGVkIHByb21pc2UuXCJgLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiwgcmVhc29uPzogYW55LCBpbmZvPzogc3RyaW5nKSB7XG4gICAgICAgIHN1cGVyKChpbmZvID8/IFwiVW5oYW5kbGVkIHJlamVjdGlvbiBpbiBjYW5jZWxsZWQgcHJvbWlzZS5cIikgKyBcIiBSZWFzb246IFwiICsgZXJyb3JNZXNzYWdlKHJlYXNvbiksIHsgY2F1c2U6IHJlYXNvbiB9KTtcbiAgICAgICAgdGhpcy5wcm9taXNlID0gcHJvbWlzZTtcbiAgICAgICAgdGhpcy5uYW1lID0gXCJDYW5jZWxsZWRSZWplY3Rpb25FcnJvclwiO1xuICAgIH1cbn1cblxudHlwZSBDYW5jZWxsYWJsZVByb21pc2VSZXNvbHZlcjxUPiA9ICh2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPikgPT4gdm9pZDtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3IgPSAocmVhc29uPzogYW55KSA9PiB2b2lkO1xudHlwZSBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIgPSAoY2F1c2U/OiBhbnkpID0+IHZvaWQgfCBQcm9taXNlTGlrZTx2b2lkPjtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlRXhlY3V0b3I8VD4gPSAocmVzb2x2ZTogQ2FuY2VsbGFibGVQcm9taXNlUmVzb2x2ZXI8VD4sIHJlamVjdDogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3IpID0+IHZvaWQ7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPiB7XG4gICAgdGhlbjxUUmVzdWx0MSA9IFQsIFRSZXN1bHQyID0gbmV2ZXI+KG9uZnVsZmlsbGVkPzogKCh2YWx1ZTogVCkgPT4gVFJlc3VsdDEgfCBQcm9taXNlTGlrZTxUUmVzdWx0MT4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQxPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gVFJlc3VsdDIgfCBQcm9taXNlTGlrZTxUUmVzdWx0Mj4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQyPikgfCB1bmRlZmluZWQgfCBudWxsKTogQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0MSB8IFRSZXN1bHQyPjtcbiAgICBjYW5jZWwoY2F1c2U/OiBhbnkpOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG59XG5cbi8qKlxuICogV3JhcHMgYSBjYW5jZWxsYWJsZSBwcm9taXNlIGFsb25nIHdpdGggaXRzIHJlc29sdXRpb24gbWV0aG9kcy5cbiAqIFRoZSBgb25jYW5jZWxsZWRgIGZpZWxkIHdpbGwgYmUgbnVsbCBpbml0aWFsbHkgYnV0IG1heSBiZSBzZXQgdG8gcHJvdmlkZSBhIGN1c3RvbSBjYW5jZWxsYXRpb24gZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgcHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+O1xuICAgIHJlc29sdmU6IENhbmNlbGxhYmxlUHJvbWlzZVJlc29sdmVyPFQ+O1xuICAgIHJlamVjdDogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3I7XG4gICAgb25jYW5jZWxsZWQ6IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlciB8IG51bGw7XG59XG5cbmludGVyZmFjZSBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSB7XG4gICAgcmVhZG9ubHkgcm9vdDogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGU7XG4gICAgcmVzb2x2aW5nOiBib29sZWFuO1xuICAgIHNldHRsZWQ6IGJvb2xlYW47XG4gICAgcmVhc29uPzogQ2FuY2VsRXJyb3I7XG59XG5cbi8vIFByaXZhdGUgZmllbGQgbmFtZXMuXG5jb25zdCBiYXJyaWVyU3ltID0gU3ltYm9sKFwiYmFycmllclwiKTtcbmNvbnN0IGNhbmNlbEltcGxTeW0gPSBTeW1ib2woXCJjYW5jZWxJbXBsXCIpO1xuY29uc3Qgc3BlY2llczogdHlwZW9mIFN5bWJvbC5zcGVjaWVzID0gU3ltYm9sLnNwZWNpZXMgPz8gU3ltYm9sKFwic3BlY2llc1BvbHlmaWxsXCIpO1xuXG4vKipcbiAqIEEgcHJvbWlzZSB3aXRoIGFuIGF0dGFjaGVkIG1ldGhvZCBmb3IgY2FuY2VsbGluZyBsb25nLXJ1bm5pbmcgb3BlcmF0aW9ucyAoc2VlIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2UjY2FuY2VsfSkuXG4gKiBDYW5jZWxsYXRpb24gY2FuIG9wdGlvbmFsbHkgYmUgYm91bmQgdG8gYW4ge0BsaW5rIEFib3J0U2lnbmFsfVxuICogZm9yIGJldHRlciBjb21wb3NhYmlsaXR5IChzZWUge0BsaW5rIENhbmNlbGxhYmxlUHJvbWlzZSNjYW5jZWxPbn0pLlxuICpcbiAqIENhbmNlbGxpbmcgYSBwZW5kaW5nIHByb21pc2Ugd2lsbCByZXN1bHQgaW4gYW4gaW1tZWRpYXRlIHJlamVjdGlvblxuICogd2l0aCBhbiBpbnN0YW5jZSBvZiB7QGxpbmsgQ2FuY2VsRXJyb3J9IGFzIHJlYXNvbixcbiAqIGJ1dCB3aG9ldmVyIHN0YXJ0ZWQgdGhlIHByb21pc2Ugd2lsbCBiZSByZXNwb25zaWJsZVxuICogZm9yIGFjdHVhbGx5IGFib3J0aW5nIHRoZSB1bmRlcmx5aW5nIG9wZXJhdGlvbi5cbiAqIFRvIHRoaXMgcHVycG9zZSwgdGhlIGNvbnN0cnVjdG9yIGFuZCBhbGwgY2hhaW5pbmcgbWV0aG9kc1xuICogYWNjZXB0IG9wdGlvbmFsIGNhbmNlbGxhdGlvbiBjYWxsYmFja3MuXG4gKlxuICogSWYgYSBgQ2FuY2VsbGFibGVQcm9taXNlYCBzdGlsbCByZXNvbHZlcyBhZnRlciBoYXZpbmcgYmVlbiBjYW5jZWxsZWQsXG4gKiB0aGUgcmVzdWx0IHdpbGwgYmUgZGlzY2FyZGVkLiBJZiBpdCByZWplY3RzLCB0aGUgcmVhc29uXG4gKiB3aWxsIGJlIHJlcG9ydGVkIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24sXG4gKiB3cmFwcGVkIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBpbnN0YW5jZS5cbiAqIFRvIGZhY2lsaXRhdGUgdGhlIGhhbmRsaW5nIG9mIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyxcbiAqIGNhbmNlbGxlZCBgQ2FuY2VsbGFibGVQcm9taXNlYHMgd2lsbCBfbm90XyByZXBvcnQgdW5oYW5kbGVkIGBDYW5jZWxFcnJvcmBzXG4gKiB3aG9zZSBgY2F1c2VgIGZpZWxkIGlzIHRoZSBzYW1lIGFzIHRoZSBvbmUgd2l0aCB3aGljaCB0aGUgY3VycmVudCBwcm9taXNlIHdhcyBjYW5jZWxsZWQuXG4gKlxuICogQWxsIHVzdWFsIHByb21pc2UgbWV0aG9kcyBhcmUgZGVmaW5lZCBhbmQgcmV0dXJuIGEgYENhbmNlbGxhYmxlUHJvbWlzZWBcbiAqIHdob3NlIGNhbmNlbCBtZXRob2Qgd2lsbCBjYW5jZWwgdGhlIHBhcmVudCBvcGVyYXRpb24gYXMgd2VsbCwgcHJvcGFnYXRpbmcgdGhlIGNhbmNlbGxhdGlvbiByZWFzb25cbiAqIHVwd2FyZHMgdGhyb3VnaCBwcm9taXNlIGNoYWlucy5cbiAqIENvbnZlcnNlbHksIGNhbmNlbGxpbmcgYSBwcm9taXNlIHdpbGwgbm90IGF1dG9tYXRpY2FsbHkgY2FuY2VsIGRlcGVuZGVudCBwcm9taXNlcyBkb3duc3RyZWFtOlxuICogYGBgdHNcbiAqIGxldCByb290ID0gbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KTtcbiAqIGxldCBjaGlsZDEgPSByb290LnRoZW4oKCkgPT4geyAuLi4gfSk7XG4gKiBsZXQgY2hpbGQyID0gY2hpbGQxLnRoZW4oKCkgPT4geyAuLi4gfSk7XG4gKiBsZXQgY2hpbGQzID0gcm9vdC5jYXRjaCgoKSA9PiB7IC4uLiB9KTtcbiAqIGNoaWxkMS5jYW5jZWwoKTsgLy8gQ2FuY2VscyBjaGlsZDEgYW5kIHJvb3QsIGJ1dCBub3QgY2hpbGQyIG9yIGNoaWxkM1xuICogYGBgXG4gKiBDYW5jZWxsaW5nIGEgcHJvbWlzZSB0aGF0IGhhcyBhbHJlYWR5IHNldHRsZWQgaXMgc2FmZSBhbmQgaGFzIG5vIGNvbnNlcXVlbmNlLlxuICpcbiAqIFRoZSBgY2FuY2VsYCBtZXRob2QgcmV0dXJucyBhIHByb21pc2UgdGhhdCBfYWx3YXlzIGZ1bGZpbGxzX1xuICogYWZ0ZXIgdGhlIHdob2xlIGNoYWluIGhhcyBwcm9jZXNzZWQgdGhlIGNhbmNlbCByZXF1ZXN0XG4gKiBhbmQgYWxsIGF0dGFjaGVkIGNhbGxiYWNrcyB1cCB0byB0aGF0IG1vbWVudCBoYXZlIHJ1bi5cbiAqXG4gKiBBbGwgRVMyMDI0IHByb21pc2UgbWV0aG9kcyAoc3RhdGljIGFuZCBpbnN0YW5jZSkgYXJlIGRlZmluZWQgb24gQ2FuY2VsbGFibGVQcm9taXNlLFxuICogYnV0IGFjdHVhbCBhdmFpbGFiaWxpdHkgbWF5IHZhcnkgd2l0aCBPUy93ZWJ2aWV3IHZlcnNpb24uXG4gKlxuICogSW4gbGluZSB3aXRoIHRoZSBwcm9wb3NhbCBhdCBodHRwczovL2dpdGh1Yi5jb20vdGMzOS9wcm9wb3NhbC1ybS1idWlsdGluLXN1YmNsYXNzaW5nLFxuICogYENhbmNlbGxhYmxlUHJvbWlzZWAgZG9lcyBub3Qgc3VwcG9ydCB0cmFuc3BhcmVudCBzdWJjbGFzc2luZy5cbiAqIEV4dGVuZGVycyBzaG91bGQgdGFrZSBjYXJlIHRvIHByb3ZpZGUgdGhlaXIgb3duIG1ldGhvZCBpbXBsZW1lbnRhdGlvbnMuXG4gKiBUaGlzIG1pZ2h0IGJlIHJlY29uc2lkZXJlZCBpbiBjYXNlIHRoZSBwcm9wb3NhbCBpcyByZXRpcmVkLlxuICpcbiAqIENhbmNlbGxhYmxlUHJvbWlzZSBpcyBhIHdyYXBwZXIgYXJvdW5kIHRoZSBET00gUHJvbWlzZSBvYmplY3RcbiAqIGFuZCBpcyBjb21wbGlhbnQgd2l0aCB0aGUgW1Byb21pc2VzL0ErIHNwZWNpZmljYXRpb25dKGh0dHBzOi8vcHJvbWlzZXNhcGx1cy5jb20vKVxuICogKGl0IHBhc3NlcyB0aGUgW2NvbXBsaWFuY2Ugc3VpdGVdKGh0dHBzOi8vZ2l0aHViLmNvbS9wcm9taXNlcy1hcGx1cy9wcm9taXNlcy10ZXN0cykpXG4gKiBpZiBzbyBpcyB0aGUgdW5kZXJseWluZyBpbXBsZW1lbnRhdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIENhbmNlbGxhYmxlUHJvbWlzZTxUPiBleHRlbmRzIFByb21pc2U8VD4gaW1wbGVtZW50cyBQcm9taXNlTGlrZTxUPiwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPiB7XG4gICAgLy8gUHJpdmF0ZSBmaWVsZHMuXG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIHByaXZhdGUgW2JhcnJpZXJTeW1dITogUGFydGlhbDxQcm9taXNlV2l0aFJlc29sdmVyczx2b2lkPj4gfCBudWxsO1xuICAgIC8qKiBAaW50ZXJuYWwgKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IFtjYW5jZWxJbXBsU3ltXSE6IChyZWFzb246IENhbmNlbEVycm9yKSA9PiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IGBDYW5jZWxsYWJsZVByb21pc2VgLlxuICAgICAqXG4gICAgICogQHBhcmFtIGV4ZWN1dG9yIC0gQSBjYWxsYmFjayB1c2VkIHRvIGluaXRpYWxpemUgdGhlIHByb21pc2UuIFRoaXMgY2FsbGJhY2sgaXMgcGFzc2VkIHR3byBhcmd1bWVudHM6XG4gICAgICogICAgICAgICAgICAgICAgICAgYSBgcmVzb2x2ZWAgY2FsbGJhY2sgdXNlZCB0byByZXNvbHZlIHRoZSBwcm9taXNlIHdpdGggYSB2YWx1ZVxuICAgICAqICAgICAgICAgICAgICAgICAgIG9yIHRoZSByZXN1bHQgb2YgYW5vdGhlciBwcm9taXNlIChwb3NzaWJseSBjYW5jZWxsYWJsZSksXG4gICAgICogICAgICAgICAgICAgICAgICAgYW5kIGEgYHJlamVjdGAgY2FsbGJhY2sgdXNlZCB0byByZWplY3QgdGhlIHByb21pc2Ugd2l0aCBhIHByb3ZpZGVkIHJlYXNvbiBvciBlcnJvci5cbiAgICAgKiAgICAgICAgICAgICAgICAgICBJZiB0aGUgdmFsdWUgcHJvdmlkZWQgdG8gdGhlIGByZXNvbHZlYCBjYWxsYmFjayBpcyBhIHRoZW5hYmxlIF9hbmRfIGNhbmNlbGxhYmxlIG9iamVjdFxuICAgICAqICAgICAgICAgICAgICAgICAgIChpdCBoYXMgYSBgdGhlbmAgX2FuZF8gYSBgY2FuY2VsYCBtZXRob2QpLFxuICAgICAqICAgICAgICAgICAgICAgICAgIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB3aWxsIGJlIGZvcndhcmRlZCB0byB0aGF0IG9iamVjdCBhbmQgdGhlIG9uY2FuY2VsbGVkIHdpbGwgbm90IGJlIGludm9rZWQgYW55bW9yZS5cbiAgICAgKiAgICAgICAgICAgICAgICAgICBJZiBhbnkgb25lIG9mIHRoZSB0d28gY2FsbGJhY2tzIGlzIGNhbGxlZCBfYWZ0ZXJfIHRoZSBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZCxcbiAgICAgKiAgICAgICAgICAgICAgICAgICB0aGUgcHJvdmlkZWQgdmFsdWVzIHdpbGwgYmUgY2FuY2VsbGVkIGFuZCByZXNvbHZlZCBhcyB1c3VhbCxcbiAgICAgKiAgICAgICAgICAgICAgICAgICBidXQgdGhlaXIgcmVzdWx0cyB3aWxsIGJlIGRpc2NhcmRlZC5cbiAgICAgKiAgICAgICAgICAgICAgICAgICBIb3dldmVyLCBpZiB0aGUgcmVzb2x1dGlvbiBwcm9jZXNzIHVsdGltYXRlbHkgZW5kcyB1cCBpbiBhIHJlamVjdGlvblxuICAgICAqICAgICAgICAgICAgICAgICAgIHRoYXQgaXMgbm90IGR1ZSB0byBjYW5jZWxsYXRpb24sIHRoZSByZWplY3Rpb24gcmVhc29uXG4gICAgICogICAgICAgICAgICAgICAgICAgd2lsbCBiZSB3cmFwcGVkIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfVxuICAgICAqICAgICAgICAgICAgICAgICAgIGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICogQHBhcmFtIG9uY2FuY2VsbGVkIC0gSXQgaXMgdGhlIGNhbGxlcidzIHJlc3BvbnNpYmlsaXR5IHRvIGVuc3VyZSB0aGF0IGFueSBvcGVyYXRpb25cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBzdGFydGVkIGJ5IHRoZSBleGVjdXRvciBpcyBwcm9wZXJseSBoYWx0ZWQgdXBvbiBjYW5jZWxsYXRpb24uXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgVGhpcyBvcHRpb25hbCBjYWxsYmFjayBjYW4gYmUgdXNlZCB0byB0aGF0IHB1cnBvc2UuXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgSXQgd2lsbCBiZSBjYWxsZWQgX3N5bmNocm9ub3VzbHlfIHdpdGggYSBjYW5jZWxsYXRpb24gY2F1c2VcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICB3aGVuIGNhbmNlbGxhdGlvbiBpcyByZXF1ZXN0ZWQsIF9hZnRlcl8gdGhlIHByb21pc2UgaGFzIGFscmVhZHkgcmVqZWN0ZWRcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICB3aXRoIGEge0BsaW5rIENhbmNlbEVycm9yfSwgYnV0IF9iZWZvcmVfXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgYW55IHtAbGluayB0aGVufS97QGxpbmsgY2F0Y2h9L3tAbGluayBmaW5hbGx5fSBjYWxsYmFjayBydW5zLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIElmIHRoZSBjYWxsYmFjayByZXR1cm5zIGEgdGhlbmFibGUsIHRoZSBwcm9taXNlIHJldHVybmVkIGZyb20ge0BsaW5rIGNhbmNlbH1cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICB3aWxsIG9ubHkgZnVsZmlsbCBhZnRlciB0aGUgZm9ybWVyIGhhcyBzZXR0bGVkLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIFVuaGFuZGxlZCBleGNlcHRpb25zIG9yIHJlamVjdGlvbnMgZnJvbSB0aGUgY2FsbGJhY2sgd2lsbCBiZSB3cmFwcGVkXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIHVuaGFuZGxlZCByZWplY3Rpb25zLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIElmIHRoZSBgcmVzb2x2ZWAgY2FsbGJhY2sgaXMgY2FsbGVkIGJlZm9yZSBjYW5jZWxsYXRpb24gd2l0aCBhIGNhbmNlbGxhYmxlIHByb21pc2UsXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgY2FuY2VsbGF0aW9uIHJlcXVlc3RzIG9uIHRoaXMgcHJvbWlzZSB3aWxsIGJlIGRpdmVydGVkIHRvIHRoYXQgcHJvbWlzZSxcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBhbmQgdGhlIG9yaWdpbmFsIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoZXhlY3V0b3I6IENhbmNlbGxhYmxlUHJvbWlzZUV4ZWN1dG9yPFQ+LCBvbmNhbmNlbGxlZD86IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcikge1xuICAgICAgICBsZXQgcmVzb2x2ZSE6ICh2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+KSA9PiB2b2lkO1xuICAgICAgICBsZXQgcmVqZWN0ITogKHJlYXNvbj86IGFueSkgPT4gdm9pZDtcbiAgICAgICAgc3VwZXIoKHJlcywgcmVqKSA9PiB7IHJlc29sdmUgPSByZXM7IHJlamVjdCA9IHJlajsgfSk7XG5cbiAgICAgICAgaWYgKCh0aGlzLmNvbnN0cnVjdG9yIGFzIGFueSlbc3BlY2llc10gIT09IFByb21pc2UpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5jZWxsYWJsZVByb21pc2UgZG9lcyBub3Qgc3VwcG9ydCB0cmFuc3BhcmVudCBzdWJjbGFzc2luZy4gUGxlYXNlIHJlZnJhaW4gZnJvbSBvdmVycmlkaW5nIHRoZSBbU3ltYm9sLnNwZWNpZXNdIHN0YXRpYyBwcm9wZXJ0eS5cIik7XG4gICAgICAgIH1cblxuICAgICAgICBsZXQgcHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiA9IHtcbiAgICAgICAgICAgIHByb21pc2U6IHRoaXMsXG4gICAgICAgICAgICByZXNvbHZlLFxuICAgICAgICAgICAgcmVqZWN0LFxuICAgICAgICAgICAgZ2V0IG9uY2FuY2VsbGVkKCkgeyByZXR1cm4gb25jYW5jZWxsZWQgPz8gbnVsbDsgfSxcbiAgICAgICAgICAgIHNldCBvbmNhbmNlbGxlZChjYikgeyBvbmNhbmNlbGxlZCA9IGNiID8/IHVuZGVmaW5lZDsgfVxuICAgICAgICB9O1xuXG4gICAgICAgIGNvbnN0IHN0YXRlOiBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSA9IHtcbiAgICAgICAgICAgIGdldCByb290KCkgeyByZXR1cm4gc3RhdGU7IH0sXG4gICAgICAgICAgICByZXNvbHZpbmc6IGZhbHNlLFxuICAgICAgICAgICAgc2V0dGxlZDogZmFsc2VcbiAgICAgICAgfTtcblxuICAgICAgICAvLyBTZXR1cCBjYW5jZWxsYXRpb24gc3lzdGVtLlxuICAgICAgICB2b2lkIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsIHtcbiAgICAgICAgICAgIFtiYXJyaWVyU3ltXToge1xuICAgICAgICAgICAgICAgIGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgZW51bWVyYWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgd3JpdGFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgdmFsdWU6IG51bGxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBbY2FuY2VsSW1wbFN5bV06IHtcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHdyaXRhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB2YWx1ZTogY2FuY2VsbGVyRm9yKHByb21pc2UsIHN0YXRlKVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBSdW4gdGhlIGFjdHVhbCBleGVjdXRvci5cbiAgICAgICAgY29uc3QgcmVqZWN0b3IgPSByZWplY3RvckZvcihwcm9taXNlLCBzdGF0ZSk7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBleGVjdXRvcihyZXNvbHZlckZvcihwcm9taXNlLCBzdGF0ZSksIHJlamVjdG9yKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICBpZiAoc3RhdGUucmVzb2x2aW5nKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5sb2coXCJVbmhhbmRsZWQgZXhjZXB0aW9uIGluIENhbmNlbGxhYmxlUHJvbWlzZSBleGVjdXRvci5cIiwgZXJyKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmVqZWN0b3IoZXJyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENhbmNlbHMgaW1tZWRpYXRlbHkgdGhlIGV4ZWN1dGlvbiBvZiB0aGUgb3BlcmF0aW9uIGFzc29jaWF0ZWQgd2l0aCB0aGlzIHByb21pc2UuXG4gICAgICogVGhlIHByb21pc2UgcmVqZWN0cyB3aXRoIGEge0BsaW5rIENhbmNlbEVycm9yfSBpbnN0YW5jZSBhcyByZWFzb24sXG4gICAgICogd2l0aCB0aGUge0BsaW5rIENhbmNlbEVycm9yI2NhdXNlfSBwcm9wZXJ0eSBzZXQgdG8gdGhlIGdpdmVuIGFyZ3VtZW50LCBpZiBhbnkuXG4gICAgICpcbiAgICAgKiBIYXMgbm8gZWZmZWN0IGlmIGNhbGxlZCBhZnRlciB0aGUgcHJvbWlzZSBoYXMgYWxyZWFkeSBzZXR0bGVkO1xuICAgICAqIHJlcGVhdGVkIGNhbGxzIGluIHBhcnRpY3VsYXIgYXJlIHNhZmUsIGJ1dCBvbmx5IHRoZSBmaXJzdCBvbmVcbiAgICAgKiB3aWxsIHNldCB0aGUgY2FuY2VsbGF0aW9uIGNhdXNlLlxuICAgICAqXG4gICAgICogVGhlIGBDYW5jZWxFcnJvcmAgZXhjZXB0aW9uIF9uZWVkIG5vdF8gYmUgaGFuZGxlZCBleHBsaWNpdGx5IF9vbiB0aGUgcHJvbWlzZXMgdGhhdCBhcmUgYmVpbmcgY2FuY2VsbGVkOl9cbiAgICAgKiBjYW5jZWxsaW5nIGEgcHJvbWlzZSB3aXRoIG5vIGF0dGFjaGVkIHJlamVjdGlvbiBoYW5kbGVyIGRvZXMgbm90IHRyaWdnZXIgYW4gdW5oYW5kbGVkIHJlamVjdGlvbiBldmVudC5cbiAgICAgKiBUaGVyZWZvcmUsIHRoZSBmb2xsb3dpbmcgaWRpb21zIGFyZSBhbGwgZXF1YWxseSBjb3JyZWN0OlxuICAgICAqIGBgYHRzXG4gICAgICogbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KS5jYW5jZWwoKTtcbiAgICAgKiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pLnRoZW4oLi4uKS5jYW5jZWwoKTtcbiAgICAgKiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pLnRoZW4oLi4uKS5jYXRjaCguLi4pLmNhbmNlbCgpO1xuICAgICAqIGBgYFxuICAgICAqIFdoZW5ldmVyIHNvbWUgY2FuY2VsbGVkIHByb21pc2UgaW4gYSBjaGFpbiByZWplY3RzIHdpdGggYSBgQ2FuY2VsRXJyb3JgXG4gICAgICogd2l0aCB0aGUgc2FtZSBjYW5jZWxsYXRpb24gY2F1c2UgYXMgaXRzZWxmLCB0aGUgZXJyb3Igd2lsbCBiZSBkaXNjYXJkZWQgc2lsZW50bHkuXG4gICAgICogSG93ZXZlciwgdGhlIGBDYW5jZWxFcnJvcmAgX3dpbGwgc3RpbGwgYmUgZGVsaXZlcmVkXyB0byBhbGwgYXR0YWNoZWQgcmVqZWN0aW9uIGhhbmRsZXJzXG4gICAgICogYWRkZWQgYnkge0BsaW5rIHRoZW59IGFuZCByZWxhdGVkIG1ldGhvZHM6XG4gICAgICogYGBgdHNcbiAgICAgKiBsZXQgY2FuY2VsbGFibGUgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pO1xuICAgICAqIGNhbmNlbGxhYmxlLnRoZW4oKCkgPT4geyAuLi4gfSkuY2F0Y2goY29uc29sZS5sb2cpO1xuICAgICAqIGNhbmNlbGxhYmxlLmNhbmNlbCgpOyAvLyBBIENhbmNlbEVycm9yIGlzIHByaW50ZWQgdG8gdGhlIGNvbnNvbGUuXG4gICAgICogYGBgXG4gICAgICogSWYgdGhlIGBDYW5jZWxFcnJvcmAgaXMgbm90IGhhbmRsZWQgZG93bnN0cmVhbSBieSB0aGUgdGltZSBpdCByZWFjaGVzXG4gICAgICogYSBfbm9uLWNhbmNlbGxlZF8gcHJvbWlzZSwgaXQgX3dpbGxfIHRyaWdnZXIgYW4gdW5oYW5kbGVkIHJlamVjdGlvbiBldmVudCxcbiAgICAgKiBqdXN0IGxpa2Ugbm9ybWFsIHJlamVjdGlvbnMgd291bGQ6XG4gICAgICogYGBgdHNcbiAgICAgKiBsZXQgY2FuY2VsbGFibGUgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsgLi4uIH0pO1xuICAgICAqIGxldCBjaGFpbmVkID0gY2FuY2VsbGFibGUudGhlbigoKSA9PiB7IC4uLiB9KS50aGVuKCgpID0+IHsgLi4uIH0pOyAvLyBObyBjYXRjaC4uLlxuICAgICAqIGNhbmNlbGxhYmxlLmNhbmNlbCgpOyAvLyBVbmhhbmRsZWQgcmVqZWN0aW9uIGV2ZW50IG9uIGNoYWluZWQhXG4gICAgICogYGBgXG4gICAgICogVGhlcmVmb3JlLCBpdCBpcyBpbXBvcnRhbnQgdG8gZWl0aGVyIGNhbmNlbCB3aG9sZSBwcm9taXNlIGNoYWlucyBmcm9tIHRoZWlyIHRhaWwsXG4gICAgICogYXMgc2hvd24gaW4gdGhlIGNvcnJlY3QgaWRpb21zIGFib3ZlLCBvciB0YWtlIGNhcmUgb2YgaGFuZGxpbmcgZXJyb3JzIGV2ZXJ5d2hlcmUuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBBIGNhbmNlbGxhYmxlIHByb21pc2UgdGhhdCBfZnVsZmlsbHNfIGFmdGVyIHRoZSBjYW5jZWwgY2FsbGJhY2sgKGlmIGFueSlcbiAgICAgKiBhbmQgYWxsIGhhbmRsZXJzIGF0dGFjaGVkIHVwIHRvIHRoZSBjYWxsIHRvIGNhbmNlbCBoYXZlIHJ1bi5cbiAgICAgKiBJZiB0aGUgY2FuY2VsIGNhbGxiYWNrIHJldHVybnMgYSB0aGVuYWJsZSwgdGhlIHByb21pc2UgcmV0dXJuZWQgYnkgYGNhbmNlbGBcbiAgICAgKiB3aWxsIGFsc28gd2FpdCBmb3IgdGhhdCB0aGVuYWJsZSB0byBzZXR0bGUuXG4gICAgICogVGhpcyBlbmFibGVzIGNhbGxlcnMgdG8gd2FpdCBmb3IgdGhlIGNhbmNlbGxlZCBvcGVyYXRpb24gdG8gdGVybWluYXRlXG4gICAgICogd2l0aG91dCBiZWluZyBmb3JjZWQgdG8gaGFuZGxlIHBvdGVudGlhbCBlcnJvcnMgYXQgdGhlIGNhbGwgc2l0ZS5cbiAgICAgKiBgYGB0c1xuICAgICAqIGNhbmNlbGxhYmxlLmNhbmNlbCgpLnRoZW4oKCkgPT4ge1xuICAgICAqICAgICAvLyBDbGVhbnVwIGZpbmlzaGVkLCBpdCdzIHNhZmUgdG8gZG8gc29tZXRoaW5nIGVsc2UuXG4gICAgICogfSwgKGVycikgPT4ge1xuICAgICAqICAgICAvLyBVbnJlYWNoYWJsZTogdGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSBjYW5jZWwgd2lsbCBuZXZlciByZWplY3QuXG4gICAgICogfSk7XG4gICAgICogYGBgXG4gICAgICogTm90ZSB0aGF0IHRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgX25vdF8gaGFuZGxlIGltcGxpY2l0bHkgYW55IHJlamVjdGlvblxuICAgICAqIHRoYXQgbWlnaHQgaGF2ZSBvY2N1cnJlZCBhbHJlYWR5IGluIHRoZSBjYW5jZWxsZWQgY2hhaW4uXG4gICAgICogSXQgd2lsbCBqdXN0IHRyYWNrIHdoZXRoZXIgcmVnaXN0ZXJlZCBoYW5kbGVycyBoYXZlIGJlZW4gZXhlY3V0ZWQgb3Igbm90LlxuICAgICAqIFRoZXJlZm9yZSwgdW5oYW5kbGVkIHJlamVjdGlvbnMgd2lsbCBuZXZlciBiZSBzaWxlbnRseSBoYW5kbGVkIGJ5IGNhbGxpbmcgY2FuY2VsLlxuICAgICAqL1xuICAgIGNhbmNlbChjYXVzZT86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHZvaWQ+KChyZXNvbHZlKSA9PiB7XG4gICAgICAgICAgICAvLyBJTlZBUklBTlQ6IHRoZSByZXN1bHQgb2YgdGhpc1tjYW5jZWxJbXBsU3ltXSBhbmQgdGhlIGJhcnJpZXIgZG8gbm90IGV2ZXIgcmVqZWN0LlxuICAgICAgICAgICAgLy8gVW5mb3J0dW5hdGVseSBtYWNPUyBIaWdoIFNpZXJyYSBkb2VzIG5vdCBzdXBwb3J0IFByb21pc2UuYWxsU2V0dGxlZC5cbiAgICAgICAgICAgIFByb21pc2UuYWxsKFtcbiAgICAgICAgICAgICAgICB0aGlzW2NhbmNlbEltcGxTeW1dKG5ldyBDYW5jZWxFcnJvcihcIlByb21pc2UgY2FuY2VsbGVkLlwiLCB7IGNhdXNlIH0pKSxcbiAgICAgICAgICAgICAgICBjdXJyZW50QmFycmllcih0aGlzKVxuICAgICAgICAgICAgXSkudGhlbigoKSA9PiByZXNvbHZlKCksICgpID0+IHJlc29sdmUoKSk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEJpbmRzIHByb21pc2UgY2FuY2VsbGF0aW9uIHRvIHRoZSBhYm9ydCBldmVudCBvZiB0aGUgZ2l2ZW4ge0BsaW5rIEFib3J0U2lnbmFsfS5cbiAgICAgKiBJZiB0aGUgc2lnbmFsIGhhcyBhbHJlYWR5IGFib3J0ZWQsIHRoZSBwcm9taXNlIHdpbGwgYmUgY2FuY2VsbGVkIGltbWVkaWF0ZWx5LlxuICAgICAqIFdoZW4gZWl0aGVyIGNvbmRpdGlvbiBpcyB2ZXJpZmllZCwgdGhlIGNhbmNlbGxhdGlvbiBjYXVzZSB3aWxsIGJlIHNldFxuICAgICAqIHRvIHRoZSBzaWduYWwncyBhYm9ydCByZWFzb24gKHNlZSB7QGxpbmsgQWJvcnRTaWduYWwjcmVhc29ufSkuXG4gICAgICpcbiAgICAgKiBIYXMgbm8gZWZmZWN0IGlmIGNhbGxlZCAob3IgaWYgdGhlIHNpZ25hbCBhYm9ydHMpIF9hZnRlcl8gdGhlIHByb21pc2UgaGFzIGFscmVhZHkgc2V0dGxlZC5cbiAgICAgKiBPbmx5IHRoZSBmaXJzdCBzaWduYWwgdG8gYWJvcnQgd2lsbCBzZXQgdGhlIGNhbmNlbGxhdGlvbiBjYXVzZS5cbiAgICAgKlxuICAgICAqIEZvciBtb3JlIGRldGFpbHMgYWJvdXQgdGhlIGNhbmNlbGxhdGlvbiBwcm9jZXNzLFxuICAgICAqIHNlZSB7QGxpbmsgY2FuY2VsfSBhbmQgdGhlIGBDYW5jZWxsYWJsZVByb21pc2VgIGNvbnN0cnVjdG9yLlxuICAgICAqXG4gICAgICogVGhpcyBtZXRob2QgZW5hYmxlcyBgYXdhaXRgaW5nIGNhbmNlbGxhYmxlIHByb21pc2VzIHdpdGhvdXQgaGF2aW5nXG4gICAgICogdG8gc3RvcmUgdGhlbSBmb3IgZnV0dXJlIGNhbmNlbGxhdGlvbiwgZS5nLjpcbiAgICAgKiBgYGB0c1xuICAgICAqIGF3YWl0IGxvbmdSdW5uaW5nT3BlcmF0aW9uKCkuY2FuY2VsT24oc2lnbmFsKTtcbiAgICAgKiBgYGBcbiAgICAgKiBpbnN0ZWFkIG9mOlxuICAgICAqIGBgYHRzXG4gICAgICogbGV0IHByb21pc2VUb0JlQ2FuY2VsbGVkID0gbG9uZ1J1bm5pbmdPcGVyYXRpb24oKTtcbiAgICAgKiBhd2FpdCBwcm9taXNlVG9CZUNhbmNlbGxlZDtcbiAgICAgKiBgYGBcbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoaXMgcHJvbWlzZSwgZm9yIG1ldGhvZCBjaGFpbmluZy5cbiAgICAgKi9cbiAgICBjYW5jZWxPbihzaWduYWw6IEFib3J0U2lnbmFsKTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgaWYgKHNpZ25hbC5hYm9ydGVkKSB7XG4gICAgICAgICAgICB2b2lkIHRoaXMuY2FuY2VsKHNpZ25hbC5yZWFzb24pXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzaWduYWwuYWRkRXZlbnRMaXN0ZW5lcignYWJvcnQnLCAoKSA9PiB2b2lkIHRoaXMuY2FuY2VsKHNpZ25hbC5yZWFzb24pLCB7Y2FwdHVyZTogdHJ1ZX0pO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQXR0YWNoZXMgY2FsbGJhY2tzIGZvciB0aGUgcmVzb2x1dGlvbiBhbmQvb3IgcmVqZWN0aW9uIG9mIHRoZSBgQ2FuY2VsbGFibGVQcm9taXNlYC5cbiAgICAgKlxuICAgICAqIFRoZSBvcHRpb25hbCBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IHdpbGwgYmUgaW52b2tlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCxcbiAgICAgKiB3aXRoIHRoZSBzYW1lIHNlbWFudGljcyBhcyB0aGUgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCBvZiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogV2hlbiB0aGUgcGFyZW50IHByb21pc2UgcmVqZWN0cyBvciBpcyBjYW5jZWxsZWQsIHRoZSBgb25yZWplY3RlZGAgY2FsbGJhY2sgd2lsbCBydW4sXG4gICAgICogX2V2ZW4gYWZ0ZXIgdGhlIHJldHVybmVkIHByb21pc2UgaGFzIGJlZW4gY2FuY2VsbGVkOl9cbiAgICAgKiBpbiB0aGF0IGNhc2UsIHNob3VsZCBpdCByZWplY3Qgb3IgdGhyb3csIHRoZSByZWFzb24gd2lsbCBiZSB3cmFwcGVkXG4gICAgICogaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gb25mdWxmaWxsZWQgVGhlIGNhbGxiYWNrIHRvIGV4ZWN1dGUgd2hlbiB0aGUgUHJvbWlzZSBpcyByZXNvbHZlZC5cbiAgICAgKiBAcGFyYW0gb25yZWplY3RlZCBUaGUgY2FsbGJhY2sgdG8gZXhlY3V0ZSB3aGVuIHRoZSBQcm9taXNlIGlzIHJlamVjdGVkLlxuICAgICAqIEByZXR1cm5zIEEgYENhbmNlbGxhYmxlUHJvbWlzZWAgZm9yIHRoZSBjb21wbGV0aW9uIG9mIHdoaWNoZXZlciBjYWxsYmFjayBpcyBleGVjdXRlZC5cbiAgICAgKiBUaGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBob29rZWQgdXAgdG8gcHJvcGFnYXRlIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB1cCB0aGUgY2hhaW4sIGJ1dCBub3QgZG93bjpcbiAgICAgKlxuICAgICAqICAgLSBpZiB0aGUgcGFyZW50IHByb21pc2UgaXMgY2FuY2VsbGVkLCB0aGUgYG9ucmVqZWN0ZWRgIGhhbmRsZXIgd2lsbCBiZSBpbnZva2VkIHdpdGggYSBgQ2FuY2VsRXJyb3JgXG4gICAgICogICAgIGFuZCB0aGUgcmV0dXJuZWQgcHJvbWlzZSBfd2lsbCByZXNvbHZlIHJlZ3VsYXJseV8gd2l0aCBpdHMgcmVzdWx0O1xuICAgICAqICAgLSBjb252ZXJzZWx5LCBpZiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIF90aGUgcGFyZW50IHByb21pc2UgaXMgY2FuY2VsbGVkIHRvbztfXG4gICAgICogICAgIHRoZSBgb25yZWplY3RlZGAgaGFuZGxlciB3aWxsIHN0aWxsIGJlIGludm9rZWQgd2l0aCB0aGUgcGFyZW50J3MgYENhbmNlbEVycm9yYCxcbiAgICAgKiAgICAgYnV0IGl0cyByZXN1bHQgd2lsbCBiZSBkaXNjYXJkZWRcbiAgICAgKiAgICAgYW5kIHRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgcmVqZWN0IHdpdGggYSBgQ2FuY2VsRXJyb3JgIGFzIHdlbGwuXG4gICAgICpcbiAgICAgKiBUaGUgcHJvbWlzZSByZXR1cm5lZCBmcm9tIHtAbGluayBjYW5jZWx9IHdpbGwgZnVsZmlsbCBvbmx5IGFmdGVyIGFsbCBhdHRhY2hlZCBoYW5kbGVyc1xuICAgICAqIHVwIHRoZSBlbnRpcmUgcHJvbWlzZSBjaGFpbiBoYXZlIGJlZW4gcnVuLlxuICAgICAqXG4gICAgICogSWYgZWl0aGVyIGNhbGxiYWNrIHJldHVybnMgYSBjYW5jZWxsYWJsZSBwcm9taXNlLFxuICAgICAqIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB3aWxsIGJlIGRpdmVydGVkIHRvIGl0LFxuICAgICAqIGFuZCB0aGUgc3BlY2lmaWVkIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICovXG4gICAgdGhlbjxUUmVzdWx0MSA9IFQsIFRSZXN1bHQyID0gbmV2ZXI+KG9uZnVsZmlsbGVkPzogKCh2YWx1ZTogVCkgPT4gVFJlc3VsdDEgfCBQcm9taXNlTGlrZTxUUmVzdWx0MT4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQxPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gVFJlc3VsdDIgfCBQcm9taXNlTGlrZTxUUmVzdWx0Mj4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQyPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbmNhbmNlbGxlZD86IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcik6IENhbmNlbGxhYmxlUHJvbWlzZTxUUmVzdWx0MSB8IFRSZXN1bHQyPiB7XG4gICAgICAgIGlmICghKHRoaXMgaW5zdGFuY2VvZiBDYW5jZWxsYWJsZVByb21pc2UpKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2FuY2VsbGFibGVQcm9taXNlLnByb3RvdHlwZS50aGVuIGNhbGxlZCBvbiBhbiBpbnZhbGlkIG9iamVjdC5cIik7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBOT1RFOiBUeXBlU2NyaXB0J3MgYnVpbHQtaW4gdHlwZSBmb3IgdGhlbiBpcyBicm9rZW4sXG4gICAgICAgIC8vIGFzIGl0IGFsbG93cyBzcGVjaWZ5aW5nIGFuIGFyYml0cmFyeSBUUmVzdWx0MSAhPSBUIGV2ZW4gd2hlbiBvbmZ1bGZpbGxlZCBpcyBub3QgYSBmdW5jdGlvbi5cbiAgICAgICAgLy8gV2UgY2Fubm90IGZpeCBpdCBpZiB3ZSB3YW50IHRvIENhbmNlbGxhYmxlUHJvbWlzZSB0byBpbXBsZW1lbnQgUHJvbWlzZUxpa2U8VD4uXG5cbiAgICAgICAgaWYgKCFpc0NhbGxhYmxlKG9uZnVsZmlsbGVkKSkgeyBvbmZ1bGZpbGxlZCA9IGlkZW50aXR5IGFzIGFueTsgfVxuICAgICAgICBpZiAoIWlzQ2FsbGFibGUob25yZWplY3RlZCkpIHsgb25yZWplY3RlZCA9IHRocm93ZXI7IH1cblxuICAgICAgICBpZiAob25mdWxmaWxsZWQgPT09IGlkZW50aXR5ICYmIG9ucmVqZWN0ZWQgPT0gdGhyb3dlcikge1xuICAgICAgICAgICAgLy8gU2hvcnRjdXQgZm9yIHRyaXZpYWwgYXJndW1lbnRzLlxuICAgICAgICAgICAgcmV0dXJuIG5ldyBDYW5jZWxsYWJsZVByb21pc2UoKHJlc29sdmUpID0+IHJlc29sdmUodGhpcyBhcyBhbnkpKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGJhcnJpZXI6IFBhcnRpYWw8UHJvbWlzZVdpdGhSZXNvbHZlcnM8dm9pZD4+ID0ge307XG4gICAgICAgIHRoaXNbYmFycmllclN5bV0gPSBiYXJyaWVyO1xuXG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFRSZXN1bHQxIHwgVFJlc3VsdDI+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHZvaWQgc3VwZXIudGhlbihcbiAgICAgICAgICAgICAgICAodmFsdWUpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXNbYmFycmllclN5bV0gPT09IGJhcnJpZXIpIHsgdGhpc1tiYXJyaWVyU3ltXSA9IG51bGw7IH1cbiAgICAgICAgICAgICAgICAgICAgYmFycmllci5yZXNvbHZlPy4oKTtcblxuICAgICAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZShvbmZ1bGZpbGxlZCEodmFsdWUpKTtcbiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgKHJlYXNvbj8pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXNbYmFycmllclN5bV0gPT09IGJhcnJpZXIpIHsgdGhpc1tiYXJyaWVyU3ltXSA9IG51bGw7IH1cbiAgICAgICAgICAgICAgICAgICAgYmFycmllci5yZXNvbHZlPy4oKTtcblxuICAgICAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZShvbnJlamVjdGVkIShyZWFzb24pKTtcbiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICk7XG4gICAgICAgIH0sIGFzeW5jIChjYXVzZT8pID0+IHtcbiAgICAgICAgICAgIC8vY2FuY2VsbGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG9uY2FuY2VsbGVkPy4oY2F1c2UpO1xuICAgICAgICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgICAgICAgICBhd2FpdCB0aGlzLmNhbmNlbChjYXVzZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF0dGFjaGVzIGEgY2FsbGJhY2sgZm9yIG9ubHkgdGhlIHJlamVjdGlvbiBvZiB0aGUgUHJvbWlzZS5cbiAgICAgKlxuICAgICAqIFRoZSBvcHRpb25hbCBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IHdpbGwgYmUgaW52b2tlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCxcbiAgICAgKiB3aXRoIHRoZSBzYW1lIHNlbWFudGljcyBhcyB0aGUgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCBvZiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogV2hlbiB0aGUgcGFyZW50IHByb21pc2UgcmVqZWN0cyBvciBpcyBjYW5jZWxsZWQsIHRoZSBgb25yZWplY3RlZGAgY2FsbGJhY2sgd2lsbCBydW4sXG4gICAgICogX2V2ZW4gYWZ0ZXIgdGhlIHJldHVybmVkIHByb21pc2UgaGFzIGJlZW4gY2FuY2VsbGVkOl9cbiAgICAgKiBpbiB0aGF0IGNhc2UsIHNob3VsZCBpdCByZWplY3Qgb3IgdGhyb3csIHRoZSByZWFzb24gd2lsbCBiZSB3cmFwcGVkXG4gICAgICogaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICpcbiAgICAgKiBJdCBpcyBlcXVpdmFsZW50IHRvXG4gICAgICogYGBgdHNcbiAgICAgKiBjYW5jZWxsYWJsZVByb21pc2UudGhlbih1bmRlZmluZWQsIG9ucmVqZWN0ZWQsIG9uY2FuY2VsbGVkKTtcbiAgICAgKiBgYGBcbiAgICAgKiBhbmQgdGhlIHNhbWUgY2F2ZWF0cyBhcHBseS5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIEEgUHJvbWlzZSBmb3IgdGhlIGNvbXBsZXRpb24gb2YgdGhlIGNhbGxiYWNrLlxuICAgICAqIENhbmNlbGxhdGlvbiByZXF1ZXN0cyBvbiB0aGUgcmV0dXJuZWQgcHJvbWlzZVxuICAgICAqIHdpbGwgcHJvcGFnYXRlIHVwIHRoZSBjaGFpbiB0byB0aGUgcGFyZW50IHByb21pc2UsXG4gICAgICogYnV0IG5vdCBpbiB0aGUgb3RoZXIgZGlyZWN0aW9uLlxuICAgICAqXG4gICAgICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB7QGxpbmsgY2FuY2VsfSB3aWxsIGZ1bGZpbGwgb25seSBhZnRlciBhbGwgYXR0YWNoZWQgaGFuZGxlcnNcbiAgICAgKiB1cCB0aGUgZW50aXJlIHByb21pc2UgY2hhaW4gaGF2ZSBiZWVuIHJ1bi5cbiAgICAgKlxuICAgICAqIElmIGBvbnJlamVjdGVkYCByZXR1cm5zIGEgY2FuY2VsbGFibGUgcHJvbWlzZSxcbiAgICAgKiBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBkaXZlcnRlZCB0byBpdCxcbiAgICAgKiBhbmQgdGhlIHNwZWNpZmllZCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqIFNlZSB7QGxpbmsgdGhlbn0gZm9yIG1vcmUgZGV0YWlscy5cbiAgICAgKi9cbiAgICBjYXRjaDxUUmVzdWx0ID0gbmV2ZXI+KG9ucmVqZWN0ZWQ/OiAoKHJlYXNvbjogYW55KSA9PiAoUHJvbWlzZUxpa2U8VFJlc3VsdD4gfCBUUmVzdWx0KSkgfCB1bmRlZmluZWQgfCBudWxsLCBvbmNhbmNlbGxlZD86IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcik6IENhbmNlbGxhYmxlUHJvbWlzZTxUIHwgVFJlc3VsdD4ge1xuICAgICAgICByZXR1cm4gdGhpcy50aGVuKHVuZGVmaW5lZCwgb25yZWplY3RlZCwgb25jYW5jZWxsZWQpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF0dGFjaGVzIGEgY2FsbGJhY2sgdGhhdCBpcyBpbnZva2VkIHdoZW4gdGhlIENhbmNlbGxhYmxlUHJvbWlzZSBpcyBzZXR0bGVkIChmdWxmaWxsZWQgb3IgcmVqZWN0ZWQpLiBUaGVcbiAgICAgKiByZXNvbHZlZCB2YWx1ZSBjYW5ub3QgYmUgYWNjZXNzZWQgb3IgbW9kaWZpZWQgZnJvbSB0aGUgY2FsbGJhY2suXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2Ugd2lsbCBzZXR0bGUgaW4gdGhlIHNhbWUgc3RhdGUgYXMgdGhlIG9yaWdpbmFsIG9uZVxuICAgICAqIGFmdGVyIHRoZSBwcm92aWRlZCBjYWxsYmFjayBoYXMgY29tcGxldGVkIGV4ZWN1dGlvbixcbiAgICAgKiB1bmxlc3MgdGhlIGNhbGxiYWNrIHRocm93cyBvciByZXR1cm5zIGEgcmVqZWN0aW5nIHByb21pc2UsXG4gICAgICogaW4gd2hpY2ggY2FzZSB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIHJlamVjdCBhcyB3ZWxsLlxuICAgICAqXG4gICAgICogVGhlIG9wdGlvbmFsIGBvbmNhbmNlbGxlZGAgYXJndW1lbnQgd2lsbCBiZSBpbnZva2VkIHdoZW4gdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLFxuICAgICAqIHdpdGggdGhlIHNhbWUgc2VtYW50aWNzIGFzIHRoZSBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IG9mIHRoZSBjb25zdHJ1Y3Rvci5cbiAgICAgKiBPbmNlIHRoZSBwYXJlbnQgcHJvbWlzZSBzZXR0bGVzLCB0aGUgYG9uZmluYWxseWAgY2FsbGJhY2sgd2lsbCBydW4sXG4gICAgICogX2V2ZW4gYWZ0ZXIgdGhlIHJldHVybmVkIHByb21pc2UgaGFzIGJlZW4gY2FuY2VsbGVkOl9cbiAgICAgKiBpbiB0aGF0IGNhc2UsIHNob3VsZCBpdCByZWplY3Qgb3IgdGhyb3csIHRoZSByZWFzb24gd2lsbCBiZSB3cmFwcGVkXG4gICAgICogaW4gYSB7QGxpbmsgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3J9IGFuZCBidWJibGVkIHVwIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb24uXG4gICAgICpcbiAgICAgKiBUaGlzIG1ldGhvZCBpcyBpbXBsZW1lbnRlZCBpbiB0ZXJtcyBvZiB7QGxpbmsgdGhlbn0gYW5kIHRoZSBzYW1lIGNhdmVhdHMgYXBwbHkuXG4gICAgICogSXQgaXMgcG9seWZpbGxlZCwgaGVuY2UgYXZhaWxhYmxlIGluIGV2ZXJ5IE9TL3dlYnZpZXcgdmVyc2lvbi5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIEEgUHJvbWlzZSBmb3IgdGhlIGNvbXBsZXRpb24gb2YgdGhlIGNhbGxiYWNrLlxuICAgICAqIENhbmNlbGxhdGlvbiByZXF1ZXN0cyBvbiB0aGUgcmV0dXJuZWQgcHJvbWlzZVxuICAgICAqIHdpbGwgcHJvcGFnYXRlIHVwIHRoZSBjaGFpbiB0byB0aGUgcGFyZW50IHByb21pc2UsXG4gICAgICogYnV0IG5vdCBpbiB0aGUgb3RoZXIgZGlyZWN0aW9uLlxuICAgICAqXG4gICAgICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB7QGxpbmsgY2FuY2VsfSB3aWxsIGZ1bGZpbGwgb25seSBhZnRlciBhbGwgYXR0YWNoZWQgaGFuZGxlcnNcbiAgICAgKiB1cCB0aGUgZW50aXJlIHByb21pc2UgY2hhaW4gaGF2ZSBiZWVuIHJ1bi5cbiAgICAgKlxuICAgICAqIElmIGBvbmZpbmFsbHlgIHJldHVybnMgYSBjYW5jZWxsYWJsZSBwcm9taXNlLFxuICAgICAqIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyB3aWxsIGJlIGRpdmVydGVkIHRvIGl0LFxuICAgICAqIGFuZCB0aGUgc3BlY2lmaWVkIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICogU2VlIHtAbGluayB0aGVufSBmb3IgbW9yZSBkZXRhaWxzLlxuICAgICAqL1xuICAgIGZpbmFsbHkob25maW5hbGx5PzogKCgpID0+IHZvaWQpIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8VD4ge1xuICAgICAgICBpZiAoISh0aGlzIGluc3RhbmNlb2YgQ2FuY2VsbGFibGVQcm9taXNlKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNhbmNlbGxhYmxlUHJvbWlzZS5wcm90b3R5cGUuZmluYWxseSBjYWxsZWQgb24gYW4gaW52YWxpZCBvYmplY3QuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFpc0NhbGxhYmxlKG9uZmluYWxseSkpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnRoZW4ob25maW5hbGx5LCBvbmZpbmFsbHksIG9uY2FuY2VsbGVkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLnRoZW4oXG4gICAgICAgICAgICAodmFsdWUpID0+IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKG9uZmluYWxseSgpKS50aGVuKCgpID0+IHZhbHVlKSxcbiAgICAgICAgICAgIChyZWFzb24/KSA9PiBDYW5jZWxsYWJsZVByb21pc2UucmVzb2x2ZShvbmZpbmFsbHkoKSkudGhlbigoKSA9PiB7IHRocm93IHJlYXNvbjsgfSksXG4gICAgICAgICAgICBvbmNhbmNlbGxlZCxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBXZSB1c2UgdGhlIGBbU3ltYm9sLnNwZWNpZXNdYCBzdGF0aWMgcHJvcGVydHksIGlmIGF2YWlsYWJsZSxcbiAgICAgKiB0byBkaXNhYmxlIHRoZSBidWlsdC1pbiBhdXRvbWF0aWMgc3ViY2xhc3NpbmcgZmVhdHVyZXMgZnJvbSB7QGxpbmsgUHJvbWlzZX0uXG4gICAgICogSXQgaXMgY3JpdGljYWwgZm9yIHBlcmZvcm1hbmNlIHJlYXNvbnMgdGhhdCBleHRlbmRlcnMgZG8gbm90IG92ZXJyaWRlIHRoaXMuXG4gICAgICogT25jZSB0aGUgcHJvcG9zYWwgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RjMzkvcHJvcG9zYWwtcm0tYnVpbHRpbi1zdWJjbGFzc2luZ1xuICAgICAqIGlzIGVpdGhlciBhY2NlcHRlZCBvciByZXRpcmVkLCB0aGlzIGltcGxlbWVudGF0aW9uIHdpbGwgaGF2ZSB0byBiZSByZXZpc2VkIGFjY29yZGluZ2x5LlxuICAgICAqXG4gICAgICogQGlnbm9yZVxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQgW3NwZWNpZXNdKCkge1xuICAgICAgICByZXR1cm4gUHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgQ2FuY2VsbGFibGVQcm9taXNlIHRoYXQgaXMgcmVzb2x2ZWQgd2l0aCBhbiBhcnJheSBvZiByZXN1bHRzXG4gICAgICogd2hlbiBhbGwgb2YgdGhlIHByb3ZpZGVkIFByb21pc2VzIHJlc29sdmUsIG9yIHJlamVjdGVkIHdoZW4gYW55IFByb21pc2UgaXMgcmVqZWN0ZWQuXG4gICAgICpcbiAgICAgKiBFdmVyeSBvbmUgb2YgdGhlIHByb3ZpZGVkIG9iamVjdHMgdGhhdCBpcyBhIHRoZW5hYmxlIF9hbmRfIGNhbmNlbGxhYmxlIG9iamVjdFxuICAgICAqIHdpbGwgYmUgY2FuY2VsbGVkIHdoZW4gdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLCB3aXRoIHRoZSBzYW1lIGNhdXNlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIGFsbDxUPih2YWx1ZXM6IEl0ZXJhYmxlPFQgfCBQcm9taXNlTGlrZTxUPj4pOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPltdPjtcbiAgICBzdGF0aWMgYWxsPFQgZXh0ZW5kcyByZWFkb25seSB1bmtub3duW10gfCBbXT4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHsgLXJlYWRvbmx5IFtQIGluIGtleW9mIFRdOiBBd2FpdGVkPFRbUF0+OyB9PjtcbiAgICBzdGF0aWMgYWxsPFQgZXh0ZW5kcyBJdGVyYWJsZTx1bmtub3duPiB8IEFycmF5TGlrZTx1bmtub3duPj4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+IHtcbiAgICAgICAgbGV0IGNvbGxlY3RlZCA9IEFycmF5LmZyb20odmFsdWVzKTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IGNvbGxlY3RlZC5sZW5ndGggPT09IDBcbiAgICAgICAgICAgID8gQ2FuY2VsbGFibGVQcm9taXNlLnJlc29sdmUoY29sbGVjdGVkKVxuICAgICAgICAgICAgOiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgICAgICB2b2lkIFByb21pc2UuYWxsKGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfSwgKGNhdXNlPyk6IFByb21pc2U8dm9pZD4gPT4gY2FuY2VsQWxsKHByb21pc2UsIGNvbGxlY3RlZCwgY2F1c2UpKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IGlzIHJlc29sdmVkIHdpdGggYW4gYXJyYXkgb2YgcmVzdWx0c1xuICAgICAqIHdoZW4gYWxsIG9mIHRoZSBwcm92aWRlZCBQcm9taXNlcyByZXNvbHZlIG9yIHJlamVjdC5cbiAgICAgKlxuICAgICAqIEV2ZXJ5IG9uZSBvZiB0aGUgcHJvdmlkZWQgb2JqZWN0cyB0aGF0IGlzIGEgdGhlbmFibGUgX2FuZF8gY2FuY2VsbGFibGUgb2JqZWN0XG4gICAgICogd2lsbCBiZSBjYW5jZWxsZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIHdpdGggdGhlIHNhbWUgY2F1c2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgYWxsU2V0dGxlZDxUPih2YWx1ZXM6IEl0ZXJhYmxlPFQgfCBQcm9taXNlTGlrZTxUPj4pOiBDYW5jZWxsYWJsZVByb21pc2U8UHJvbWlzZVNldHRsZWRSZXN1bHQ8QXdhaXRlZDxUPj5bXT47XG4gICAgc3RhdGljIGFsbFNldHRsZWQ8VCBleHRlbmRzIHJlYWRvbmx5IHVua25vd25bXSB8IFtdPih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8eyAtcmVhZG9ubHkgW1AgaW4ga2V5b2YgVF06IFByb21pc2VTZXR0bGVkUmVzdWx0PEF3YWl0ZWQ8VFtQXT4+OyB9PjtcbiAgICBzdGF0aWMgYWxsU2V0dGxlZDxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBjb2xsZWN0ZWQubGVuZ3RoID09PSAwXG4gICAgICAgICAgICA/IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKGNvbGxlY3RlZClcbiAgICAgICAgICAgIDogbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdm9pZCBQcm9taXNlLmFsbFNldHRsZWQoY29sbGVjdGVkKS50aGVuKHJlc29sdmUsIHJlamVjdCk7XG4gICAgICAgICAgICB9LCAoY2F1c2U/KTogUHJvbWlzZTx2b2lkPiA9PiBjYW5jZWxBbGwocHJvbWlzZSwgY29sbGVjdGVkLCBjYXVzZSkpO1xuICAgICAgICByZXR1cm4gcHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGUgYW55IGZ1bmN0aW9uIHJldHVybnMgYSBwcm9taXNlIHRoYXQgaXMgZnVsZmlsbGVkIGJ5IHRoZSBmaXJzdCBnaXZlbiBwcm9taXNlIHRvIGJlIGZ1bGZpbGxlZCxcbiAgICAgKiBvciByZWplY3RlZCB3aXRoIGFuIEFnZ3JlZ2F0ZUVycm9yIGNvbnRhaW5pbmcgYW4gYXJyYXkgb2YgcmVqZWN0aW9uIHJlYXNvbnNcbiAgICAgKiBpZiBhbGwgb2YgdGhlIGdpdmVuIHByb21pc2VzIGFyZSByZWplY3RlZC5cbiAgICAgKiBJdCByZXNvbHZlcyBhbGwgZWxlbWVudHMgb2YgdGhlIHBhc3NlZCBpdGVyYWJsZSB0byBwcm9taXNlcyBhcyBpdCBydW5zIHRoaXMgYWxnb3JpdGhtLlxuICAgICAqXG4gICAgICogRXZlcnkgb25lIG9mIHRoZSBwcm92aWRlZCBvYmplY3RzIHRoYXQgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiB3aWxsIGJlIGNhbmNlbGxlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgd2l0aCB0aGUgc2FtZSBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBhbnk8VD4odmFsdWVzOiBJdGVyYWJsZTxUIHwgUHJvbWlzZUxpa2U8VD4+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+O1xuICAgIHN0YXRpYyBhbnk8VCBleHRlbmRzIHJlYWRvbmx5IHVua25vd25bXSB8IFtdPih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUW251bWJlcl0+PjtcbiAgICBzdGF0aWMgYW55PFQgZXh0ZW5kcyBJdGVyYWJsZTx1bmtub3duPiB8IEFycmF5TGlrZTx1bmtub3duPj4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+IHtcbiAgICAgICAgbGV0IGNvbGxlY3RlZCA9IEFycmF5LmZyb20odmFsdWVzKTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IGNvbGxlY3RlZC5sZW5ndGggPT09IDBcbiAgICAgICAgICAgID8gQ2FuY2VsbGFibGVQcm9taXNlLnJlc29sdmUoY29sbGVjdGVkKVxuICAgICAgICAgICAgOiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgICAgICB2b2lkIFByb21pc2UuYW55KGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfSwgKGNhdXNlPyk6IFByb21pc2U8dm9pZD4gPT4gY2FuY2VsQWxsKHByb21pc2UsIGNvbGxlY3RlZCwgY2F1c2UpKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIFByb21pc2UgdGhhdCBpcyByZXNvbHZlZCBvciByZWplY3RlZCB3aGVuIGFueSBvZiB0aGUgcHJvdmlkZWQgUHJvbWlzZXMgYXJlIHJlc29sdmVkIG9yIHJlamVjdGVkLlxuICAgICAqXG4gICAgICogRXZlcnkgb25lIG9mIHRoZSBwcm92aWRlZCBvYmplY3RzIHRoYXQgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiB3aWxsIGJlIGNhbmNlbGxlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgd2l0aCB0aGUgc2FtZSBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByYWNlPFQ+KHZhbHVlczogSXRlcmFibGU8VCB8IFByb21pc2VMaWtlPFQ+Pik6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFQ+PjtcbiAgICBzdGF0aWMgcmFjZTxUIGV4dGVuZHMgcmVhZG9ubHkgdW5rbm93bltdIHwgW10+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFRbbnVtYmVyXT4+O1xuICAgIHN0YXRpYyByYWNlPFQgZXh0ZW5kcyBJdGVyYWJsZTx1bmtub3duPiB8IEFycmF5TGlrZTx1bmtub3duPj4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+IHtcbiAgICAgICAgbGV0IGNvbGxlY3RlZCA9IEFycmF5LmZyb20odmFsdWVzKTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgdm9pZCBQcm9taXNlLnJhY2UoY29sbGVjdGVkKS50aGVuKHJlc29sdmUsIHJlamVjdCk7XG4gICAgICAgIH0sIChjYXVzZT8pOiBQcm9taXNlPHZvaWQ+ID0+IGNhbmNlbEFsbChwcm9taXNlLCBjb2xsZWN0ZWQsIGNhdXNlKSk7XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgY2FuY2VsbGVkIENhbmNlbGxhYmxlUHJvbWlzZSBmb3IgdGhlIHByb3ZpZGVkIGNhdXNlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIGNhbmNlbDxUID0gbmV2ZXI+KGNhdXNlPzogYW55KTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgY29uc3QgcCA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKCkgPT4ge30pO1xuICAgICAgICBwLmNhbmNlbChjYXVzZSk7XG4gICAgICAgIHJldHVybiBwO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlIHRoYXQgY2FuY2Vsc1xuICAgICAqIGFmdGVyIHRoZSBzcGVjaWZpZWQgdGltZW91dCwgd2l0aCB0aGUgcHJvdmlkZWQgY2F1c2UuXG4gICAgICpcbiAgICAgKiBJZiB0aGUge0BsaW5rIEFib3J0U2lnbmFsLnRpbWVvdXR9IGZhY3RvcnkgbWV0aG9kIGlzIGF2YWlsYWJsZSxcbiAgICAgKiBpdCBpcyB1c2VkIHRvIGJhc2UgdGhlIHRpbWVvdXQgb24gX2FjdGl2ZV8gdGltZSByYXRoZXIgdGhhbiBfZWxhcHNlZF8gdGltZS5cbiAgICAgKiBPdGhlcndpc2UsIGB0aW1lb3V0YCBmYWxscyBiYWNrIHRvIHtAbGluayBzZXRUaW1lb3V0fS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyB0aW1lb3V0PFQgPSBuZXZlcj4obWlsbGlzZWNvbmRzOiBudW1iZXIsIGNhdXNlPzogYW55KTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKCkgPT4ge30pO1xuICAgICAgICBpZiAoQWJvcnRTaWduYWwgJiYgdHlwZW9mIEFib3J0U2lnbmFsID09PSAnZnVuY3Rpb24nICYmIEFib3J0U2lnbmFsLnRpbWVvdXQgJiYgdHlwZW9mIEFib3J0U2lnbmFsLnRpbWVvdXQgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICAgIEFib3J0U2lnbmFsLnRpbWVvdXQobWlsbGlzZWNvbmRzKS5hZGRFdmVudExpc3RlbmVyKCdhYm9ydCcsICgpID0+IHZvaWQgcHJvbWlzZS5jYW5jZWwoY2F1c2UpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4gdm9pZCBwcm9taXNlLmNhbmNlbChjYXVzZSksIG1pbGxpc2Vjb25kcyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBDYW5jZWxsYWJsZVByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlciB0aGUgc3BlY2lmaWVkIHRpbWVvdXQuXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2UgY2FuIGJlIGNhbmNlbGxlZCB3aXRob3V0IGNvbnNlcXVlbmNlcy5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBzbGVlcChtaWxsaXNlY29uZHM6IG51bWJlcik6IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPjtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IHJlc29sdmVzIGFmdGVyXG4gICAgICogdGhlIHNwZWNpZmllZCB0aW1lb3V0LCB3aXRoIHRoZSBwcm92aWRlZCB2YWx1ZS5cbiAgICAgKiBUaGUgcmV0dXJuZWQgcHJvbWlzZSBjYW4gYmUgY2FuY2VsbGVkIHdpdGhvdXQgY29uc2VxdWVuY2VzLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHNsZWVwPFQ+KG1pbGxpc2Vjb25kczogbnVtYmVyLCB2YWx1ZTogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPjtcbiAgICBzdGF0aWMgc2xlZXA8VCA9IHZvaWQ+KG1pbGxpc2Vjb25kczogbnVtYmVyLCB2YWx1ZT86IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8VD4ge1xuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTxUPigocmVzb2x2ZSkgPT4ge1xuICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiByZXNvbHZlKHZhbHVlISksIG1pbGxpc2Vjb25kcyk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgcmVqZWN0ZWQgQ2FuY2VsbGFibGVQcm9taXNlIGZvciB0aGUgcHJvdmlkZWQgcmVhc29uLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHJlamVjdDxUID0gbmV2ZXI+KHJlYXNvbj86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KChfLCByZWplY3QpID0+IHJlamVjdChyZWFzb24pKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlc29sdmVkIENhbmNlbGxhYmxlUHJvbWlzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZXNvbHZlKCk6IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPjtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlc29sdmVkIENhbmNlbGxhYmxlUHJvbWlzZSBmb3IgdGhlIHByb3ZpZGVkIHZhbHVlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHJlc29sdmU8VD4odmFsdWU6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPj47XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyByZXNvbHZlZCBDYW5jZWxsYWJsZVByb21pc2UgZm9yIHRoZSBwcm92aWRlZCB2YWx1ZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZXNvbHZlPFQ+KHZhbHVlOiBUIHwgUHJvbWlzZUxpa2U8VD4pOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPj47XG4gICAgc3RhdGljIHJlc29sdmU8VCA9IHZvaWQ+KHZhbHVlPzogVCB8IFByb21pc2VMaWtlPFQ+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+IHtcbiAgICAgICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgQ2FuY2VsbGFibGVQcm9taXNlKSB7XG4gICAgICAgICAgICAvLyBPcHRpbWlzZSBmb3IgY2FuY2VsbGFibGUgcHJvbWlzZXMuXG4gICAgICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5ldyBDYW5jZWxsYWJsZVByb21pc2U8YW55PigocmVzb2x2ZSkgPT4gcmVzb2x2ZSh2YWx1ZSkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlIGFuZCByZXR1cm5zIGl0IGluIGFuIG9iamVjdCwgYWxvbmcgd2l0aCBpdHMgcmVzb2x2ZSBhbmQgcmVqZWN0IGZ1bmN0aW9uc1xuICAgICAqIGFuZCBhIGdldHRlci9zZXR0ZXIgZm9yIHRoZSBjYW5jZWxsYXRpb24gY2FsbGJhY2suXG4gICAgICpcbiAgICAgKiBUaGlzIG1ldGhvZCBpcyBwb2x5ZmlsbGVkLCBoZW5jZSBhdmFpbGFibGUgaW4gZXZlcnkgT1Mvd2VidmlldyB2ZXJzaW9uLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIHdpdGhSZXNvbHZlcnM8VD4oKTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgICAgIGxldCByZXN1bHQ6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4gPSB7IG9uY2FuY2VsbGVkOiBudWxsIH0gYXMgYW55O1xuICAgICAgICByZXN1bHQucHJvbWlzZSA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgcmVzdWx0LnJlc29sdmUgPSByZXNvbHZlO1xuICAgICAgICAgICAgcmVzdWx0LnJlamVjdCA9IHJlamVjdDtcbiAgICAgICAgfSwgKGNhdXNlPzogYW55KSA9PiB7IHJlc3VsdC5vbmNhbmNlbGxlZD8uKGNhdXNlKTsgfSk7XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWxsYmFjayB0aGF0IGltcGxlbWVudHMgdGhlIGNhbmNlbGxhdGlvbiBhbGdvcml0aG0gZm9yIHRoZSBnaXZlbiBjYW5jZWxsYWJsZSBwcm9taXNlLlxuICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB0aGUgcmVzdWx0aW5nIGZ1bmN0aW9uIGRvZXMgbm90IHJlamVjdC5cbiAqL1xuZnVuY3Rpb24gY2FuY2VsbGVyRm9yPFQ+KHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4sIHN0YXRlOiBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSkge1xuICAgIGxldCBjYW5jZWxsYXRpb25Qcm9taXNlOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD4gPSB1bmRlZmluZWQ7XG5cbiAgICByZXR1cm4gKHJlYXNvbjogQ2FuY2VsRXJyb3IpOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD4gPT4ge1xuICAgICAgICBpZiAoIXN0YXRlLnNldHRsZWQpIHtcbiAgICAgICAgICAgIHN0YXRlLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAgICAgc3RhdGUucmVhc29uID0gcmVhc29uO1xuICAgICAgICAgICAgcHJvbWlzZS5yZWplY3QocmVhc29uKTtcblxuICAgICAgICAgICAgLy8gQXR0YWNoIGFuIGVycm9yIGhhbmRsZXIgdGhhdCBpZ25vcmVzIHRoaXMgc3BlY2lmaWMgcmVqZWN0aW9uIHJlYXNvbiBhbmQgbm90aGluZyBlbHNlLlxuICAgICAgICAgICAgLy8gSW4gdGhlb3J5LCBhIHNhbmUgdW5kZXJseWluZyBpbXBsZW1lbnRhdGlvbiBhdCB0aGlzIHBvaW50XG4gICAgICAgICAgICAvLyBzaG91bGQgYWx3YXlzIHJlamVjdCB3aXRoIG91ciBjYW5jZWxsYXRpb24gcmVhc29uLFxuICAgICAgICAgICAgLy8gaGVuY2UgdGhlIGhhbmRsZXIgd2lsbCBuZXZlciB0aHJvdy5cbiAgICAgICAgICAgIHZvaWQgUHJvbWlzZS5wcm90b3R5cGUudGhlbi5jYWxsKHByb21pc2UucHJvbWlzZSwgdW5kZWZpbmVkLCAoZXJyKSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKGVyciAhPT0gcmVhc29uKSB7XG4gICAgICAgICAgICAgICAgICAgIHRocm93IGVycjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIElmIHJlYXNvbiBpcyBub3Qgc2V0LCB0aGUgcHJvbWlzZSByZXNvbHZlZCByZWd1bGFybHksIGhlbmNlIHdlIG11c3Qgbm90IGNhbGwgb25jYW5jZWxsZWQuXG4gICAgICAgIC8vIElmIG9uY2FuY2VsbGVkIGlzIHVuc2V0LCBubyBuZWVkIHRvIGdvIGFueSBmdXJ0aGVyLlxuICAgICAgICBpZiAoIXN0YXRlLnJlYXNvbiB8fCAhcHJvbWlzZS5vbmNhbmNlbGxlZCkgeyByZXR1cm47IH1cblxuICAgICAgICBjYW5jZWxsYXRpb25Qcm9taXNlID0gbmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUpID0+IHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShwcm9taXNlLm9uY2FuY2VsbGVkIShzdGF0ZS5yZWFzb24hLmNhdXNlKSk7XG4gICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICBQcm9taXNlLnJlamVjdChuZXcgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3IocHJvbWlzZS5wcm9taXNlLCBlcnIsIFwiVW5oYW5kbGVkIGV4Y2VwdGlvbiBpbiBvbmNhbmNlbGxlZCBjYWxsYmFjay5cIikpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KS5jYXRjaCgocmVhc29uPykgPT4ge1xuICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHByb21pc2UucHJvbWlzZSwgcmVhc29uLCBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gb25jYW5jZWxsZWQgY2FsbGJhY2suXCIpKTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gVW5zZXQgb25jYW5jZWxsZWQgdG8gcHJldmVudCByZXBlYXRlZCBjYWxscy5cbiAgICAgICAgcHJvbWlzZS5vbmNhbmNlbGxlZCA9IG51bGw7XG5cbiAgICAgICAgcmV0dXJuIGNhbmNlbGxhdGlvblByb21pc2U7XG4gICAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWxsYmFjayB0aGF0IGltcGxlbWVudHMgdGhlIHJlc29sdXRpb24gYWxnb3JpdGhtIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS5cbiAqL1xuZnVuY3Rpb24gcmVzb2x2ZXJGb3I8VD4ocHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiwgc3RhdGU6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlKTogQ2FuY2VsbGFibGVQcm9taXNlUmVzb2x2ZXI8VD4ge1xuICAgIHJldHVybiAodmFsdWUpID0+IHtcbiAgICAgICAgaWYgKHN0YXRlLnJlc29sdmluZykgeyByZXR1cm47IH1cbiAgICAgICAgc3RhdGUucmVzb2x2aW5nID0gdHJ1ZTtcblxuICAgICAgICBpZiAodmFsdWUgPT09IHByb21pc2UucHJvbWlzZSkge1xuICAgICAgICAgICAgaWYgKHN0YXRlLnNldHRsZWQpIHsgcmV0dXJuOyB9XG4gICAgICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIHByb21pc2UucmVqZWN0KG5ldyBUeXBlRXJyb3IoXCJBIHByb21pc2UgY2Fubm90IGJlIHJlc29sdmVkIHdpdGggaXRzZWxmLlwiKSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodmFsdWUgIT0gbnVsbCAmJiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyB8fCB0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicpKSB7XG4gICAgICAgICAgICBsZXQgdGhlbjogYW55O1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICB0aGVuID0gKHZhbHVlIGFzIGFueSkudGhlbjtcbiAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgIHN0YXRlLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIHByb21pc2UucmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoaXNDYWxsYWJsZSh0aGVuKSkge1xuICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAgIGxldCBjYW5jZWwgPSAodmFsdWUgYXMgYW55KS5jYW5jZWw7XG4gICAgICAgICAgICAgICAgICAgIGlmIChpc0NhbGxhYmxlKGNhbmNlbCkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IG9uY2FuY2VsbGVkID0gKGNhdXNlPzogYW55KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVmbGVjdC5hcHBseShjYW5jZWwsIHZhbHVlLCBbY2F1c2VdKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc3RhdGUucmVhc29uKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gSWYgYWxyZWFkeSBjYW5jZWxsZWQsIHByb3BhZ2F0ZSBjYW5jZWxsYXRpb24uXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB0aGUgY2FuY2VsbGVyIGFsZ29yaXRobSBkb2VzIG5vdCByZWplY3RcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBzbyBpdCBjYW4gYmUgZGlzY2FyZGVkIHNhZmVseS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2b2lkIGNhbmNlbGxlckZvcih7IC4uLnByb21pc2UsIG9uY2FuY2VsbGVkIH0sIHN0YXRlKShzdGF0ZS5yZWFzb24pO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9taXNlLm9uY2FuY2VsbGVkID0gb25jYW5jZWxsZWQ7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9IGNhdGNoIHt9XG5cbiAgICAgICAgICAgICAgICBjb25zdCBuZXdTdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUgPSB7XG4gICAgICAgICAgICAgICAgICAgIHJvb3Q6IHN0YXRlLnJvb3QsXG4gICAgICAgICAgICAgICAgICAgIHJlc29sdmluZzogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIGdldCBzZXR0bGVkKCkgeyByZXR1cm4gdGhpcy5yb290LnNldHRsZWQgfSxcbiAgICAgICAgICAgICAgICAgICAgc2V0IHNldHRsZWQodmFsdWUpIHsgdGhpcy5yb290LnNldHRsZWQgPSB2YWx1ZTsgfSxcbiAgICAgICAgICAgICAgICAgICAgZ2V0IHJlYXNvbigpIHsgcmV0dXJuIHRoaXMucm9vdC5yZWFzb24gfVxuICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICBjb25zdCByZWplY3RvciA9IHJlamVjdG9yRm9yKHByb21pc2UsIG5ld1N0YXRlKTtcbiAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICBSZWZsZWN0LmFwcGx5KHRoZW4sIHZhbHVlLCBbcmVzb2x2ZXJGb3IocHJvbWlzZSwgbmV3U3RhdGUpLCByZWplY3Rvcl0pO1xuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICByZWplY3RvcihlcnIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm47IC8vIElNUE9SVEFOVCFcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChzdGF0ZS5zZXR0bGVkKSB7IHJldHVybjsgfVxuICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgcHJvbWlzZS5yZXNvbHZlKHZhbHVlKTtcbiAgICB9O1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWxsYmFjayB0aGF0IGltcGxlbWVudHMgdGhlIHJlamVjdGlvbiBhbGdvcml0aG0gZm9yIHRoZSBnaXZlbiBjYW5jZWxsYWJsZSBwcm9taXNlLlxuICovXG5mdW5jdGlvbiByZWplY3RvckZvcjxUPihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+LCBzdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUpOiBDYW5jZWxsYWJsZVByb21pc2VSZWplY3RvciB7XG4gICAgcmV0dXJuIChyZWFzb24/KSA9PiB7XG4gICAgICAgIGlmIChzdGF0ZS5yZXNvbHZpbmcpIHsgcmV0dXJuOyB9XG4gICAgICAgIHN0YXRlLnJlc29sdmluZyA9IHRydWU7XG5cbiAgICAgICAgaWYgKHN0YXRlLnNldHRsZWQpIHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgaWYgKHJlYXNvbiBpbnN0YW5jZW9mIENhbmNlbEVycm9yICYmIHN0YXRlLnJlYXNvbiBpbnN0YW5jZW9mIENhbmNlbEVycm9yICYmIE9iamVjdC5pcyhyZWFzb24uY2F1c2UsIHN0YXRlLnJlYXNvbi5jYXVzZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gU3dhbGxvdyBsYXRlIHJlamVjdGlvbnMgdGhhdCBhcmUgQ2FuY2VsRXJyb3JzIHdob3NlIGNhbmNlbGxhdGlvbiBjYXVzZSBpcyB0aGUgc2FtZSBhcyBvdXJzLlxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBjYXRjaCB7fVxuXG4gICAgICAgICAgICB2b2lkIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwcm9taXNlLnByb21pc2UsIHJlYXNvbikpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc3RhdGUuc2V0dGxlZCA9IHRydWU7XG4gICAgICAgICAgICBwcm9taXNlLnJlamVjdChyZWFzb24pO1xuICAgICAgICB9XG4gICAgfVxufVxuXG4vKipcbiAqIENhbmNlbHMgYWxsIHZhbHVlcyBpbiBhbiBhcnJheSB0aGF0IGxvb2sgbGlrZSBjYW5jZWxsYWJsZSB0aGVuYWJsZXMuXG4gKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IGZ1bGZpbGxzIG9uY2UgYWxsIGNhbmNlbGxhdGlvbiBwcm9jZWR1cmVzIGZvciB0aGUgZ2l2ZW4gdmFsdWVzIGhhdmUgc2V0dGxlZC5cbiAqL1xuZnVuY3Rpb24gY2FuY2VsQWxsKHBhcmVudDogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+LCB2YWx1ZXM6IGFueVtdLCBjYXVzZT86IGFueSk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHJlc3VsdHM6IFByb21pc2U8dm9pZD5bXSA9IFtdO1xuXG4gICAgZm9yIChjb25zdCB2YWx1ZSBvZiB2YWx1ZXMpIHtcbiAgICAgICAgbGV0IGNhbmNlbDogQ2FuY2VsbGFibGVQcm9taXNlQ2FuY2VsbGVyO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgaWYgKCFpc0NhbGxhYmxlKHZhbHVlLnRoZW4pKSB7IGNvbnRpbnVlOyB9XG4gICAgICAgICAgICBjYW5jZWwgPSB2YWx1ZS5jYW5jZWw7XG4gICAgICAgICAgICBpZiAoIWlzQ2FsbGFibGUoY2FuY2VsKSkgeyBjb250aW51ZTsgfVxuICAgICAgICB9IGNhdGNoIHsgY29udGludWU7IH1cblxuICAgICAgICBsZXQgcmVzdWx0OiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXN1bHQgPSBSZWZsZWN0LmFwcGx5KGNhbmNlbCwgdmFsdWUsIFtjYXVzZV0pO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwYXJlbnQsIGVyciwgXCJVbmhhbmRsZWQgZXhjZXB0aW9uIGluIGNhbmNlbCBtZXRob2QuXCIpKTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFyZXN1bHQpIHsgY29udGludWU7IH1cbiAgICAgICAgcmVzdWx0cy5wdXNoKFxuICAgICAgICAgICAgKHJlc3VsdCBpbnN0YW5jZW9mIFByb21pc2UgID8gcmVzdWx0IDogUHJvbWlzZS5yZXNvbHZlKHJlc3VsdCkpLmNhdGNoKChyZWFzb24/KSA9PiB7XG4gICAgICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHBhcmVudCwgcmVhc29uLCBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gY2FuY2VsIG1ldGhvZC5cIikpO1xuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICByZXR1cm4gUHJvbWlzZS5hbGwocmVzdWx0cykgYXMgYW55O1xufVxuXG4vKipcbiAqIFJldHVybnMgaXRzIGFyZ3VtZW50LlxuICovXG5mdW5jdGlvbiBpZGVudGl0eTxUPih4OiBUKTogVCB7XG4gICAgcmV0dXJuIHg7XG59XG5cbi8qKlxuICogVGhyb3dzIGl0cyBhcmd1bWVudC5cbiAqL1xuZnVuY3Rpb24gdGhyb3dlcihyZWFzb24/OiBhbnkpOiBuZXZlciB7XG4gICAgdGhyb3cgcmVhc29uO1xufVxuXG4vKipcbiAqIEF0dGVtcHRzIHZhcmlvdXMgc3RyYXRlZ2llcyB0byBjb252ZXJ0IGFuIGVycm9yIHRvIGEgc3RyaW5nLlxuICovXG5mdW5jdGlvbiBlcnJvck1lc3NhZ2UoZXJyOiBhbnkpOiBzdHJpbmcge1xuICAgIHRyeSB7XG4gICAgICAgIGlmIChlcnIgaW5zdGFuY2VvZiBFcnJvciB8fCB0eXBlb2YgZXJyICE9PSAnb2JqZWN0JyB8fCBlcnIudG9TdHJpbmcgIT09IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcpIHtcbiAgICAgICAgICAgIHJldHVybiBcIlwiICsgZXJyO1xuICAgICAgICB9XG4gICAgfSBjYXRjaCB7fVxuXG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KGVycik7XG4gICAgfSBjYXRjaCB7fVxuXG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChlcnIpO1xuICAgIH0gY2F0Y2gge31cblxuICAgIHJldHVybiBcIjxjb3VsZCBub3QgY29udmVydCBlcnJvciB0byBzdHJpbmc+XCI7XG59XG5cbi8qKlxuICogR2V0cyB0aGUgY3VycmVudCBiYXJyaWVyIHByb21pc2UgZm9yIHRoZSBnaXZlbiBjYW5jZWxsYWJsZSBwcm9taXNlLiBJZiBuZWNlc3NhcnksIGluaXRpYWxpc2VzIHRoZSBiYXJyaWVyLlxuICovXG5mdW5jdGlvbiBjdXJyZW50QmFycmllcjxUPihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2U8VD4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsZXQgcHdyOiBQYXJ0aWFsPFByb21pc2VXaXRoUmVzb2x2ZXJzPHZvaWQ+PiA9IHByb21pc2VbYmFycmllclN5bV0gPz8ge307XG4gICAgaWYgKCEoJ3Byb21pc2UnIGluIHB3cikpIHtcbiAgICAgICAgT2JqZWN0LmFzc2lnbihwd3IsIHByb21pc2VXaXRoUmVzb2x2ZXJzPHZvaWQ+KCkpO1xuICAgIH1cbiAgICBpZiAocHJvbWlzZVtiYXJyaWVyU3ltXSA9PSBudWxsKSB7XG4gICAgICAgIHB3ci5yZXNvbHZlISgpO1xuICAgICAgICBwcm9taXNlW2JhcnJpZXJTeW1dID0gcHdyO1xuICAgIH1cbiAgICByZXR1cm4gcHdyLnByb21pc2UhO1xufVxuXG4vLyBQb2x5ZmlsbCBQcm9taXNlLndpdGhSZXNvbHZlcnMuXG5sZXQgcHJvbWlzZVdpdGhSZXNvbHZlcnMgPSBQcm9taXNlLndpdGhSZXNvbHZlcnM7XG5pZiAocHJvbWlzZVdpdGhSZXNvbHZlcnMgJiYgdHlwZW9mIHByb21pc2VXaXRoUmVzb2x2ZXJzID09PSAnZnVuY3Rpb24nKSB7XG4gICAgcHJvbWlzZVdpdGhSZXNvbHZlcnMgPSBwcm9taXNlV2l0aFJlc29sdmVycy5iaW5kKFByb21pc2UpO1xufSBlbHNlIHtcbiAgICBwcm9taXNlV2l0aFJlc29sdmVycyA9IGZ1bmN0aW9uIDxUPigpOiBQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgICAgIGxldCByZXNvbHZlITogKHZhbHVlOiBUIHwgUHJvbWlzZUxpa2U8VD4pID0+IHZvaWQ7XG4gICAgICAgIGxldCByZWplY3QhOiAocmVhc29uPzogYW55KSA9PiB2b2lkO1xuICAgICAgICBjb25zdCBwcm9taXNlID0gbmV3IFByb21pc2U8VD4oKHJlcywgcmVqKSA9PiB7IHJlc29sdmUgPSByZXM7IHJlamVjdCA9IHJlajsgfSk7XG4gICAgICAgIHJldHVybiB7IHByb21pc2UsIHJlc29sdmUsIHJlamVjdCB9O1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5DbGlwYm9hcmQpO1xuXG5jb25zdCBDbGlwYm9hcmRTZXRUZXh0ID0gMDtcbmNvbnN0IENsaXBib2FyZFRleHQgPSAxO1xuXG4vKipcbiAqIFNldHMgdGhlIHRleHQgdG8gdGhlIENsaXBib2FyZC5cbiAqXG4gKiBAcGFyYW0gdGV4dCAtIFRoZSB0ZXh0IHRvIGJlIHNldCB0byB0aGUgQ2xpcGJvYXJkLlxuICogQHJldHVybiBBIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBvcGVyYXRpb24gaXMgc3VjY2Vzc2Z1bC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNldFRleHQodGV4dDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoQ2xpcGJvYXJkU2V0VGV4dCwge3RleHR9KTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIENsaXBib2FyZCB0ZXh0XG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgdGV4dCBmcm9tIHRoZSBDbGlwYm9hcmQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBUZXh0KCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgcmV0dXJuIGNhbGwoQ2xpcGJvYXJkVGV4dCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmV4cG9ydCBpbnRlcmZhY2UgU2l6ZSB7XG4gICAgLyoqIFRoZSB3aWR0aCBvZiBhIHJlY3Rhbmd1bGFyIGFyZWEuICovXG4gICAgV2lkdGg6IG51bWJlcjtcbiAgICAvKiogVGhlIGhlaWdodCBvZiBhIHJlY3Rhbmd1bGFyIGFyZWEuICovXG4gICAgSGVpZ2h0OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVjdCB7XG4gICAgLyoqIFRoZSBYIGNvb3JkaW5hdGUgb2YgdGhlIG9yaWdpbi4gKi9cbiAgICBYOiBudW1iZXI7XG4gICAgLyoqIFRoZSBZIGNvb3JkaW5hdGUgb2YgdGhlIG9yaWdpbi4gKi9cbiAgICBZOiBudW1iZXI7XG4gICAgLyoqIFRoZSB3aWR0aCBvZiB0aGUgcmVjdGFuZ2xlLiAqL1xuICAgIFdpZHRoOiBudW1iZXI7XG4gICAgLyoqIFRoZSBoZWlnaHQgb2YgdGhlIHJlY3RhbmdsZS4gKi9cbiAgICBIZWlnaHQ6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTY3JlZW4ge1xuICAgIC8qKiBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHNjcmVlbi4gKi9cbiAgICBJRDogc3RyaW5nO1xuICAgIC8qKiBIdW1hbi1yZWFkYWJsZSBuYW1lIG9mIHRoZSBzY3JlZW4uICovXG4gICAgTmFtZTogc3RyaW5nO1xuICAgIC8qKiBUaGUgc2NhbGUgZmFjdG9yIG9mIHRoZSBzY3JlZW4gKERQSS85NikuIDEgPSBzdGFuZGFyZCBEUEksIDIgPSBIaURQSSAoUmV0aW5hKSwgZXRjLiAqL1xuICAgIFNjYWxlRmFjdG9yOiBudW1iZXI7XG4gICAgLyoqIFRoZSBYIGNvb3JkaW5hdGUgb2YgdGhlIHNjcmVlbi4gKi9cbiAgICBYOiBudW1iZXI7XG4gICAgLyoqIFRoZSBZIGNvb3JkaW5hdGUgb2YgdGhlIHNjcmVlbi4gKi9cbiAgICBZOiBudW1iZXI7XG4gICAgLyoqIENvbnRhaW5zIHRoZSB3aWR0aCBhbmQgaGVpZ2h0IG9mIHRoZSBzY3JlZW4uICovXG4gICAgU2l6ZTogU2l6ZTtcbiAgICAvKiogQ29udGFpbnMgdGhlIGJvdW5kcyBvZiB0aGUgc2NyZWVuIGluIHRlcm1zIG9mIFgsIFksIFdpZHRoLCBhbmQgSGVpZ2h0LiAqL1xuICAgIEJvdW5kczogUmVjdDtcbiAgICAvKiogQ29udGFpbnMgdGhlIHBoeXNpY2FsIGJvdW5kcyBvZiB0aGUgc2NyZWVuIGluIHRlcm1zIG9mIFgsIFksIFdpZHRoLCBhbmQgSGVpZ2h0IChiZWZvcmUgc2NhbGluZykuICovXG4gICAgUGh5c2ljYWxCb3VuZHM6IFJlY3Q7XG4gICAgLyoqIENvbnRhaW5zIHRoZSBhcmVhIG9mIHRoZSBzY3JlZW4gdGhhdCBpcyBhY3R1YWxseSB1c2FibGUgKGV4Y2x1ZGluZyB0YXNrYmFyIGFuZCBvdGhlciBzeXN0ZW0gVUkpLiAqL1xuICAgIFdvcmtBcmVhOiBSZWN0O1xuICAgIC8qKiBDb250YWlucyB0aGUgcGh5c2ljYWwgV29ya0FyZWEgb2YgdGhlIHNjcmVlbiAoYmVmb3JlIHNjYWxpbmcpLiAqL1xuICAgIFBoeXNpY2FsV29ya0FyZWE6IFJlY3Q7XG4gICAgLyoqIFRydWUgaWYgdGhpcyBpcyB0aGUgcHJpbWFyeSBtb25pdG9yIHNlbGVjdGVkIGJ5IHRoZSB1c2VyIGluIHRoZSBvcGVyYXRpbmcgc3lzdGVtLiAqL1xuICAgIElzUHJpbWFyeTogYm9vbGVhbjtcbiAgICAvKiogVGhlIHJvdGF0aW9uIG9mIHRoZSBzY3JlZW4uICovXG4gICAgUm90YXRpb246IG51bWJlcjtcbn1cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5TY3JlZW5zKTtcblxuY29uc3QgZ2V0QWxsID0gMDtcbmNvbnN0IGdldFByaW1hcnkgPSAxO1xuY29uc3QgZ2V0Q3VycmVudCA9IDI7XG5cbi8qKlxuICogR2V0cyBhbGwgc2NyZWVucy5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byBhbiBhcnJheSBvZiBTY3JlZW4gb2JqZWN0cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEFsbCgpOiBQcm9taXNlPFNjcmVlbltdPiB7XG4gICAgcmV0dXJuIGNhbGwoZ2V0QWxsKTtcbn1cblxuLyoqXG4gKiBHZXRzIHRoZSBwcmltYXJ5IHNjcmVlbi5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0aGUgcHJpbWFyeSBzY3JlZW4uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRQcmltYXJ5KCk6IFByb21pc2U8U2NyZWVuPiB7XG4gICAgcmV0dXJuIGNhbGwoZ2V0UHJpbWFyeSk7XG59XG5cbi8qKlxuICogR2V0cyB0aGUgY3VycmVudCBhY3RpdmUgc2NyZWVuLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEN1cnJlbnQoKTogUHJvbWlzZTxTY3JlZW4+IHtcbiAgICByZXR1cm4gY2FsbChnZXRDdXJyZW50KTtcbn1cbiIsICIvKlxuIF8gICAgIF9fICAgICBfIF9fXG58IHwgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLklPUyk7XG5cbi8vIE1ldGhvZCBJRHNcbmNvbnN0IEhhcHRpY3NJbXBhY3QgPSAwO1xuY29uc3QgRGV2aWNlSW5mbyA9IDE7XG5cbmV4cG9ydCBuYW1lc3BhY2UgSGFwdGljcyB7XG4gICAgZXhwb3J0IHR5cGUgSW1wYWN0U3R5bGUgPSBcImxpZ2h0XCJ8XCJtZWRpdW1cInxcImhlYXZ5XCJ8XCJzb2Z0XCJ8XCJyaWdpZFwiO1xuICAgIGV4cG9ydCBmdW5jdGlvbiBJbXBhY3Qoc3R5bGU6IEltcGFjdFN0eWxlID0gXCJtZWRpdW1cIik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gY2FsbChIYXB0aWNzSW1wYWN0LCB7IHN0eWxlIH0pO1xuICAgIH1cbn1cblxuZXhwb3J0IG5hbWVzcGFjZSBEZXZpY2Uge1xuICAgIGV4cG9ydCBpbnRlcmZhY2UgSW5mbyB7XG4gICAgICAgIG1vZGVsOiBzdHJpbmc7XG4gICAgICAgIHN5c3RlbU5hbWU6IHN0cmluZztcbiAgICAgICAgc3lzdGVtVmVyc2lvbjogc3RyaW5nO1xuICAgICAgICBpc1NpbXVsYXRvcjogYm9vbGVhbjtcbiAgICB9XG4gICAgZXhwb3J0IGZ1bmN0aW9uIEluZm8oKTogUHJvbWlzZTxJbmZvPiB7XG4gICAgICAgIHJldHVybiBjYWxsKERldmljZUluZm8pO1xuICAgIH1cbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7QUNBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7QUNBQTtBQUFBO0FBQUE7QUFBQTs7O0FDNkJBLElBQU0sY0FDRjtBQUVHLFNBQVMsT0FBTyxPQUFlLElBQVk7QUFDOUMsTUFBSSxLQUFLO0FBRVQsTUFBSSxJQUFJLE9BQU87QUFDZixTQUFPLEtBQUs7QUFFUixVQUFNLFlBQWEsS0FBSyxPQUFPLElBQUksS0FBTSxD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QyxTQUFPQSxNQUFLLFVBQVU7QUFDMUI7OztBQ3ZGQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBWUEsSUFBTUMsU0FBTyxpQkFBaUIsWUFBWSxHQUFHO0FBRzdDLElBQU0sZ0JBQWdCO0FBQ3RCLElBQU0sYUFBYTtBQUVaLElBQVU7QUFBQSxDQUFWLENBQVVDLGFBQVY7QUFFSSxXQUFTLE9BQU8sUUFBcUIsVUFBeUI7QUFDakUsV0FBT0QsT0FBSyxlQUFlLEVBQUUsTUFBTSxDQUFDO0FBQUEsRUFDeEM7QUFGTyxFQUFBQyxTQUFTO0FBQUEsR0FGSDtBQU9WLElBQVU7QUFBQSxDQUFWLENBQVVDLFlBQVY7QUFPSSxXQUFTQyxRQUFzQjtBQUNsQyxXQUFPSCxPQUFLLFVBQVU7QUFBQSxFQUMxQjtBQUZPLEVBQUFFLFFBQVMsT0FBQUM7QUFBQSxHQVBIOzs7QXZCZGpCLE9BQU8sU0FBUyxPQUFPLFVBQVUsQ0FBQztBQXNEbEMsT0FBTyxPQUFPLFNBQWdCO0FBSzlCLE9BQU8sT0FBTyx5QkFBeUIsZUFBTyx1QkFBdUIsS0FBSyxjQUFNO0FBRXpFLE9BQU8scUJBQXFCOyIsCiAgIm5hbWVzIjogWyJfYSIsICJFcnJvciIsICJjYWxsIiwgIkVycm9yIiwgIl9hIiwgIkFycmF5IiwgIk1hcCIsICJBcnJheSIsICJNYXAiLCAia2V5IiwgImNhbGwiLCAiX2EiLCAicmVzaXphYmxlIiwgImNhbGwiLCAiX2EiLCAiY2FsbCIsICJfYSIsICJjYWxsIiwgIkhpZGVNZXRob2QiLCAiU2hvd01ldGhvZCIsICJpc0RvY3VtZW50RG90QWxsIiwgIl9hIiwgInJlYXNvbiIsICJ2YWx1ZSIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgIkhhcHRpY3MiLCAiRGV2aWNlIiwgIkluZm8iXQp9Cg== +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2luZGV4LnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy93bWwudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2Jyb3dzZXIudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL25hbm9pZC50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvcnVudGltZS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZGlhbG9ncy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZXZlbnRzLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9saXN0ZW5lci50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY3JlYXRlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9ldmVudF90eXBlcy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvdXRpbHMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3dpbmRvdy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvY29tcGlsZWQvbWFpbi5qcyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvc3lzdGVtLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jb250ZXh0bWVudS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZmxhZ3MudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2RyYWcudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2FwcGxpY2F0aW9uLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jYWxscy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY2FsbGFibGUudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2NhbmNlbGxhYmxlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jbGlwYm9hcmQudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3NjcmVlbnMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2lvcy50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcblxuaW1wb3J0IFwiLi9jb250ZXh0bWVudS5qc1wiO1xuaW1wb3J0IFwiLi9kcmFnLmpzXCI7XG5cbi8vIFJlLWV4cG9ydCBwdWJsaWMgQVBJXG5pbXBvcnQgKiBhcyBBcHBsaWNhdGlvbiBmcm9tIFwiLi9hcHBsaWNhdGlvbi5qc1wiO1xuaW1wb3J0ICogYXMgQnJvd3NlciBmcm9tIFwiLi9icm93c2VyLmpzXCI7XG5pbXBvcnQgKiBhcyBDYWxsIGZyb20gXCIuL2NhbGxzLmpzXCI7XG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSBcIi4vY2xpcGJvYXJkLmpzXCI7XG5pbXBvcnQgKiBhcyBDcmVhdGUgZnJvbSBcIi4vY3JlYXRlLmpzXCI7XG5pbXBvcnQgKiBhcyBEaWFsb2dzIGZyb20gXCIuL2RpYWxvZ3MuanNcIjtcbmltcG9ydCAqIGFzIEV2ZW50cyBmcm9tIFwiLi9ldmVudHMuanNcIjtcbmltcG9ydCAqIGFzIEZsYWdzIGZyb20gXCIuL2ZsYWdzLmpzXCI7XG5pbXBvcnQgKiBhcyBTY3JlZW5zIGZyb20gXCIuL3NjcmVlbnMuanNcIjtcbmltcG9ydCAqIGFzIFN5c3RlbSBmcm9tIFwiLi9zeXN0ZW0uanNcIjtcbmltcG9ydCAqIGFzIElPUyBmcm9tIFwiLi9pb3MuanNcIjtcbmltcG9ydCBXaW5kb3csIHsgaGFuZGxlRHJhZ0VudGVyLCBoYW5kbGVEcmFnTGVhdmUsIGhhbmRsZURyYWdPdmVyIH0gZnJvbSBcIi4vd2luZG93LmpzXCI7XG5pbXBvcnQgKiBhcyBXTUwgZnJvbSBcIi4vd21sLmpzXCI7XG5cbmV4cG9ydCB7XG4gICAgQXBwbGljYXRpb24sXG4gICAgQnJvd3NlcixcbiAgICBDYWxsLFxuICAgIENsaXBib2FyZCxcbiAgICBEaWFsb2dzLFxuICAgIEV2ZW50cyxcbiAgICBGbGFncyxcbiAgICBTY3JlZW5zLFxuICAgIFN5c3RlbSxcbiAgICBJT1MsXG4gICAgV2luZG93LFxuICAgIFdNTFxufTtcblxuLyoqXG4gKiBBbiBpbnRlcm5hbCB1dGlsaXR5IGNvbnN1bWVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqXG4gKiBAaWdub3JlXG4gKi9cbmV4cG9ydCB7IENyZWF0ZSB9O1xuXG5leHBvcnQgKiBmcm9tIFwiLi9jYW5jZWxsYWJsZS5qc1wiO1xuXG4vLyBFeHBvcnQgdHJhbnNwb3J0IGludGVyZmFjZXMgYW5kIHV0aWxpdGllc1xuZXhwb3J0IHtcbiAgICBzZXRUcmFuc3BvcnQsXG4gICAgZ2V0VHJhbnNwb3J0LFxuICAgIHR5cGUgUnVudGltZVRyYW5zcG9ydCxcbiAgICBvYmplY3ROYW1lcyxcbiAgICBjbGllbnRJZCxcbn0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG4vLyBOb3RpZnkgYmFja2VuZFxud2luZG93Ll93YWlscy5pbnZva2UgPSBTeXN0ZW0uaW52b2tlO1xuXG4vLyBSZWdpc3RlciBwbGF0Zm9ybSBoYW5kbGVycyAoaW50ZXJuYWwgQVBJKVxuLy8gTm90ZTogV2luZG93IGlzIHRoZSB0aGlzV2luZG93IGluc3RhbmNlIChkZWZhdWx0IGV4cG9ydCBmcm9tIHdpbmRvdy50cylcbi8vIEJpbmRpbmcgZW5zdXJlcyAndGhpcycgY29ycmVjdGx5IHJlZmVycyB0byB0aGUgY3VycmVudCB3aW5kb3cgaW5zdGFuY2VcbndpbmRvdy5fd2FpbHMuaGFuZGxlUGxhdGZvcm1GaWxlRHJvcCA9IFdpbmRvdy5IYW5kbGVQbGF0Zm9ybUZpbGVEcm9wLmJpbmQoV2luZG93KTtcblxuLy8gTGludXgtc3BlY2lmaWMgZHJhZyBoYW5kbGVycyAoR1RLIGludGVyY2VwdHMgRE9NIGRyYWcgZXZlbnRzKVxud2luZG93Ll93YWlscy5oYW5kbGVEcmFnRW50ZXIgPSBoYW5kbGVEcmFnRW50ZXI7XG53aW5kb3cuX3dhaWxzLmhhbmRsZURyYWdMZWF2ZSA9IGhhbmRsZURyYWdMZWF2ZTtcbndpbmRvdy5fd2FpbHMuaGFuZGxlRHJhZ092ZXIgPSBoYW5kbGVEcmFnT3ZlcjtcblxuU3lzdGVtLmludm9rZShcIndhaWxzOnJ1bnRpbWU6cmVhZHlcIik7XG4iLCAiLypcbiBfICAgICBfXyAgICAgXyBfX1xufCB8ICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IE9wZW5VUkwgfSBmcm9tIFwiLi9icm93c2VyLmpzXCI7XG5pbXBvcnQgeyBRdWVzdGlvbiB9IGZyb20gXCIuL2RpYWxvZ3MuanNcIjtcbmltcG9ydCB7IEVtaXQgfSBmcm9tIFwiLi9ldmVudHMuanNcIjtcbmltcG9ydCB7IGNhbkFib3J0TGlzdGVuZXJzLCB3aGVuUmVhZHkgfSBmcm9tIFwiLi91dGlscy5qc1wiO1xuaW1wb3J0IFdpbmRvdyBmcm9tIFwiLi93aW5kb3cuanNcIjtcblxuLyoqXG4gKiBTZW5kcyBhbiBldmVudCB3aXRoIHRoZSBnaXZlbiBuYW1lIGFuZCBvcHRpb25hbCBkYXRhLlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byBzZW5kLlxuICogQHBhcmFtIFtkYXRhPW51bGxdIC0gLSBPcHRpb25hbCBkYXRhIHRvIHNlbmQgYWxvbmcgd2l0aCB0aGUgZXZlbnQuXG4gKi9cbmZ1bmN0aW9uIHNlbmRFdmVudChldmVudE5hbWU6IHN0cmluZywgZGF0YTogYW55ID0gbnVsbCk6IHZvaWQge1xuICAgIEVtaXQoZXZlbnROYW1lLCBkYXRhKTtcbn1cblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBvbiBhIHNwZWNpZmllZCB3aW5kb3cuXG4gKlxuICogQHBhcmFtIHdpbmRvd05hbWUgLSBUaGUgbmFtZSBvZiB0aGUgd2luZG93IHRvIGNhbGwgdGhlIG1ldGhvZCBvbi5cbiAqIEBwYXJhbSBtZXRob2ROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIG1ldGhvZCB0byBjYWxsLlxuICovXG5mdW5jdGlvbiBjYWxsV2luZG93TWV0aG9kKHdpbmRvd05hbWU6IHN0cmluZywgbWV0aG9kTmFtZTogc3RyaW5nKSB7XG4gICAgY29uc3QgdGFyZ2V0V2luZG93ID0gV2luZG93LkdldCh3aW5kb3dOYW1lKTtcbiAgICBjb25zdCBtZXRob2QgPSAodGFyZ2V0V2luZG93IGFzIGFueSlbbWV0aG9kTmFtZV07XG5cbiAgICBpZiAodHlwZW9mIG1ldGhvZCAhPT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYFdpbmRvdyBtZXRob2QgJyR7bWV0aG9kTmFtZX0nIG5vdCBmb3VuZGApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgICAgbWV0aG9kLmNhbGwodGFyZ2V0V2luZG93KTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYEVycm9yIGNhbGxpbmcgd2luZG93IG1ldGhvZCAnJHttZXRob2ROYW1lfSc6IGAsIGUpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZXNwb25kcyB0byBhIHRyaWdnZXJpbmcgZXZlbnQgYnkgcnVubmluZyBhcHByb3ByaWF0ZSBXTUwgYWN0aW9ucyBmb3IgdGhlIGN1cnJlbnQgdGFyZ2V0LlxuICovXG5mdW5jdGlvbiBvbldNTFRyaWdnZXJlZChldjogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZXYuY3VycmVudFRhcmdldCBhcyBFbGVtZW50O1xuXG4gICAgZnVuY3Rpb24gcnVuRWZmZWN0KGNob2ljZSA9IFwiWWVzXCIpIHtcbiAgICAgICAgaWYgKGNob2ljZSAhPT0gXCJZZXNcIilcbiAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICBjb25zdCBldmVudFR5cGUgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLWV2ZW50JykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWV2ZW50Jyk7XG4gICAgICAgIGNvbnN0IHRhcmdldFdpbmRvdyA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdGFyZ2V0LXdpbmRvdycpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10YXJnZXQtd2luZG93JykgfHwgXCJcIjtcbiAgICAgICAgY29uc3Qgd2luZG93TWV0aG9kID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC13aW5kb3cnKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtd2luZG93Jyk7XG4gICAgICAgIGNvbnN0IHVybCA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtb3BlbnVybCcpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1vcGVudXJsJyk7XG5cbiAgICAgICAgaWYgKGV2ZW50VHlwZSAhPT0gbnVsbClcbiAgICAgICAgICAgIHNlbmRFdmVudChldmVudFR5cGUpO1xuICAgICAgICBpZiAod2luZG93TWV0aG9kICE9PSBudWxsKVxuICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh0YXJnZXRXaW5kb3csIHdpbmRvd01ldGhvZCk7XG4gICAgICAgIGlmICh1cmwgIT09IG51bGwpXG4gICAgICAgICAgICB2b2lkIE9wZW5VUkwodXJsKTtcbiAgICB9XG5cbiAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1jb25maXJtJykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWNvbmZpcm0nKTtcblxuICAgIGlmIChjb25maXJtKSB7XG4gICAgICAgIFF1ZXN0aW9uKHtcbiAgICAgICAgICAgIFRpdGxlOiBcIkNvbmZpcm1cIixcbiAgICAgICAgICAgIE1lc3NhZ2U6IGNvbmZpcm0sXG4gICAgICAgICAgICBEZXRhY2hlZDogZmFsc2UsXG4gICAgICAgICAgICBCdXR0b25zOiBbXG4gICAgICAgICAgICAgICAgeyBMYWJlbDogXCJZZXNcIiB9LFxuICAgICAgICAgICAgICAgIHsgTGFiZWw6IFwiTm9cIiwgSXNEZWZhdWx0OiB0cnVlIH1cbiAgICAgICAgICAgIF1cbiAgICAgICAgfSkudGhlbihydW5FZmZlY3QpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJ1bkVmZmVjdCgpO1xuICAgIH1cbn1cblxuLy8gUHJpdmF0ZSBmaWVsZCBuYW1lcy5cbmNvbnN0IGNvbnRyb2xsZXJTeW0gPSBTeW1ib2woXCJjb250cm9sbGVyXCIpO1xuY29uc3QgdHJpZ2dlck1hcFN5bSA9IFN5bWJvbChcInRyaWdnZXJNYXBcIik7XG5jb25zdCBlbGVtZW50Q291bnRTeW0gPSBTeW1ib2woXCJlbGVtZW50Q291bnRcIik7XG5cbi8qKlxuICogQWJvcnRDb250cm9sbGVyUmVnaXN0cnkgZG9lcyBub3QgYWN0dWFsbHkgcmVtZW1iZXIgYWN0aXZlIGV2ZW50IGxpc3RlbmVyczogaW5zdGVhZFxuICogaXQgdGllcyB0aGVtIHRvIGFuIEFib3J0U2lnbmFsIGFuZCB1c2VzIGFuIEFib3J0Q29udHJvbGxlciB0byByZW1vdmUgdGhlbSBhbGwgYXQgb25jZS5cbiAqL1xuY2xhc3MgQWJvcnRDb250cm9sbGVyUmVnaXN0cnkge1xuICAgIC8vIFByaXZhdGUgZmllbGRzLlxuICAgIFtjb250cm9sbGVyU3ltXTogQWJvcnRDb250cm9sbGVyO1xuXG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXNbY29udHJvbGxlclN5bV0gPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhbiBvcHRpb25zIG9iamVjdCBmb3IgYWRkRXZlbnRMaXN0ZW5lciB0aGF0IHRpZXMgdGhlIGxpc3RlbmVyXG4gICAgICogdG8gdGhlIEFib3J0U2lnbmFsIGZyb20gdGhlIGN1cnJlbnQgQWJvcnRDb250cm9sbGVyLlxuICAgICAqXG4gICAgICogQHBhcmFtIGVsZW1lbnQgLSBBbiBIVE1MIGVsZW1lbnRcbiAgICAgKiBAcGFyYW0gdHJpZ2dlcnMgLSBUaGUgbGlzdCBvZiBhY3RpdmUgV01MIHRyaWdnZXIgZXZlbnRzIGZvciB0aGUgc3BlY2lmaWVkIGVsZW1lbnRzXG4gICAgICovXG4gICAgc2V0KGVsZW1lbnQ6IEVsZW1lbnQsIHRyaWdnZXJzOiBzdHJpbmdbXSk6IEFkZEV2ZW50TGlzdGVuZXJPcHRpb25zIHtcbiAgICAgICAgcmV0dXJuIHsgc2lnbmFsOiB0aGlzW2NvbnRyb2xsZXJTeW1dLnNpZ25hbCB9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgYWxsIHJlZ2lzdGVyZWQgZXZlbnQgbGlzdGVuZXJzIGFuZCByZXNldHMgdGhlIHJlZ2lzdHJ5LlxuICAgICAqL1xuICAgIHJlc2V0KCk6IHZvaWQge1xuICAgICAgICB0aGlzW2NvbnRyb2xsZXJTeW1dLmFib3J0KCk7XG4gICAgICAgIHRoaXNbY29udHJvbGxlclN5bV0gPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgfVxufVxuXG4vKipcbiAqIFdlYWtNYXBSZWdpc3RyeSBtYXBzIGFjdGl2ZSB0cmlnZ2VyIGV2ZW50cyB0byBlYWNoIERPTSBlbGVtZW50IHRocm91Z2ggYSBXZWFrTWFwLlxuICogVGhpcyBlbnN1cmVzIHRoYXQgdGhlIG1hcHBpbmcgcmVtYWlucyBwcml2YXRlIHRvIHRoaXMgbW9kdWxlLCB3aGlsZSBzdGlsbCBhbGxvd2luZyBnYXJiYWdlXG4gKiBjb2xsZWN0aW9uIG9mIHRoZSBpbnZvbHZlZCBlbGVtZW50cy5cbiAqL1xuY2xhc3MgV2Vha01hcFJlZ2lzdHJ5IHtcbiAgICAvKiogU3RvcmVzIHRoZSBjdXJyZW50IGVsZW1lbnQtdG8tdHJpZ2dlciBtYXBwaW5nLiAqL1xuICAgIFt0cmlnZ2VyTWFwU3ltXTogV2Vha01hcDxFbGVtZW50LCBzdHJpbmdbXT47XG4gICAgLyoqIENvdW50cyB0aGUgbnVtYmVyIG9mIGVsZW1lbnRzIHdpdGggYWN0aXZlIFdNTCB0cmlnZ2Vycy4gKi9cbiAgICBbZWxlbWVudENvdW50U3ltXTogbnVtYmVyO1xuXG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXNbdHJpZ2dlck1hcFN5bV0gPSBuZXcgV2Vha01hcCgpO1xuICAgICAgICB0aGlzW2VsZW1lbnRDb3VudFN5bV0gPSAwO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgYWN0aXZlIHRyaWdnZXJzIGZvciB0aGUgc3BlY2lmaWVkIGVsZW1lbnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZWxlbWVudCAtIEFuIEhUTUwgZWxlbWVudFxuICAgICAqIEBwYXJhbSB0cmlnZ2VycyAtIFRoZSBsaXN0IG9mIGFjdGl2ZSBXTUwgdHJpZ2dlciBldmVudHMgZm9yIHRoZSBzcGVjaWZpZWQgZWxlbWVudFxuICAgICAqL1xuICAgIHNldChlbGVtZW50OiBFbGVtZW50LCB0cmlnZ2Vyczogc3RyaW5nW10pOiBBZGRFdmVudExpc3RlbmVyT3B0aW9ucyB7XG4gICAgICAgIGlmICghdGhpc1t0cmlnZ2VyTWFwU3ltXS5oYXMoZWxlbWVudCkpIHsgdGhpc1tlbGVtZW50Q291bnRTeW1dKys7IH1cbiAgICAgICAgdGhpc1t0cmlnZ2VyTWFwU3ltXS5zZXQoZWxlbWVudCwgdHJpZ2dlcnMpO1xuICAgICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlcyBhbGwgcmVnaXN0ZXJlZCBldmVudCBsaXN0ZW5lcnMuXG4gICAgICovXG4gICAgcmVzZXQoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzW2VsZW1lbnRDb3VudFN5bV0gPD0gMClcbiAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICBmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuYm9keS5xdWVyeVNlbGVjdG9yQWxsKCcqJykpIHtcbiAgICAgICAgICAgIGlmICh0aGlzW2VsZW1lbnRDb3VudFN5bV0gPD0gMClcbiAgICAgICAgICAgICAgICBicmVhaztcblxuICAgICAgICAgICAgY29uc3QgdHJpZ2dlcnMgPSB0aGlzW3RyaWdnZXJNYXBTeW1dLmdldChlbGVtZW50KTtcbiAgICAgICAgICAgIGlmICh0cmlnZ2VycyAhPSBudWxsKSB7IHRoaXNbZWxlbWVudENvdW50U3ltXS0tOyB9XG5cbiAgICAgICAgICAgIGZvciAoY29uc3QgdHJpZ2dlciBvZiB0cmlnZ2VycyB8fCBbXSlcbiAgICAgICAgICAgICAgICBlbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodHJpZ2dlciwgb25XTUxUcmlnZ2VyZWQpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpc1t0cmlnZ2VyTWFwU3ltXSA9IG5ldyBXZWFrTWFwKCk7XG4gICAgICAgIHRoaXNbZWxlbWVudENvdW50U3ltXSA9IDA7XG4gICAgfVxufVxuXG5jb25zdCB0cmlnZ2VyUmVnaXN0cnkgPSBjYW5BYm9ydExpc3RlbmVycygpID8gbmV3IEFib3J0Q29udHJvbGxlclJlZ2lzdHJ5KCkgOiBuZXcgV2Vha01hcFJlZ2lzdHJ5KCk7XG5cbi8qKlxuICogQWRkcyBldmVudCBsaXN0ZW5lcnMgdG8gdGhlIHNwZWNpZmllZCBlbGVtZW50LlxuICovXG5mdW5jdGlvbiBhZGRXTUxMaXN0ZW5lcnMoZWxlbWVudDogRWxlbWVudCk6IHZvaWQge1xuICAgIGNvbnN0IHRyaWdnZXJSZWdFeHAgPSAvXFxTKy9nO1xuICAgIGNvbnN0IHRyaWdnZXJBdHRyID0gKGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtdHJpZ2dlcicpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiKTtcbiAgICBjb25zdCB0cmlnZ2Vyczogc3RyaW5nW10gPSBbXTtcblxuICAgIGxldCBtYXRjaDtcbiAgICB3aGlsZSAoKG1hdGNoID0gdHJpZ2dlclJlZ0V4cC5leGVjKHRyaWdnZXJBdHRyKSkgIT09IG51bGwpXG4gICAgICAgIHRyaWdnZXJzLnB1c2gobWF0Y2hbMF0pO1xuXG4gICAgY29uc3Qgb3B0aW9ucyA9IHRyaWdnZXJSZWdpc3RyeS5zZXQoZWxlbWVudCwgdHJpZ2dlcnMpO1xuICAgIGZvciAoY29uc3QgdHJpZ2dlciBvZiB0cmlnZ2VycylcbiAgICAgICAgZWxlbWVudC5hZGRFdmVudExpc3RlbmVyKHRyaWdnZXIsIG9uV01MVHJpZ2dlcmVkLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTY2hlZHVsZXMgYW4gYXV0b21hdGljIHJlbG9hZCBvZiBXTUwgdG8gYmUgcGVyZm9ybWVkIGFzIHNvb24gYXMgdGhlIGRvY3VtZW50IGlzIGZ1bGx5IGxvYWRlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEVuYWJsZSgpOiB2b2lkIHtcbiAgICB3aGVuUmVhZHkoUmVsb2FkKTtcbn1cblxuLyoqXG4gKiBSZWxvYWRzIHRoZSBXTUwgcGFnZSBieSBhZGRpbmcgbmVjZXNzYXJ5IGV2ZW50IGxpc3RlbmVycyBhbmQgYnJvd3NlciBsaXN0ZW5lcnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZWxvYWQoKTogdm9pZCB7XG4gICAgdHJpZ2dlclJlZ2lzdHJ5LnJlc2V0KCk7XG4gICAgZG9jdW1lbnQuYm9keS5xdWVyeVNlbGVjdG9yQWxsKCdbd21sLWV2ZW50XSwgW3dtbC13aW5kb3ddLCBbd21sLW9wZW51cmxdLCBbZGF0YS13bWwtZXZlbnRdLCBbZGF0YS13bWwtd2luZG93XSwgW2RhdGEtd21sLW9wZW51cmxdJykuZm9yRWFjaChhZGRXTUxMaXN0ZW5lcnMpO1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lcyB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQnJvd3Nlcik7XG5cbmNvbnN0IEJyb3dzZXJPcGVuVVJMID0gMDtcblxuLyoqXG4gKiBPcGVuIGEgYnJvd3NlciB3aW5kb3cgdG8gdGhlIGdpdmVuIFVSTC5cbiAqXG4gKiBAcGFyYW0gdXJsIC0gVGhlIFVSTCB0byBvcGVuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPcGVuVVJMKHVybDogc3RyaW5nIHwgVVJMKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoQnJvd3Nlck9wZW5VUkwsIHt1cmw6IHVybC50b1N0cmluZygpfSk7XG59XG4iLCAiLy8gU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vYWkvbmFub2lkXG5cbi8vIFRoZSBNSVQgTGljZW5zZSAoTUlUKVxuLy9cbi8vIENvcHlyaWdodCAyMDE3IEFuZHJleSBTaXRuaWsgPGFuZHJleUBzaXRuaWsucnU+XG4vL1xuLy8gUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weSBvZlxuLy8gdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgXCJTb2Z0d2FyZVwiKSwgdG8gZGVhbCBpblxuLy8gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cyB0b1xuLy8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2Zcbi8vIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25zIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzIGZ1cm5pc2hlZCB0byBkbyBzbyxcbi8vICAgICBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczpcbi8vXG4vLyAgICAgVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsXG4vLyBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vICAgICBUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELCBFWFBSRVNTIE9SXG4vLyBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTU1xuLy8gRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SXG4vLyBDT1BZUklHSFQgSE9MREVSUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVJcbi8vIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOXG4vLyBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG4vLyBUaGlzIGFscGhhYmV0IHVzZXMgYEEtWmEtejAtOV8tYCBzeW1ib2xzLlxuLy8gVGhlIG9yZGVyIG9mIGNoYXJhY3RlcnMgaXMgb3B0aW1pemVkIGZvciBiZXR0ZXIgZ3ppcCBhbmQgYnJvdGxpIGNvbXByZXNzaW9uLlxuLy8gUmVmZXJlbmNlcyB0byB0aGUgc2FtZSBmaWxlICh3b3JrcyBib3RoIGZvciBnemlwIGFuZCBicm90bGkpOlxuLy8gYCd1c2VgLCBgYW5kb21gLCBhbmQgYHJpY3QnYFxuLy8gUmVmZXJlbmNlcyB0byB0aGUgYnJvdGxpIGRlZmF1bHQgZGljdGlvbmFyeTpcbi8vIGAtMjZUYCwgYDE5ODNgLCBgNDBweGAsIGA3NXB4YCwgYGJ1c2hgLCBgamFja2AsIGBtaW5kYCwgYHZlcnlgLCBhbmQgYHdvbGZgXG5jb25zdCB1cmxBbHBoYWJldCA9XG4gICAgJ3VzZWFuZG9tLTI2VDE5ODM0MFBYNzVweEpBQ0tWRVJZTUlOREJVU0hXT0xGX0dRWmJmZ2hqa2xxdnd5enJpY3QnXG5cbmV4cG9ydCBmdW5jdGlvbiBuYW5vaWQoc2l6ZTogbnVtYmVyID0gMjEpOiBzdHJpbmcge1xuICAgIGxldCBpZCA9ICcnXG4gICAgLy8gQSBjb21wYWN0IGFsdGVybmF0aXZlIGZvciBgZm9yICh2YXIgaSA9IDA7IGkgPCBzdGVwOyBpKyspYC5cbiAgICBsZXQgaSA9IHNpemUgfCAwXG4gICAgd2hpbGUgKGktLSkge1xuICAgICAgICAvLyBgfCAwYCBpcyBtb3JlIGNvbXBhY3QgYW5kIGZhc3RlciB0aGFuIGBNYXRoLmZsb29yKClgLlxuICAgICAgICBpZCArPSB1cmxBbHBoYWJldFsoTWF0aC5yYW5kb20oKSAqIDY0KSB8IDBdXG4gICAgfVxuICAgIHJldHVybiBpZFxufVxuIiwgIi8qXG4gXyAgICAgX18gICAgIF8gX19cbnwgfCAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuYW5vaWQgfSBmcm9tIFwiLi9uYW5vaWQuanNcIjtcblxuY29uc3QgcnVudGltZVVSTCA9IHdpbmRvdy5sb2NhdGlvbi5vcmlnaW4gKyBcIi93YWlscy9ydW50aW1lXCI7XG5cbi8vIFJlLWV4cG9ydCBuYW5vaWQgZm9yIGN1c3RvbSB0cmFuc3BvcnQgaW1wbGVtZW50YXRpb25zXG5leHBvcnQgeyBuYW5vaWQgfTtcblxuLy8gT2JqZWN0IE5hbWVzXG5leHBvcnQgY29uc3Qgb2JqZWN0TmFtZXMgPSBPYmplY3QuZnJlZXplKHtcbiAgICBDYWxsOiAwLFxuICAgIENsaXBib2FyZDogMSxcbiAgICBBcHBsaWNhdGlvbjogMixcbiAgICBFdmVudHM6IDMsXG4gICAgQ29udGV4dE1lbnU6IDQsXG4gICAgRGlhbG9nOiA1LFxuICAgIFdpbmRvdzogNixcbiAgICBTY3JlZW5zOiA3LFxuICAgIFN5c3RlbTogOCxcbiAgICBCcm93c2VyOiA5LFxuICAgIENhbmNlbENhbGw6IDEwLFxuICAgIElPUzogMTEsXG59KTtcbmV4cG9ydCBsZXQgY2xpZW50SWQgPSBuYW5vaWQoKTtcblxuLyoqXG4gKiBSdW50aW1lVHJhbnNwb3J0IGRlZmluZXMgdGhlIGludGVyZmFjZSBmb3IgY3VzdG9tIElQQyB0cmFuc3BvcnQgaW1wbGVtZW50YXRpb25zLlxuICogSW1wbGVtZW50IHRoaXMgaW50ZXJmYWNlIHRvIHVzZSBXZWJTb2NrZXRzLCBjdXN0b20gcHJvdG9jb2xzLCBvciBhbnkgb3RoZXJcbiAqIHRyYW5zcG9ydCBtZWNoYW5pc20gaW5zdGVhZCBvZiB0aGUgZGVmYXVsdCBIVFRQIGZldGNoLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJ1bnRpbWVUcmFuc3BvcnQge1xuICAgIC8qKlxuICAgICAqIFNlbmQgYSBydW50aW1lIGNhbGwgYW5kIHJldHVybiB0aGUgcmVzcG9uc2UuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gb2JqZWN0SUQgLSBUaGUgV2FpbHMgb2JqZWN0IElEICgwPUNhbGwsIDE9Q2xpcGJvYXJkLCBldGMuKVxuICAgICAqIEBwYXJhbSBtZXRob2QgLSBUaGUgbWV0aG9kIElEIHRvIGNhbGxcbiAgICAgKiBAcGFyYW0gd2luZG93TmFtZSAtIE9wdGlvbmFsIHdpbmRvdyBuYW1lXG4gICAgICogQHBhcmFtIGFyZ3MgLSBBcmd1bWVudHMgdG8gcGFzcyAod2lsbCBiZSBKU09OIHN0cmluZ2lmaWVkIGlmIHByZXNlbnQpXG4gICAgICogQHJldHVybnMgUHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIHJlc3BvbnNlIGRhdGFcbiAgICAgKi9cbiAgICBjYWxsKG9iamVjdElEOiBudW1iZXIsIG1ldGhvZDogbnVtYmVyLCB3aW5kb3dOYW1lOiBzdHJpbmcsIGFyZ3M6IGFueSk6IFByb21pc2U8YW55Pjtcbn1cblxuLyoqXG4gKiBDdXN0b20gdHJhbnNwb3J0IGltcGxlbWVudGF0aW9uIChjYW4gYmUgc2V0IGJ5IHVzZXIpXG4gKi9cbmxldCBjdXN0b21UcmFuc3BvcnQ6IFJ1bnRpbWVUcmFuc3BvcnQgfCBudWxsID0gbnVsbDtcblxuLyoqXG4gKiBTZXQgYSBjdXN0b20gdHJhbnNwb3J0IGZvciBhbGwgV2FpbHMgcnVudGltZSBjYWxscy5cbiAqIFRoaXMgYWxsb3dzIHlvdSB0byByZXBsYWNlIHRoZSBkZWZhdWx0IEhUVFAgZmV0Y2ggdHJhbnNwb3J0IHdpdGhcbiAqIFdlYlNvY2tldHMsIGN1c3RvbSBwcm90b2NvbHMsIG9yIGFueSBvdGhlciBtZWNoYW5pc20uXG4gKlxuICogQHBhcmFtIHRyYW5zcG9ydCAtIFlvdXIgY3VzdG9tIHRyYW5zcG9ydCBpbXBsZW1lbnRhdGlvblxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBpbXBvcnQgeyBzZXRUcmFuc3BvcnQgfSBmcm9tICcvd2FpbHMvcnVudGltZS5qcyc7XG4gKlxuICogY29uc3Qgd3NUcmFuc3BvcnQgPSB7XG4gKiAgIGNhbGw6IGFzeW5jIChvYmplY3RJRCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKSA9PiB7XG4gKiAgICAgLy8gWW91ciBXZWJTb2NrZXQgaW1wbGVtZW50YXRpb25cbiAqICAgfVxuICogfTtcbiAqXG4gKiBzZXRUcmFuc3BvcnQod3NUcmFuc3BvcnQpO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZXRUcmFuc3BvcnQodHJhbnNwb3J0OiBSdW50aW1lVHJhbnNwb3J0IHwgbnVsbCk6IHZvaWQge1xuICAgIGN1c3RvbVRyYW5zcG9ydCA9IHRyYW5zcG9ydDtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIGN1cnJlbnQgdHJhbnNwb3J0ICh1c2VmdWwgZm9yIGV4dGVuZGluZy93cmFwcGluZylcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFRyYW5zcG9ydCgpOiBSdW50aW1lVHJhbnNwb3J0IHwgbnVsbCB7XG4gICAgcmV0dXJuIGN1c3RvbVRyYW5zcG9ydDtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgbmV3IHJ1bnRpbWUgY2FsbGVyIHdpdGggc3BlY2lmaWVkIElELlxuICpcbiAqIEBwYXJhbSBvYmplY3QgLSBUaGUgb2JqZWN0IHRvIGludm9rZSB0aGUgbWV0aG9kIG9uLlxuICogQHBhcmFtIHdpbmRvd05hbWUgLSBUaGUgbmFtZSBvZiB0aGUgd2luZG93LlxuICogQHJldHVybiBUaGUgbmV3IHJ1bnRpbWUgY2FsbGVyIGZ1bmN0aW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbmV3UnVudGltZUNhbGxlcihvYmplY3Q6IG51bWJlciwgd2luZG93TmFtZTogc3RyaW5nID0gJycpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKG1ldGhvZDogbnVtYmVyLCBhcmdzOiBhbnkgPSBudWxsKSB7XG4gICAgICAgIHJldHVybiBydW50aW1lQ2FsbFdpdGhJRChvYmplY3QsIG1ldGhvZCwgd2luZG93TmFtZSwgYXJncyk7XG4gICAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcnVudGltZUNhbGxXaXRoSUQob2JqZWN0SUQ6IG51bWJlciwgbWV0aG9kOiBudW1iZXIsIHdpbmRvd05hbWU6IHN0cmluZywgYXJnczogYW55KTogUHJvbWlzZTxhbnk+IHtcbiAgICAvLyBVc2UgY3VzdG9tIHRyYW5zcG9ydCBpZiBhdmFpbGFibGVcbiAgICBpZiAoY3VzdG9tVHJhbnNwb3J0KSB7XG4gICAgICAgIHJldHVybiBjdXN0b21UcmFuc3BvcnQuY2FsbChvYmplY3RJRCwgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcbiAgICB9XG5cbiAgICAvLyBEZWZhdWx0IEhUVFAgZmV0Y2ggdHJhbnNwb3J0XG4gICAgbGV0IHVybCA9IG5ldyBVUkwocnVudGltZVVSTCk7XG5cbiAgICBsZXQgYm9keTogeyBvYmplY3Q6IG51bWJlcjsgbWV0aG9kOiBudW1iZXIsIGFyZ3M/OiBhbnkgfSA9IHtcbiAgICAgIG9iamVjdDogb2JqZWN0SUQsXG4gICAgICBtZXRob2RcbiAgICB9XG4gICAgaWYgKGFyZ3MgIT09IG51bGwgJiYgYXJncyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBib2R5LmFyZ3MgPSBhcmdzO1xuICAgIH1cblxuICAgIGxldCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgICAgICBbXCJ4LXdhaWxzLWNsaWVudC1pZFwiXTogY2xpZW50SWQsXG4gICAgICAgIFtcIkNvbnRlbnQtVHlwZVwiXTogXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgICB9XG4gICAgaWYgKHdpbmRvd05hbWUpIHtcbiAgICAgICAgaGVhZGVyc1tcIngtd2FpbHMtd2luZG93LW5hbWVcIl0gPSB3aW5kb3dOYW1lO1xuICAgIH1cblxuICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCwge1xuICAgICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgICBoZWFkZXJzLFxuICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoYm9keSlcbiAgICB9KTtcbiAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihhd2FpdCByZXNwb25zZS50ZXh0KCkpO1xuICAgIH1cblxuICAgIGlmICgocmVzcG9uc2UuaGVhZGVycy5nZXQoXCJDb250ZW50LVR5cGVcIik/LmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpID8/IC0xKSAhPT0gLTEpIHtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gcmVzcG9uc2UudGV4dCgpO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG4vLyBzZXR1cFxud2luZG93Ll93YWlscyA9IHdpbmRvdy5fd2FpbHMgfHwge307XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkRpYWxvZyk7XG5cbi8vIERlZmluZSBjb25zdGFudHMgZnJvbSB0aGUgYG1ldGhvZHNgIG9iamVjdCBpbiBUaXRsZSBDYXNlXG5jb25zdCBEaWFsb2dJbmZvID0gMDtcbmNvbnN0IERpYWxvZ1dhcm5pbmcgPSAxO1xuY29uc3QgRGlhbG9nRXJyb3IgPSAyO1xuY29uc3QgRGlhbG9nUXVlc3Rpb24gPSAzO1xuY29uc3QgRGlhbG9nT3BlbkZpbGUgPSA0O1xuY29uc3QgRGlhbG9nU2F2ZUZpbGUgPSA1O1xuXG5leHBvcnQgaW50ZXJmYWNlIE9wZW5GaWxlRGlhbG9nT3B0aW9ucyB7XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY2hvc2VuLiAqL1xuICAgIENhbkNob29zZURpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGZpbGVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZGlyZWN0b3JpZXMgY2FuIGJlIGNyZWF0ZWQuICovXG4gICAgQ2FuQ3JlYXRlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGZpbGVzIHNob3VsZCBiZSBzaG93bi4gKi9cbiAgICBTaG93SGlkZGVuRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgYWxpYXNlcyBzaG91bGQgYmUgcmVzb2x2ZWQuICovXG4gICAgUmVzb2x2ZXNBbGlhc2VzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG11bHRpcGxlIHNlbGVjdGlvbiBpcyBhbGxvd2VkLiAqL1xuICAgIEFsbG93c011bHRpcGxlU2VsZWN0aW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBleHRlbnNpb24gc2hvdWxkIGJlIGhpZGRlbi4gKi9cbiAgICBIaWRlRXh0ZW5zaW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGhpZGRlbiBleHRlbnNpb25zIGNhbiBiZSBzZWxlY3RlZC4gKi9cbiAgICBDYW5TZWxlY3RIaWRkZW5FeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZSBwYWNrYWdlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBkaXJlY3Rvcmllcy4gKi9cbiAgICBUcmVhdHNGaWxlUGFja2FnZXNBc0RpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG90aGVyIGZpbGUgdHlwZXMgYXJlIGFsbG93ZWQuICovXG4gICAgQWxsb3dzT3RoZXJGaWxldHlwZXM/OiBib29sZWFuO1xuICAgIC8qKiBBcnJheSBvZiBmaWxlIGZpbHRlcnMuICovXG4gICAgRmlsdGVycz86IEZpbGVGaWx0ZXJbXTtcbiAgICAvKiogVGl0bGUgb2YgdGhlIGRpYWxvZy4gKi9cbiAgICBUaXRsZT86IHN0cmluZztcbiAgICAvKiogTWVzc2FnZSB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgTWVzc2FnZT86IHN0cmluZztcbiAgICAvKiogVGV4dCB0byBkaXNwbGF5IG9uIHRoZSBidXR0b24uICovXG4gICAgQnV0dG9uVGV4dD86IHN0cmluZztcbiAgICAvKiogRGlyZWN0b3J5IHRvIG9wZW4gaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBEaXJlY3Rvcnk/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiB0aGUgZGlhbG9nIHNob3VsZCBhcHBlYXIgZGV0YWNoZWQgZnJvbSB0aGUgbWFpbiB3aW5kb3cuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFNhdmVGaWxlRGlhbG9nT3B0aW9ucyB7XG4gICAgLyoqIERlZmF1bHQgZmlsZW5hbWUgdG8gdXNlIGluIHRoZSBkaWFsb2cuICovXG4gICAgRmlsZW5hbWU/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY2hvc2VuLiAqL1xuICAgIENhbkNob29zZURpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGZpbGVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZGlyZWN0b3JpZXMgY2FuIGJlIGNyZWF0ZWQuICovXG4gICAgQ2FuQ3JlYXRlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGZpbGVzIHNob3VsZCBiZSBzaG93bi4gKi9cbiAgICBTaG93SGlkZGVuRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgYWxpYXNlcyBzaG91bGQgYmUgcmVzb2x2ZWQuICovXG4gICAgUmVzb2x2ZXNBbGlhc2VzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBleHRlbnNpb24gc2hvdWxkIGJlIGhpZGRlbi4gKi9cbiAgICBIaWRlRXh0ZW5zaW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGhpZGRlbiBleHRlbnNpb25zIGNhbiBiZSBzZWxlY3RlZC4gKi9cbiAgICBDYW5TZWxlY3RIaWRkZW5FeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZSBwYWNrYWdlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBkaXJlY3Rvcmllcy4gKi9cbiAgICBUcmVhdHNGaWxlUGFja2FnZXNBc0RpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG90aGVyIGZpbGUgdHlwZXMgYXJlIGFsbG93ZWQuICovXG4gICAgQWxsb3dzT3RoZXJGaWxldHlwZXM/OiBib29sZWFuO1xuICAgIC8qKiBBcnJheSBvZiBmaWxlIGZpbHRlcnMuICovXG4gICAgRmlsdGVycz86IEZpbGVGaWx0ZXJbXTtcbiAgICAvKiogVGl0bGUgb2YgdGhlIGRpYWxvZy4gKi9cbiAgICBUaXRsZT86IHN0cmluZztcbiAgICAvKiogTWVzc2FnZSB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgTWVzc2FnZT86IHN0cmluZztcbiAgICAvKiogVGV4dCB0byBkaXNwbGF5IG9uIHRoZSBidXR0b24uICovXG4gICAgQnV0dG9uVGV4dD86IHN0cmluZztcbiAgICAvKiogRGlyZWN0b3J5IHRvIG9wZW4gaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBEaXJlY3Rvcnk/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiB0aGUgZGlhbG9nIHNob3VsZCBhcHBlYXIgZGV0YWNoZWQgZnJvbSB0aGUgbWFpbiB3aW5kb3cuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE1lc3NhZ2VEaWFsb2dPcHRpb25zIHtcbiAgICAvKiogVGhlIHRpdGxlIG9mIHRoZSBkaWFsb2cgd2luZG93LiAqL1xuICAgIFRpdGxlPzogc3RyaW5nO1xuICAgIC8qKiBUaGUgbWFpbiBtZXNzYWdlIHRvIHNob3cgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBNZXNzYWdlPzogc3RyaW5nO1xuICAgIC8qKiBBcnJheSBvZiBidXR0b24gb3B0aW9ucyB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgQnV0dG9ucz86IEJ1dHRvbltdO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBkaWFsb2cgc2hvdWxkIGFwcGVhciBkZXRhY2hlZCBmcm9tIHRoZSBtYWluIHdpbmRvdyAoaWYgYXBwbGljYWJsZSkuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEJ1dHRvbiB7XG4gICAgLyoqIFRleHQgdGhhdCBhcHBlYXJzIHdpdGhpbiB0aGUgYnV0dG9uLiAqL1xuICAgIExhYmVsPzogc3RyaW5nO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBidXR0b24gc2hvdWxkIGNhbmNlbCBhbiBvcGVyYXRpb24gd2hlbiBjbGlja2VkLiAqL1xuICAgIElzQ2FuY2VsPzogYm9vbGVhbjtcbiAgICAvKiogVHJ1ZSBpZiB0aGUgYnV0dG9uIHNob3VsZCBiZSB0aGUgZGVmYXVsdCBhY3Rpb24gd2hlbiB0aGUgdXNlciBwcmVzc2VzIGVudGVyLiAqL1xuICAgIElzRGVmYXVsdD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmlsZUZpbHRlciB7XG4gICAgLyoqIERpc3BsYXkgbmFtZSBmb3IgdGhlIGZpbHRlciwgaXQgY291bGQgYmUgXCJUZXh0IEZpbGVzXCIsIFwiSW1hZ2VzXCIgZXRjLiAqL1xuICAgIERpc3BsYXlOYW1lPzogc3RyaW5nO1xuICAgIC8qKiBQYXR0ZXJuIHRvIG1hdGNoIGZvciB0aGUgZmlsdGVyLCBlLmcuIFwiKi50eHQ7Ki5tZFwiIGZvciB0ZXh0IG1hcmtkb3duIGZpbGVzLiAqL1xuICAgIFBhdHRlcm4/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogUHJlc2VudHMgYSBkaWFsb2cgb2Ygc3BlY2lmaWVkIHR5cGUgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cbiAqXG4gKiBAcGFyYW0gdHlwZSAtIERpYWxvZyB0eXBlLlxuICogQHBhcmFtIG9wdGlvbnMgLSBPcHRpb25zIGZvciB0aGUgZGlhbG9nLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCByZXN1bHQgb2YgZGlhbG9nLlxuICovXG5mdW5jdGlvbiBkaWFsb2codHlwZTogbnVtYmVyLCBvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyB8IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyB8IFNhdmVGaWxlRGlhbG9nT3B0aW9ucyA9IHt9KTogUHJvbWlzZTxhbnk+IHtcbiAgICByZXR1cm4gY2FsbCh0eXBlLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBQcmVzZW50cyBhbiBpbmZvIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBsYWJlbCBvZiB0aGUgY2hvc2VuIGJ1dHRvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEluZm8ob3B0aW9uczogTWVzc2FnZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ0luZm8sIG9wdGlvbnMpOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSB3YXJuaW5nIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgbGFiZWwgb2YgdGhlIGNob3NlbiBidXR0b24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXYXJuaW5nKG9wdGlvbnM6IE1lc3NhZ2VEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dXYXJuaW5nLCBvcHRpb25zKTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGFuIGVycm9yIGRpYWxvZy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgbGFiZWwgb2YgdGhlIGNob3NlbiBidXR0b24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFcnJvcihvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nRXJyb3IsIG9wdGlvbnMpOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSBxdWVzdGlvbiBkaWFsb2cuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9ucy5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGxhYmVsIG9mIHRoZSBjaG9zZW4gYnV0dG9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gUXVlc3Rpb24ob3B0aW9uczogTWVzc2FnZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ1F1ZXN0aW9uLCBvcHRpb25zKTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGEgZmlsZSBzZWxlY3Rpb24gZGlhbG9nIHRvIHBpY2sgb25lIG9yIG1vcmUgZmlsZXMgdG8gb3Blbi5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgU2VsZWN0ZWQgZmlsZSBvciBsaXN0IG9mIGZpbGVzLCBvciBhIGJsYW5rIHN0cmluZy9lbXB0eSBsaXN0IGlmIG5vIGZpbGUgaGFzIGJlZW4gc2VsZWN0ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMgJiB7IEFsbG93c011bHRpcGxlU2VsZWN0aW9uOiB0cnVlIH0pOiBQcm9taXNlPHN0cmluZ1tdPjtcbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMgJiB7IEFsbG93c011bHRpcGxlU2VsZWN0aW9uPzogZmFsc2UgfCB1bmRlZmluZWQgfSk6IFByb21pc2U8c3RyaW5nPjtcbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZyB8IHN0cmluZ1tdPjtcbmV4cG9ydCBmdW5jdGlvbiBPcGVuRmlsZShvcHRpb25zOiBPcGVuRmlsZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZyB8IHN0cmluZ1tdPiB7IHJldHVybiBkaWFsb2coRGlhbG9nT3BlbkZpbGUsIG9wdGlvbnMpID8/IFtdOyB9XG5cbi8qKlxuICogUHJlc2VudHMgYSBmaWxlIHNlbGVjdGlvbiBkaWFsb2cgdG8gcGljayBhIGZpbGUgdG8gc2F2ZS5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIERpYWxvZyBvcHRpb25zLlxuICogQHJldHVybnMgU2VsZWN0ZWQgZmlsZSwgb3IgYSBibGFuayBzdHJpbmcgaWYgbm8gZmlsZSBoYXMgYmVlbiBzZWxlY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNhdmVGaWxlKG9wdGlvbnM6IFNhdmVGaWxlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nU2F2ZUZpbGUsIG9wdGlvbnMpOyB9XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgZXZlbnRMaXN0ZW5lcnMsIExpc3RlbmVyLCBsaXN0ZW5lck9mZiB9IGZyb20gXCIuL2xpc3RlbmVyLmpzXCI7XG5pbXBvcnQgeyBFdmVudHMgYXMgQ3JlYXRlIH0gZnJvbSBcIi4vY3JlYXRlLmpzXCI7XG5pbXBvcnQgeyBUeXBlcyB9IGZyb20gXCIuL2V2ZW50X3R5cGVzLmpzXCI7XG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcbndpbmRvdy5fd2FpbHMuZGlzcGF0Y2hXYWlsc0V2ZW50ID0gZGlzcGF0Y2hXYWlsc0V2ZW50O1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5FdmVudHMpO1xuY29uc3QgRW1pdE1ldGhvZCA9IDA7XG5cbmV4cG9ydCAqIGZyb20gXCIuL2V2ZW50X3R5cGVzLmpzXCI7XG5cbi8qKlxuICogQSB0YWJsZSBvZiBkYXRhIHR5cGVzIGZvciBhbGwga25vd24gZXZlbnRzLlxuICogV2lsbCBiZSBtb25rZXktcGF0Y2hlZCBieSB0aGUgYmluZGluZyBnZW5lcmF0b3IuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ3VzdG9tRXZlbnRzIHt9XG5cbi8qKlxuICogRWl0aGVyIGEga25vd24gZXZlbnQgbmFtZSBvciBhbiBhcmJpdHJhcnkgc3RyaW5nLlxuICovXG5leHBvcnQgdHlwZSBXYWlsc0V2ZW50TmFtZTxFIGV4dGVuZHMga2V5b2YgQ3VzdG9tRXZlbnRzID0ga2V5b2YgQ3VzdG9tRXZlbnRzPiA9IEUgfCAoc3RyaW5nICYge30pO1xuXG4vKipcbiAqIFVuaW9uIG9mIGFsbCBrbm93biBzeXN0ZW0gZXZlbnQgbmFtZXMuXG4gKi9cbnR5cGUgU3lzdGVtRXZlbnROYW1lID0ge1xuICAgIFtLIGluIGtleW9mICh0eXBlb2YgVHlwZXMpXTogKHR5cGVvZiBUeXBlcylbS11ba2V5b2YgKCh0eXBlb2YgVHlwZXMpW0tdKV1cbn0gZXh0ZW5kcyAoaW5mZXIgTSkgPyBNW2tleW9mIE1dIDogbmV2ZXI7XG5cbi8qKlxuICogVGhlIGRhdGEgdHlwZSBhc3NvY2lhdGVkIHRvIGEgZ2l2ZW4gZXZlbnQuXG4gKi9cbmV4cG9ydCB0eXBlIFdhaWxzRXZlbnREYXRhPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPiA9XG4gICAgRSBleHRlbmRzIGtleW9mIEN1c3RvbUV2ZW50cyA/IEN1c3RvbUV2ZW50c1tFXSA6IChFIGV4dGVuZHMgU3lzdGVtRXZlbnROYW1lID8gdm9pZCA6IGFueSk7XG5cbi8qKlxuICogVGhlIHR5cGUgb2YgaGFuZGxlcnMgZm9yIGEgZ2l2ZW4gZXZlbnQuXG4gKi9cbmV4cG9ydCB0eXBlIFdhaWxzRXZlbnRDYWxsYmFjazxFIGV4dGVuZHMgV2FpbHNFdmVudE5hbWUgPSBXYWlsc0V2ZW50TmFtZT4gPSAoZXY6IFdhaWxzRXZlbnQ8RT4pID0+IHZvaWQ7XG5cbi8qKlxuICogUmVwcmVzZW50cyBhIHN5c3RlbSBldmVudCBvciBhIGN1c3RvbSBldmVudCBlbWl0dGVkIHRocm91Z2ggd2FpbHMtcHJvdmlkZWQgZmFjaWxpdGllcy5cbiAqL1xuZXhwb3J0IGNsYXNzIFdhaWxzRXZlbnQ8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+IHtcbiAgICAvKipcbiAgICAgKiBUaGUgbmFtZSBvZiB0aGUgZXZlbnQuXG4gICAgICovXG4gICAgbmFtZTogRTtcblxuICAgIC8qKlxuICAgICAqIE9wdGlvbmFsIGRhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBlbWl0dGVkIGV2ZW50LlxuICAgICAqL1xuICAgIGRhdGE6IFdhaWxzRXZlbnREYXRhPEU+O1xuXG4gICAgLyoqXG4gICAgICogTmFtZSBvZiB0aGUgb3JpZ2luYXRpbmcgd2luZG93LiBPbWl0dGVkIGZvciBhcHBsaWNhdGlvbiBldmVudHMuXG4gICAgICogV2lsbCBiZSBvdmVycmlkZGVuIGlmIHNldCBtYW51YWxseS5cbiAgICAgKi9cbiAgICBzZW5kZXI/OiBzdHJpbmc7XG5cbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBFLCBkYXRhOiBXYWlsc0V2ZW50RGF0YTxFPik7XG4gICAgY29uc3RydWN0b3IobmFtZTogV2FpbHNFdmVudERhdGE8RT4gZXh0ZW5kcyBudWxsIHwgdm9pZCA/IEUgOiBuZXZlcilcbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBFLCBkYXRhPzogYW55KSB7XG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XG4gICAgICAgIHRoaXMuZGF0YSA9IGRhdGEgPz8gbnVsbDtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGRpc3BhdGNoV2FpbHNFdmVudChldmVudDogYW55KSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudC5uYW1lKTtcbiAgICBpZiAoIWxpc3RlbmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGV0IHdhaWxzRXZlbnQgPSBuZXcgV2FpbHNFdmVudChcbiAgICAgICAgZXZlbnQubmFtZSxcbiAgICAgICAgKGV2ZW50Lm5hbWUgaW4gQ3JlYXRlKSA/IENyZWF0ZVtldmVudC5uYW1lXShldmVudC5kYXRhKSA6IGV2ZW50LmRhdGFcbiAgICApO1xuICAgIGlmICgnc2VuZGVyJyBpbiBldmVudCkge1xuICAgICAgICB3YWlsc0V2ZW50LnNlbmRlciA9IGV2ZW50LnNlbmRlcjtcbiAgICB9XG5cbiAgICBsaXN0ZW5lcnMgPSBsaXN0ZW5lcnMuZmlsdGVyKGxpc3RlbmVyID0+ICFsaXN0ZW5lci5kaXNwYXRjaCh3YWlsc0V2ZW50KSk7XG4gICAgaWYgKGxpc3RlbmVycy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgZXZlbnRMaXN0ZW5lcnMuZGVsZXRlKGV2ZW50Lm5hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudC5uYW1lLCBsaXN0ZW5lcnMpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZWdpc3RlciBhIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBtdWx0aXBsZSB0aW1lcyBmb3IgYSBzcGVjaWZpYyBldmVudC5cbiAqXG4gKiBAcGFyYW0gZXZlbnROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIHJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmb3IuXG4gKiBAcGFyYW0gY2FsbGJhY2sgLSBUaGUgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIHdoZW4gdGhlIGV2ZW50IGlzIHRyaWdnZXJlZC5cbiAqIEBwYXJhbSBtYXhDYWxsYmFja3MgLSBUaGUgbWF4aW11bSBudW1iZXIgb2YgdGltZXMgdGhlIGNhbGxiYWNrIGNhbiBiZSBjYWxsZWQgZm9yIHRoZSBldmVudC4gT25jZSB0aGUgbWF4aW11bSBudW1iZXIgaXMgcmVhY2hlZCwgdGhlIGNhbGxiYWNrIHdpbGwgbm8gbG9uZ2VyIGJlIGNhbGxlZC5cbiAqIEByZXR1cm5zIEEgZnVuY3Rpb24gdGhhdCwgd2hlbiBjYWxsZWQsIHdpbGwgdW5yZWdpc3RlciB0aGUgY2FsbGJhY2sgZnJvbSB0aGUgZXZlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPbk11bHRpcGxlPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihldmVudE5hbWU6IEUsIGNhbGxiYWNrOiBXYWlsc0V2ZW50Q2FsbGJhY2s8RT4sIG1heENhbGxiYWNrczogbnVtYmVyKSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpIHx8IFtdO1xuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xuICAgIGxpc3RlbmVycy5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50TmFtZSwgbGlzdGVuZXJzKTtcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBleGVjdXRlZCB3aGVuIHRoZSBzcGVjaWZpZWQgZXZlbnQgb2NjdXJzLlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgZXZlbnQgdG8gcmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZvci5cbiAqIEBwYXJhbSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBjYWxsZWQgd2hlbiB0aGUgZXZlbnQgaXMgdHJpZ2dlcmVkLlxuICogQHJldHVybnMgQSBmdW5jdGlvbiB0aGF0LCB3aGVuIGNhbGxlZCwgd2lsbCB1bnJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmcm9tIHRoZSBldmVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uPEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihldmVudE5hbWU6IEUsIGNhbGxiYWNrOiBXYWlsc0V2ZW50Q2FsbGJhY2s8RT4pOiAoKSA9PiB2b2lkIHtcbiAgICByZXR1cm4gT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAtMSk7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGEgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgZXhlY3V0ZWQgb25seSBvbmNlIGZvciB0aGUgc3BlY2lmaWVkIGV2ZW50LlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgZXZlbnQgdG8gcmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZvci5cbiAqIEBwYXJhbSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBjYWxsZWQgd2hlbiB0aGUgZXZlbnQgaXMgdHJpZ2dlcmVkLlxuICogQHJldHVybnMgQSBmdW5jdGlvbiB0aGF0LCB3aGVuIGNhbGxlZCwgd2lsbCB1bnJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmcm9tIHRoZSBldmVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uY2U8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KGV2ZW50TmFtZTogRSwgY2FsbGJhY2s6IFdhaWxzRXZlbnRDYWxsYmFjazxFPik6ICgpID0+IHZvaWQge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG4vKipcbiAqIFJlbW92ZXMgZXZlbnQgbGlzdGVuZXJzIGZvciB0aGUgc3BlY2lmaWVkIGV2ZW50IG5hbWVzLlxuICpcbiAqIEBwYXJhbSBldmVudE5hbWVzIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50cyB0byByZW1vdmUgbGlzdGVuZXJzIGZvci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9mZiguLi5ldmVudE5hbWVzOiBbV2FpbHNFdmVudE5hbWUsIC4uLldhaWxzRXZlbnROYW1lW11dKTogdm9pZCB7XG4gICAgZXZlbnROYW1lcy5mb3JFYWNoKGV2ZW50TmFtZSA9PiBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKSk7XG59XG5cbi8qKlxuICogUmVtb3ZlcyBhbGwgZXZlbnQgbGlzdGVuZXJzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmQWxsKCk6IHZvaWQge1xuICAgIGV2ZW50TGlzdGVuZXJzLmNsZWFyKCk7XG59XG5cbi8qKlxuICogRW1pdHMgYW4gZXZlbnQuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgd2lsbCBiZSBmdWxmaWxsZWQgb25jZSB0aGUgZXZlbnQgaGFzIGJlZW4gZW1pdHRlZC4gIFJlc29sdmVzIHRvIHRydWUgaWYgdGhlIGV2ZW50IHdhcyBjYW5jZWxsZWQuXG4gKiBAcGFyYW0gbmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byBlbWl0XG4gKiBAcGFyYW0gZGF0YSAtIFRoZSBkYXRhIHRoYXQgd2lsbCBiZSBzZW50IHdpdGggdGhlIGV2ZW50XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbWl0PEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihuYW1lOiBFLCBkYXRhOiBXYWlsc0V2ZW50RGF0YTxFPik6IFByb21pc2U8Ym9vbGVhbj5cbmV4cG9ydCBmdW5jdGlvbiBFbWl0PEUgZXh0ZW5kcyBXYWlsc0V2ZW50TmFtZSA9IFdhaWxzRXZlbnROYW1lPihuYW1lOiBXYWlsc0V2ZW50RGF0YTxFPiBleHRlbmRzIG51bGwgfCB2b2lkID8gRSA6IG5ldmVyKTogUHJvbWlzZTxib29sZWFuPlxuZXhwb3J0IGZ1bmN0aW9uIEVtaXQ8RSBleHRlbmRzIFdhaWxzRXZlbnROYW1lID0gV2FpbHNFdmVudE5hbWU+KG5hbWU6IFdhaWxzRXZlbnREYXRhPEU+LCBkYXRhPzogYW55KTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgcmV0dXJuIGNhbGwoRW1pdE1ldGhvZCwgIG5ldyBXYWlsc0V2ZW50KG5hbWUsIGRhdGEpKVxufVxuXG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIFRoZSBmb2xsb3dpbmcgdXRpbGl0aWVzIGhhdmUgYmVlbiBmYWN0b3JlZCBvdXQgb2YgLi9ldmVudHMudHNcbi8vIGZvciB0ZXN0aW5nIHB1cnBvc2VzLlxuXG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnMgPSBuZXcgTWFwPHN0cmluZywgTGlzdGVuZXJbXT4oKTtcblxuZXhwb3J0IGNsYXNzIExpc3RlbmVyIHtcbiAgICBldmVudE5hbWU6IHN0cmluZztcbiAgICBjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gdm9pZDtcbiAgICBtYXhDYWxsYmFja3M6IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZTogc3RyaW5nLCBjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gdm9pZCwgbWF4Q2FsbGJhY2tzOiBudW1iZXIpIHtcbiAgICAgICAgdGhpcy5ldmVudE5hbWUgPSBldmVudE5hbWU7XG4gICAgICAgIHRoaXMuY2FsbGJhY2sgPSBjYWxsYmFjaztcbiAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgPSBtYXhDYWxsYmFja3MgfHwgLTE7XG4gICAgfVxuXG4gICAgZGlzcGF0Y2goZGF0YTogYW55KTogYm9vbGVhbiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLmNhbGxiYWNrKGRhdGEpO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHJldHVybiBmYWxzZTtcbiAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgcmV0dXJuIHRoaXMubWF4Q2FsbGJhY2tzID09PSAwO1xuICAgIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpc3RlbmVyT2ZmKGxpc3RlbmVyOiBMaXN0ZW5lcik6IHZvaWQge1xuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQobGlzdGVuZXIuZXZlbnROYW1lKTtcbiAgICBpZiAoIWxpc3RlbmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcbiAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUobGlzdGVuZXIuZXZlbnROYW1lKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBldmVudExpc3RlbmVycy5zZXQobGlzdGVuZXIuZXZlbnROYW1lLCBsaXN0ZW5lcnMpO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoqXG4gKiBBbnkgaXMgYSBkdW1teSBjcmVhdGlvbiBmdW5jdGlvbiBmb3Igc2ltcGxlIG9yIHVua25vd24gdHlwZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBBbnk8VCA9IGFueT4oc291cmNlOiBhbnkpOiBUIHtcbiAgICByZXR1cm4gc291cmNlO1xufVxuXG4vKipcbiAqIEJ5dGVTbGljZSBpcyBhIGNyZWF0aW9uIGZ1bmN0aW9uIHRoYXQgcmVwbGFjZXNcbiAqIG51bGwgc3RyaW5ncyB3aXRoIGVtcHR5IHN0cmluZ3MuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCeXRlU2xpY2Uoc291cmNlOiBhbnkpOiBzdHJpbmcge1xuICAgIHJldHVybiAoKHNvdXJjZSA9PSBudWxsKSA/IFwiXCIgOiBzb3VyY2UpO1xufVxuXG4vKipcbiAqIEFycmF5IHRha2VzIGEgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIGFyYml0cmFyeSB0eXBlXG4gKiBhbmQgcmV0dXJucyBhbiBpbi1wbGFjZSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYW4gYXJyYXlcbiAqIHdob3NlIGVsZW1lbnRzIGFyZSBvZiB0aGF0IHR5cGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBBcnJheTxUID0gYW55PihlbGVtZW50OiAoc291cmNlOiBhbnkpID0+IFQpOiAoc291cmNlOiBhbnkpID0+IFRbXSB7XG4gICAgaWYgKGVsZW1lbnQgPT09IEFueSkge1xuICAgICAgICByZXR1cm4gKHNvdXJjZSkgPT4gKHNvdXJjZSA9PT0gbnVsbCA/IFtdIDogc291cmNlKTtcbiAgICB9XG5cbiAgICByZXR1cm4gKHNvdXJjZSkgPT4ge1xuICAgICAgICBpZiAoc291cmNlID09PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIH1cbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzb3VyY2UubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHNvdXJjZVtpXSA9IGVsZW1lbnQoc291cmNlW2ldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc291cmNlO1xuICAgIH07XG59XG5cbi8qKlxuICogTWFwIHRha2VzIGNyZWF0aW9uIGZ1bmN0aW9ucyBmb3IgdHdvIGFyYml0cmFyeSB0eXBlc1xuICogYW5kIHJldHVybnMgYW4gaW4tcGxhY2UgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIG9iamVjdFxuICogd2hvc2Uga2V5cyBhbmQgdmFsdWVzIGFyZSBvZiB0aG9zZSB0eXBlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE1hcDxWID0gYW55PihrZXk6IChzb3VyY2U6IGFueSkgPT4gc3RyaW5nLCB2YWx1ZTogKHNvdXJjZTogYW55KSA9PiBWKTogKHNvdXJjZTogYW55KSA9PiBSZWNvcmQ8c3RyaW5nLCBWPiB7XG4gICAgaWYgKHZhbHVlID09PSBBbnkpIHtcbiAgICAgICAgcmV0dXJuIChzb3VyY2UpID0+IChzb3VyY2UgPT09IG51bGwgPyB7fSA6IHNvdXJjZSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChzb3VyY2UpID0+IHtcbiAgICAgICAgaWYgKHNvdXJjZSA9PT0gbnVsbCkge1xuICAgICAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgICB9XG4gICAgICAgIGZvciAoY29uc3Qga2V5IGluIHNvdXJjZSkge1xuICAgICAgICAgICAgc291cmNlW2tleV0gPSB2YWx1ZShzb3VyY2Vba2V5XSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHNvdXJjZTtcbiAgICB9O1xufVxuXG4vKipcbiAqIE51bGxhYmxlIHRha2VzIGEgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIGFyYml0cmFyeSB0eXBlXG4gKiBhbmQgcmV0dXJucyBhIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhIG51bGxhYmxlIHZhbHVlIG9mIHRoYXQgdHlwZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE51bGxhYmxlPFQgPSBhbnk+KGVsZW1lbnQ6IChzb3VyY2U6IGFueSkgPT4gVCk6IChzb3VyY2U6IGFueSkgPT4gKFQgfCBudWxsKSB7XG4gICAgaWYgKGVsZW1lbnQgPT09IEFueSkge1xuICAgICAgICByZXR1cm4gQW55O1xuICAgIH1cblxuICAgIHJldHVybiAoc291cmNlKSA9PiAoc291cmNlID09PSBudWxsID8gbnVsbCA6IGVsZW1lbnQoc291cmNlKSk7XG59XG5cbi8qKlxuICogU3RydWN0IHRha2VzIGFuIG9iamVjdCBtYXBwaW5nIGZpZWxkIG5hbWVzIHRvIGNyZWF0aW9uIGZ1bmN0aW9uc1xuICogYW5kIHJldHVybnMgYW4gaW4tcGxhY2UgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGEgc3RydWN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gU3RydWN0KGNyZWF0ZUZpZWxkOiBSZWNvcmQ8c3RyaW5nLCAoc291cmNlOiBhbnkpID0+IGFueT4pOlxuICAgIDxVIGV4dGVuZHMgUmVjb3JkPHN0cmluZywgYW55PiA9IGFueT4oc291cmNlOiBhbnkpID0+IFVcbntcbiAgICBsZXQgYWxsQW55ID0gdHJ1ZTtcbiAgICBmb3IgKGNvbnN0IG5hbWUgaW4gY3JlYXRlRmllbGQpIHtcbiAgICAgICAgaWYgKGNyZWF0ZUZpZWxkW25hbWVdICE9PSBBbnkpIHtcbiAgICAgICAgICAgIGFsbEFueSA9IGZhbHNlO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKGFsbEFueSkge1xuICAgICAgICByZXR1cm4gQW55O1xuICAgIH1cblxuICAgIHJldHVybiAoc291cmNlKSA9PiB7XG4gICAgICAgIGZvciAoY29uc3QgbmFtZSBpbiBjcmVhdGVGaWVsZCkge1xuICAgICAgICAgICAgaWYgKG5hbWUgaW4gc291cmNlKSB7XG4gICAgICAgICAgICAgICAgc291cmNlW25hbWVdID0gY3JlYXRlRmllbGRbbmFtZV0oc291cmNlW25hbWVdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc291cmNlO1xuICAgIH07XG59XG5cbi8qKlxuICogTWFwcyBrbm93biBldmVudCBuYW1lcyB0byBjcmVhdGlvbiBmdW5jdGlvbnMgZm9yIHRoZWlyIGRhdGEgdHlwZXMuXG4gKiBXaWxsIGJlIG1vbmtleS1wYXRjaGVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqL1xuZXhwb3J0IGNvbnN0IEV2ZW50czogUmVjb3JkPHN0cmluZywgKHNvdXJjZTogYW55KSA9PiBhbnk+ID0ge307XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIEN5bmh5cmNod3lkIHkgZmZlaWwgaG9uIHluIGF3dG9tYXRpZy4gUEVJRElXQ0ggXHUwMEMyIE1PRElXTFxuLy8gVGhpcyBmaWxlIGlzIGF1dG9tYXRpY2FsbHkgZ2VuZXJhdGVkLiBETyBOT1QgRURJVFxuXG5leHBvcnQgY29uc3QgVHlwZXMgPSBPYmplY3QuZnJlZXplKHtcblx0V2luZG93czogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QVBNUG93ZXJTZXR0aW5nQ2hhbmdlOiBcIndpbmRvd3M6QVBNUG93ZXJTZXR0aW5nQ2hhbmdlXCIsXG5cdFx0QVBNUG93ZXJTdGF0dXNDaGFuZ2U6IFwid2luZG93czpBUE1Qb3dlclN0YXR1c0NoYW5nZVwiLFxuXHRcdEFQTVJlc3VtZUF1dG9tYXRpYzogXCJ3aW5kb3dzOkFQTVJlc3VtZUF1dG9tYXRpY1wiLFxuXHRcdEFQTVJlc3VtZVN1c3BlbmQ6IFwid2luZG93czpBUE1SZXN1bWVTdXNwZW5kXCIsXG5cdFx0QVBNU3VzcGVuZDogXCJ3aW5kb3dzOkFQTVN1c3BlbmRcIixcblx0XHRBcHBsaWNhdGlvblN0YXJ0ZWQ6IFwid2luZG93czpBcHBsaWNhdGlvblN0YXJ0ZWRcIixcblx0XHRTeXN0ZW1UaGVtZUNoYW5nZWQ6IFwid2luZG93czpTeXN0ZW1UaGVtZUNoYW5nZWRcIixcblx0XHRXZWJWaWV3TmF2aWdhdGlvbkNvbXBsZXRlZDogXCJ3aW5kb3dzOldlYlZpZXdOYXZpZ2F0aW9uQ29tcGxldGVkXCIsXG5cdFx0V2luZG93QWN0aXZlOiBcIndpbmRvd3M6V2luZG93QWN0aXZlXCIsXG5cdFx0V2luZG93QmFja2dyb3VuZEVyYXNlOiBcIndpbmRvd3M6V2luZG93QmFja2dyb3VuZEVyYXNlXCIsXG5cdFx0V2luZG93Q2xpY2tBY3RpdmU6IFwid2luZG93czpXaW5kb3dDbGlja0FjdGl2ZVwiLFxuXHRcdFdpbmRvd0Nsb3Npbmc6IFwid2luZG93czpXaW5kb3dDbG9zaW5nXCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJ3aW5kb3dzOldpbmRvd0RpZE1vdmVcIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwid2luZG93czpXaW5kb3dEaWRSZXNpemVcIixcblx0XHRXaW5kb3dEUElDaGFuZ2VkOiBcIndpbmRvd3M6V2luZG93RFBJQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd0RyYWdEcm9wOiBcIndpbmRvd3M6V2luZG93RHJhZ0Ryb3BcIixcblx0XHRXaW5kb3dEcmFnRW50ZXI6IFwid2luZG93czpXaW5kb3dEcmFnRW50ZXJcIixcblx0XHRXaW5kb3dEcmFnTGVhdmU6IFwid2luZG93czpXaW5kb3dEcmFnTGVhdmVcIixcblx0XHRXaW5kb3dEcmFnT3ZlcjogXCJ3aW5kb3dzOldpbmRvd0RyYWdPdmVyXCIsXG5cdFx0V2luZG93RW5kTW92ZTogXCJ3aW5kb3dzOldpbmRvd0VuZE1vdmVcIixcblx0XHRXaW5kb3dFbmRSZXNpemU6IFwid2luZG93czpXaW5kb3dFbmRSZXNpemVcIixcblx0XHRXaW5kb3dGdWxsc2NyZWVuOiBcIndpbmRvd3M6V2luZG93RnVsbHNjcmVlblwiLFxuXHRcdFdpbmRvd0hpZGU6IFwid2luZG93czpXaW5kb3dIaWRlXCIsXG5cdFx0V2luZG93SW5hY3RpdmU6IFwid2luZG93czpXaW5kb3dJbmFjdGl2ZVwiLFxuXHRcdFdpbmRvd0tleURvd246IFwid2luZG93czpXaW5kb3dLZXlEb3duXCIsXG5cdFx0V2luZG93S2V5VXA6IFwid2luZG93czpXaW5kb3dLZXlVcFwiLFxuXHRcdFdpbmRvd0tpbGxGb2N1czogXCJ3aW5kb3dzOldpbmRvd0tpbGxGb2N1c1wiLFxuXHRcdFdpbmRvd05vbkNsaWVudEhpdDogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudEhpdFwiLFxuXHRcdFdpbmRvd05vbkNsaWVudE1vdXNlRG93bjogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudE1vdXNlRG93blwiLFxuXHRcdFdpbmRvd05vbkNsaWVudE1vdXNlTGVhdmU6IFwid2luZG93czpXaW5kb3dOb25DbGllbnRNb3VzZUxlYXZlXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VNb3ZlOiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50TW91c2VNb3ZlXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VVcDogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudE1vdXNlVXBcIixcblx0XHRXaW5kb3dQYWludDogXCJ3aW5kb3dzOldpbmRvd1BhaW50XCIsXG5cdFx0V2luZG93UmVzdG9yZTogXCJ3aW5kb3dzOldpbmRvd1Jlc3RvcmVcIixcblx0XHRXaW5kb3dTZXRGb2N1czogXCJ3aW5kb3dzOldpbmRvd1NldEZvY3VzXCIsXG5cdFx0V2luZG93U2hvdzogXCJ3aW5kb3dzOldpbmRvd1Nob3dcIixcblx0XHRXaW5kb3dTdGFydE1vdmU6IFwid2luZG93czpXaW5kb3dTdGFydE1vdmVcIixcblx0XHRXaW5kb3dTdGFydFJlc2l6ZTogXCJ3aW5kb3dzOldpbmRvd1N0YXJ0UmVzaXplXCIsXG5cdFx0V2luZG93VW5GdWxsc2NyZWVuOiBcIndpbmRvd3M6V2luZG93VW5GdWxsc2NyZWVuXCIsXG5cdFx0V2luZG93Wk9yZGVyQ2hhbmdlZDogXCJ3aW5kb3dzOldpbmRvd1pPcmRlckNoYW5nZWRcIixcblx0XHRXaW5kb3dNaW5pbWlzZTogXCJ3aW5kb3dzOldpbmRvd01pbmltaXNlXCIsXG5cdFx0V2luZG93VW5NaW5pbWlzZTogXCJ3aW5kb3dzOldpbmRvd1VuTWluaW1pc2VcIixcblx0XHRXaW5kb3dNYXhpbWlzZTogXCJ3aW5kb3dzOldpbmRvd01heGltaXNlXCIsXG5cdFx0V2luZG93VW5NYXhpbWlzZTogXCJ3aW5kb3dzOldpbmRvd1VuTWF4aW1pc2VcIixcblx0fSksXG5cdE1hYzogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQmVjb21lQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VCYWNraW5nUHJvcGVydGllczogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VCYWNraW5nUHJvcGVydGllc1wiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlRWZmZWN0aXZlQXBwZWFyYW5jZTogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VJY29uOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZUljb25cIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZU9jY2x1c2lvblN0YXRlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZU9jY2x1c2lvblN0YXRlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VTY3JlZW5QYXJhbWV0ZXJzOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZVNjcmVlblBhcmFtZXRlcnNcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0JhckZyYW1lOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0JhckZyYW1lXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VTdGF0dXNCYXJPcmllbnRhdGlvbjogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VTdGF0dXNCYXJPcmllbnRhdGlvblwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlVGhlbWU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlVGhlbWVcIixcblx0XHRBcHBsaWNhdGlvbkRpZEZpbmlzaExhdW5jaGluZzogXCJtYWM6QXBwbGljYXRpb25EaWRGaW5pc2hMYXVuY2hpbmdcIixcblx0XHRBcHBsaWNhdGlvbkRpZEhpZGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkSGlkZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkUmVzaWduQWN0aXZlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZFJlc2lnbkFjdGl2ZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkVW5oaWRlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZFVuaGlkZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkVXBkYXRlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZFVwZGF0ZVwiLFxuXHRcdEFwcGxpY2F0aW9uU2hvdWxkSGFuZGxlUmVvcGVuOiBcIm1hYzpBcHBsaWNhdGlvblNob3VsZEhhbmRsZVJlb3BlblwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbEJlY29tZUFjdGl2ZTogXCJtYWM6QXBwbGljYXRpb25XaWxsQmVjb21lQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsRmluaXNoTGF1bmNoaW5nOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxGaW5pc2hMYXVuY2hpbmdcIixcblx0XHRBcHBsaWNhdGlvbldpbGxIaWRlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxIaWRlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsUmVzaWduQWN0aXZlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxUZXJtaW5hdGU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZVwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbFVuaGlkZTogXCJtYWM6QXBwbGljYXRpb25XaWxsVW5oaWRlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsVXBkYXRlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxVcGRhdGVcIixcblx0XHRNZW51RGlkQWRkSXRlbTogXCJtYWM6TWVudURpZEFkZEl0ZW1cIixcblx0XHRNZW51RGlkQmVnaW5UcmFja2luZzogXCJtYWM6TWVudURpZEJlZ2luVHJhY2tpbmdcIixcblx0XHRNZW51RGlkQ2xvc2U6IFwibWFjOk1lbnVEaWRDbG9zZVwiLFxuXHRcdE1lbnVEaWREaXNwbGF5SXRlbTogXCJtYWM6TWVudURpZERpc3BsYXlJdGVtXCIsXG5cdFx0TWVudURpZEVuZFRyYWNraW5nOiBcIm1hYzpNZW51RGlkRW5kVHJhY2tpbmdcIixcblx0XHRNZW51RGlkSGlnaGxpZ2h0SXRlbTogXCJtYWM6TWVudURpZEhpZ2hsaWdodEl0ZW1cIixcblx0XHRNZW51RGlkT3BlbjogXCJtYWM6TWVudURpZE9wZW5cIixcblx0XHRNZW51RGlkUG9wVXA6IFwibWFjOk1lbnVEaWRQb3BVcFwiLFxuXHRcdE1lbnVEaWRSZW1vdmVJdGVtOiBcIm1hYzpNZW51RGlkUmVtb3ZlSXRlbVwiLFxuXHRcdE1lbnVEaWRTZW5kQWN0aW9uOiBcIm1hYzpNZW51RGlkU2VuZEFjdGlvblwiLFxuXHRcdE1lbnVEaWRTZW5kQWN0aW9uVG9JdGVtOiBcIm1hYzpNZW51RGlkU2VuZEFjdGlvblRvSXRlbVwiLFxuXHRcdE1lbnVEaWRVcGRhdGU6IFwibWFjOk1lbnVEaWRVcGRhdGVcIixcblx0XHRNZW51V2lsbEFkZEl0ZW06IFwibWFjOk1lbnVXaWxsQWRkSXRlbVwiLFxuXHRcdE1lbnVXaWxsQmVnaW5UcmFja2luZzogXCJtYWM6TWVudVdpbGxCZWdpblRyYWNraW5nXCIsXG5cdFx0TWVudVdpbGxEaXNwbGF5SXRlbTogXCJtYWM6TWVudVdpbGxEaXNwbGF5SXRlbVwiLFxuXHRcdE1lbnVXaWxsRW5kVHJhY2tpbmc6IFwibWFjOk1lbnVXaWxsRW5kVHJhY2tpbmdcIixcblx0XHRNZW51V2lsbEhpZ2hsaWdodEl0ZW06IFwibWFjOk1lbnVXaWxsSGlnaGxpZ2h0SXRlbVwiLFxuXHRcdE1lbnVXaWxsT3BlbjogXCJtYWM6TWVudVdpbGxPcGVuXCIsXG5cdFx0TWVudVdpbGxQb3BVcDogXCJtYWM6TWVudVdpbGxQb3BVcFwiLFxuXHRcdE1lbnVXaWxsUmVtb3ZlSXRlbTogXCJtYWM6TWVudVdpbGxSZW1vdmVJdGVtXCIsXG5cdFx0TWVudVdpbGxTZW5kQWN0aW9uOiBcIm1hYzpNZW51V2lsbFNlbmRBY3Rpb25cIixcblx0XHRNZW51V2lsbFNlbmRBY3Rpb25Ub0l0ZW06IFwibWFjOk1lbnVXaWxsU2VuZEFjdGlvblRvSXRlbVwiLFxuXHRcdE1lbnVXaWxsVXBkYXRlOiBcIm1hYzpNZW51V2lsbFVwZGF0ZVwiLFxuXHRcdFdlYlZpZXdEaWRDb21taXROYXZpZ2F0aW9uOiBcIm1hYzpXZWJWaWV3RGlkQ29tbWl0TmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uOiBcIm1hYzpXZWJWaWV3RGlkRmluaXNoTmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRSZWNlaXZlU2VydmVyUmVkaXJlY3RGb3JQcm92aXNpb25hbE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRSZWNlaXZlU2VydmVyUmVkaXJlY3RGb3JQcm92aXNpb25hbE5hdmlnYXRpb25cIixcblx0XHRXZWJWaWV3RGlkU3RhcnRQcm92aXNpb25hbE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRTdGFydFByb3Zpc2lvbmFsTmF2aWdhdGlvblwiLFxuXHRcdFdpbmRvd0RpZEJlY29tZUtleTogXCJtYWM6V2luZG93RGlkQmVjb21lS2V5XCIsXG5cdFx0V2luZG93RGlkQmVjb21lTWFpbjogXCJtYWM6V2luZG93RGlkQmVjb21lTWFpblwiLFxuXHRcdFdpbmRvd0RpZEJlZ2luU2hlZXQ6IFwibWFjOldpbmRvd0RpZEJlZ2luU2hlZXRcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VBbHBoYTogXCJtYWM6V2luZG93RGlkQ2hhbmdlQWxwaGFcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VCYWNraW5nTG9jYXRpb246IFwibWFjOldpbmRvd0RpZENoYW5nZUJhY2tpbmdMb2NhdGlvblwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VCYWNraW5nUHJvcGVydGllc1wiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUNvbGxlY3Rpb25CZWhhdmlvcjogXCJtYWM6V2luZG93RGlkQ2hhbmdlQ29sbGVjdGlvbkJlaGF2aW9yXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlRWZmZWN0aXZlQXBwZWFyYW5jZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlRWZmZWN0aXZlQXBwZWFyYW5jZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZU9jY2x1c2lvblN0YXRlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VPY2NsdXNpb25TdGF0ZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZU9yZGVyaW5nTW9kZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlT3JkZXJpbmdNb2RlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5cIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTY3JlZW5QYXJhbWV0ZXJzOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5QYXJhbWV0ZXJzXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuUHJvZmlsZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU2NyZWVuUHJvZmlsZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZVNjcmVlblNwYWNlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5TcGFjZVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZVNjcmVlblNwYWNlUHJvcGVydGllczogXCJtYWM6V2luZG93RGlkQ2hhbmdlU2NyZWVuU3BhY2VQcm9wZXJ0aWVzXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2hhcmluZ1R5cGU6IFwibWFjOldpbmRvd0RpZENoYW5nZVNoYXJpbmdUeXBlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU3BhY2U6IFwibWFjOldpbmRvd0RpZENoYW5nZVNwYWNlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU3BhY2VPcmRlcmluZ01vZGU6IFwibWFjOldpbmRvd0RpZENoYW5nZVNwYWNlT3JkZXJpbmdNb2RlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlVGl0bGU6IFwibWFjOldpbmRvd0RpZENoYW5nZVRpdGxlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlVG9vbGJhcjogXCJtYWM6V2luZG93RGlkQ2hhbmdlVG9vbGJhclwiLFxuXHRcdFdpbmRvd0RpZERlbWluaWF0dXJpemU6IFwibWFjOldpbmRvd0RpZERlbWluaWF0dXJpemVcIixcblx0XHRXaW5kb3dEaWRFbmRTaGVldDogXCJtYWM6V2luZG93RGlkRW5kU2hlZXRcIixcblx0XHRXaW5kb3dEaWRFbnRlckZ1bGxTY3JlZW46IFwibWFjOldpbmRvd0RpZEVudGVyRnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZEVudGVyVmVyc2lvbkJyb3dzZXI6IFwibWFjOldpbmRvd0RpZEVudGVyVmVyc2lvbkJyb3dzZXJcIixcblx0XHRXaW5kb3dEaWRFeGl0RnVsbFNjcmVlbjogXCJtYWM6V2luZG93RGlkRXhpdEZ1bGxTY3JlZW5cIixcblx0XHRXaW5kb3dEaWRFeGl0VmVyc2lvbkJyb3dzZXI6IFwibWFjOldpbmRvd0RpZEV4aXRWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd0RpZEV4cG9zZTogXCJtYWM6V2luZG93RGlkRXhwb3NlXCIsXG5cdFx0V2luZG93RGlkRm9jdXM6IFwibWFjOldpbmRvd0RpZEZvY3VzXCIsXG5cdFx0V2luZG93RGlkTWluaWF0dXJpemU6IFwibWFjOldpbmRvd0RpZE1pbmlhdHVyaXplXCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJtYWM6V2luZG93RGlkTW92ZVwiLFxuXHRcdFdpbmRvd0RpZE9yZGVyT2ZmU2NyZWVuOiBcIm1hYzpXaW5kb3dEaWRPcmRlck9mZlNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZE9yZGVyT25TY3JlZW46IFwibWFjOldpbmRvd0RpZE9yZGVyT25TY3JlZW5cIixcblx0XHRXaW5kb3dEaWRSZXNpZ25LZXk6IFwibWFjOldpbmRvd0RpZFJlc2lnbktleVwiLFxuXHRcdFdpbmRvd0RpZFJlc2lnbk1haW46IFwibWFjOldpbmRvd0RpZFJlc2lnbk1haW5cIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwibWFjOldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZTogXCJtYWM6V2luZG93RGlkVXBkYXRlXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlQWxwaGE6IFwibWFjOldpbmRvd0RpZFVwZGF0ZUFscGhhXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlQ29sbGVjdGlvbkJlaGF2aW9yOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uQmVoYXZpb3JcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uUHJvcGVydGllczogXCJtYWM6V2luZG93RGlkVXBkYXRlQ29sbGVjdGlvblByb3BlcnRpZXNcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVTaGFkb3c6IFwibWFjOldpbmRvd0RpZFVwZGF0ZVNoYWRvd1wiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZVRpdGxlOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVUaXRsZVwiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZVRvb2xiYXI6IFwibWFjOldpbmRvd0RpZFVwZGF0ZVRvb2xiYXJcIixcblx0XHRXaW5kb3dEaWRab29tOiBcIm1hYzpXaW5kb3dEaWRab29tXCIsXG5cdFx0V2luZG93RmlsZURyYWdnaW5nRW50ZXJlZDogXCJtYWM6V2luZG93RmlsZURyYWdnaW5nRW50ZXJlZFwiLFxuXHRcdFdpbmRvd0ZpbGVEcmFnZ2luZ0V4aXRlZDogXCJtYWM6V2luZG93RmlsZURyYWdnaW5nRXhpdGVkXCIsXG5cdFx0V2luZG93RmlsZURyYWdnaW5nUGVyZm9ybWVkOiBcIm1hYzpXaW5kb3dGaWxlRHJhZ2dpbmdQZXJmb3JtZWRcIixcblx0XHRXaW5kb3dIaWRlOiBcIm1hYzpXaW5kb3dIaWRlXCIsXG5cdFx0V2luZG93TWF4aW1pc2U6IFwibWFjOldpbmRvd01heGltaXNlXCIsXG5cdFx0V2luZG93VW5NYXhpbWlzZTogXCJtYWM6V2luZG93VW5NYXhpbWlzZVwiLFxuXHRcdFdpbmRvd01pbmltaXNlOiBcIm1hYzpXaW5kb3dNaW5pbWlzZVwiLFxuXHRcdFdpbmRvd1VuTWluaW1pc2U6IFwibWFjOldpbmRvd1VuTWluaW1pc2VcIixcblx0XHRXaW5kb3dTaG91bGRDbG9zZTogXCJtYWM6V2luZG93U2hvdWxkQ2xvc2VcIixcblx0XHRXaW5kb3dTaG93OiBcIm1hYzpXaW5kb3dTaG93XCIsXG5cdFx0V2luZG93V2lsbEJlY29tZUtleTogXCJtYWM6V2luZG93V2lsbEJlY29tZUtleVwiLFxuXHRcdFdpbmRvd1dpbGxCZWNvbWVNYWluOiBcIm1hYzpXaW5kb3dXaWxsQmVjb21lTWFpblwiLFxuXHRcdFdpbmRvd1dpbGxCZWdpblNoZWV0OiBcIm1hYzpXaW5kb3dXaWxsQmVnaW5TaGVldFwiLFxuXHRcdFdpbmRvd1dpbGxDaGFuZ2VPcmRlcmluZ01vZGU6IFwibWFjOldpbmRvd1dpbGxDaGFuZ2VPcmRlcmluZ01vZGVcIixcblx0XHRXaW5kb3dXaWxsQ2xvc2U6IFwibWFjOldpbmRvd1dpbGxDbG9zZVwiLFxuXHRcdFdpbmRvd1dpbGxEZW1pbmlhdHVyaXplOiBcIm1hYzpXaW5kb3dXaWxsRGVtaW5pYXR1cml6ZVwiLFxuXHRcdFdpbmRvd1dpbGxFbnRlckZ1bGxTY3JlZW46IFwibWFjOldpbmRvd1dpbGxFbnRlckZ1bGxTY3JlZW5cIixcblx0XHRXaW5kb3dXaWxsRW50ZXJWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93V2lsbEVudGVyVmVyc2lvbkJyb3dzZXJcIixcblx0XHRXaW5kb3dXaWxsRXhpdEZ1bGxTY3JlZW46IFwibWFjOldpbmRvd1dpbGxFeGl0RnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxFeGl0VmVyc2lvbkJyb3dzZXI6IFwibWFjOldpbmRvd1dpbGxFeGl0VmVyc2lvbkJyb3dzZXJcIixcblx0XHRXaW5kb3dXaWxsRm9jdXM6IFwibWFjOldpbmRvd1dpbGxGb2N1c1wiLFxuXHRcdFdpbmRvd1dpbGxNaW5pYXR1cml6ZTogXCJtYWM6V2luZG93V2lsbE1pbmlhdHVyaXplXCIsXG5cdFx0V2luZG93V2lsbE1vdmU6IFwibWFjOldpbmRvd1dpbGxNb3ZlXCIsXG5cdFx0V2luZG93V2lsbE9yZGVyT2ZmU2NyZWVuOiBcIm1hYzpXaW5kb3dXaWxsT3JkZXJPZmZTY3JlZW5cIixcblx0XHRXaW5kb3dXaWxsT3JkZXJPblNjcmVlbjogXCJtYWM6V2luZG93V2lsbE9yZGVyT25TY3JlZW5cIixcblx0XHRXaW5kb3dXaWxsUmVzaWduTWFpbjogXCJtYWM6V2luZG93V2lsbFJlc2lnbk1haW5cIixcblx0XHRXaW5kb3dXaWxsUmVzaXplOiBcIm1hYzpXaW5kb3dXaWxsUmVzaXplXCIsXG5cdFx0V2luZG93V2lsbFVuZm9jdXM6IFwibWFjOldpbmRvd1dpbGxVbmZvY3VzXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVwiLFxuXHRcdFdpbmRvd1dpbGxVcGRhdGVBbHBoYTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZUFscGhhXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25CZWhhdmlvcjogXCJtYWM6V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25CZWhhdmlvclwiLFxuXHRcdFdpbmRvd1dpbGxVcGRhdGVDb2xsZWN0aW9uUHJvcGVydGllczogXCJtYWM6V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25Qcm9wZXJ0aWVzXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVNoYWRvdzogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVNoYWRvd1wiLFxuXHRcdFdpbmRvd1dpbGxVcGRhdGVUaXRsZTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVRpdGxlXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVRvb2xiYXI6IFwibWFjOldpbmRvd1dpbGxVcGRhdGVUb29sYmFyXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVZpc2liaWxpdHk6IFwibWFjOldpbmRvd1dpbGxVcGRhdGVWaXNpYmlsaXR5XCIsXG5cdFx0V2luZG93V2lsbFVzZVN0YW5kYXJkRnJhbWU6IFwibWFjOldpbmRvd1dpbGxVc2VTdGFuZGFyZEZyYW1lXCIsXG5cdFx0V2luZG93Wm9vbUluOiBcIm1hYzpXaW5kb3dab29tSW5cIixcblx0XHRXaW5kb3dab29tT3V0OiBcIm1hYzpXaW5kb3dab29tT3V0XCIsXG5cdFx0V2luZG93Wm9vbVJlc2V0OiBcIm1hYzpXaW5kb3dab29tUmVzZXRcIixcblx0fSksXG5cdExpbnV4OiBPYmplY3QuZnJlZXplKHtcblx0XHRBcHBsaWNhdGlvblN0YXJ0dXA6IFwibGludXg6QXBwbGljYXRpb25TdGFydHVwXCIsXG5cdFx0U3lzdGVtVGhlbWVDaGFuZ2VkOiBcImxpbnV4OlN5c3RlbVRoZW1lQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd0RlbGV0ZUV2ZW50OiBcImxpbnV4OldpbmRvd0RlbGV0ZUV2ZW50XCIsXG5cdFx0V2luZG93RGlkTW92ZTogXCJsaW51eDpXaW5kb3dEaWRNb3ZlXCIsXG5cdFx0V2luZG93RGlkUmVzaXplOiBcImxpbnV4OldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0ZvY3VzSW46IFwibGludXg6V2luZG93Rm9jdXNJblwiLFxuXHRcdFdpbmRvd0ZvY3VzT3V0OiBcImxpbnV4OldpbmRvd0ZvY3VzT3V0XCIsXG5cdFx0V2luZG93TG9hZFN0YXJ0ZWQ6IFwibGludXg6V2luZG93TG9hZFN0YXJ0ZWRcIixcblx0XHRXaW5kb3dMb2FkUmVkaXJlY3RlZDogXCJsaW51eDpXaW5kb3dMb2FkUmVkaXJlY3RlZFwiLFxuXHRcdFdpbmRvd0xvYWRDb21taXR0ZWQ6IFwibGludXg6V2luZG93TG9hZENvbW1pdHRlZFwiLFxuXHRcdFdpbmRvd0xvYWRGaW5pc2hlZDogXCJsaW51eDpXaW5kb3dMb2FkRmluaXNoZWRcIixcblx0fSksXG5cdGlPUzogT2JqZWN0LmZyZWV6ZSh7XG5cdFx0QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmU6IFwiaW9zOkFwcGxpY2F0aW9uRGlkQmVjb21lQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRFbnRlckJhY2tncm91bmQ6IFwiaW9zOkFwcGxpY2F0aW9uRGlkRW50ZXJCYWNrZ3JvdW5kXCIsXG5cdFx0QXBwbGljYXRpb25EaWRGaW5pc2hMYXVuY2hpbmc6IFwiaW9zOkFwcGxpY2F0aW9uRGlkRmluaXNoTGF1bmNoaW5nXCIsXG5cdFx0QXBwbGljYXRpb25EaWRSZWNlaXZlTWVtb3J5V2FybmluZzogXCJpb3M6QXBwbGljYXRpb25EaWRSZWNlaXZlTWVtb3J5V2FybmluZ1wiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbEVudGVyRm9yZWdyb3VuZDogXCJpb3M6QXBwbGljYXRpb25XaWxsRW50ZXJGb3JlZ3JvdW5kXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsUmVzaWduQWN0aXZlOiBcImlvczpBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxUZXJtaW5hdGU6IFwiaW9zOkFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZVwiLFxuXHRcdFdpbmRvd0RpZExvYWQ6IFwiaW9zOldpbmRvd0RpZExvYWRcIixcblx0XHRXaW5kb3dXaWxsQXBwZWFyOiBcImlvczpXaW5kb3dXaWxsQXBwZWFyXCIsXG5cdFx0V2luZG93RGlkQXBwZWFyOiBcImlvczpXaW5kb3dEaWRBcHBlYXJcIixcblx0XHRXaW5kb3dXaWxsRGlzYXBwZWFyOiBcImlvczpXaW5kb3dXaWxsRGlzYXBwZWFyXCIsXG5cdFx0V2luZG93RGlkRGlzYXBwZWFyOiBcImlvczpXaW5kb3dEaWREaXNhcHBlYXJcIixcblx0XHRXaW5kb3dTYWZlQXJlYUluc2V0c0NoYW5nZWQ6IFwiaW9zOldpbmRvd1NhZmVBcmVhSW5zZXRzQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd09yaWVudGF0aW9uQ2hhbmdlZDogXCJpb3M6V2luZG93T3JpZW50YXRpb25DaGFuZ2VkXCIsXG5cdFx0V2luZG93VG91Y2hCZWdhbjogXCJpb3M6V2luZG93VG91Y2hCZWdhblwiLFxuXHRcdFdpbmRvd1RvdWNoTW92ZWQ6IFwiaW9zOldpbmRvd1RvdWNoTW92ZWRcIixcblx0XHRXaW5kb3dUb3VjaEVuZGVkOiBcImlvczpXaW5kb3dUb3VjaEVuZGVkXCIsXG5cdFx0V2luZG93VG91Y2hDYW5jZWxsZWQ6IFwiaW9zOldpbmRvd1RvdWNoQ2FuY2VsbGVkXCIsXG5cdFx0V2ViVmlld0RpZFN0YXJ0TmF2aWdhdGlvbjogXCJpb3M6V2ViVmlld0RpZFN0YXJ0TmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uOiBcImlvczpXZWJWaWV3RGlkRmluaXNoTmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRGYWlsTmF2aWdhdGlvbjogXCJpb3M6V2ViVmlld0RpZEZhaWxOYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RlY2lkZVBvbGljeUZvck5hdmlnYXRpb25BY3Rpb246IFwiaW9zOldlYlZpZXdEZWNpZGVQb2xpY3lGb3JOYXZpZ2F0aW9uQWN0aW9uXCIsXG5cdH0pLFxuXHRDb21tb246IE9iamVjdC5mcmVlemUoe1xuXHRcdEFwcGxpY2F0aW9uT3BlbmVkV2l0aEZpbGU6IFwiY29tbW9uOkFwcGxpY2F0aW9uT3BlbmVkV2l0aEZpbGVcIixcblx0XHRBcHBsaWNhdGlvblN0YXJ0ZWQ6IFwiY29tbW9uOkFwcGxpY2F0aW9uU3RhcnRlZFwiLFxuXHRcdEFwcGxpY2F0aW9uTGF1bmNoZWRXaXRoVXJsOiBcImNvbW1vbjpBcHBsaWNhdGlvbkxhdW5jaGVkV2l0aFVybFwiLFxuXHRcdFRoZW1lQ2hhbmdlZDogXCJjb21tb246VGhlbWVDaGFuZ2VkXCIsXG5cdFx0V2luZG93Q2xvc2luZzogXCJjb21tb246V2luZG93Q2xvc2luZ1wiLFxuXHRcdFdpbmRvd0RpZE1vdmU6IFwiY29tbW9uOldpbmRvd0RpZE1vdmVcIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwiY29tbW9uOldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0RQSUNoYW5nZWQ6IFwiY29tbW9uOldpbmRvd0RQSUNoYW5nZWRcIixcblx0XHRXaW5kb3dGaWxlc0Ryb3BwZWQ6IFwiY29tbW9uOldpbmRvd0ZpbGVzRHJvcHBlZFwiLFxuXHRcdFdpbmRvd0ZvY3VzOiBcImNvbW1vbjpXaW5kb3dGb2N1c1wiLFxuXHRcdFdpbmRvd0Z1bGxzY3JlZW46IFwiY29tbW9uOldpbmRvd0Z1bGxzY3JlZW5cIixcblx0XHRXaW5kb3dIaWRlOiBcImNvbW1vbjpXaW5kb3dIaWRlXCIsXG5cdFx0V2luZG93TG9zdEZvY3VzOiBcImNvbW1vbjpXaW5kb3dMb3N0Rm9jdXNcIixcblx0XHRXaW5kb3dNYXhpbWlzZTogXCJjb21tb246V2luZG93TWF4aW1pc2VcIixcblx0XHRXaW5kb3dNaW5pbWlzZTogXCJjb21tb246V2luZG93TWluaW1pc2VcIixcblx0XHRXaW5kb3dUb2dnbGVGcmFtZWxlc3M6IFwiY29tbW9uOldpbmRvd1RvZ2dsZUZyYW1lbGVzc1wiLFxuXHRcdFdpbmRvd1Jlc3RvcmU6IFwiY29tbW9uOldpbmRvd1Jlc3RvcmVcIixcblx0XHRXaW5kb3dSdW50aW1lUmVhZHk6IFwiY29tbW9uOldpbmRvd1J1bnRpbWVSZWFkeVwiLFxuXHRcdFdpbmRvd1Nob3c6IFwiY29tbW9uOldpbmRvd1Nob3dcIixcblx0XHRXaW5kb3dVbkZ1bGxzY3JlZW46IFwiY29tbW9uOldpbmRvd1VuRnVsbHNjcmVlblwiLFxuXHRcdFdpbmRvd1VuTWF4aW1pc2U6IFwiY29tbW9uOldpbmRvd1VuTWF4aW1pc2VcIixcblx0XHRXaW5kb3dVbk1pbmltaXNlOiBcImNvbW1vbjpXaW5kb3dVbk1pbmltaXNlXCIsXG5cdFx0V2luZG93Wm9vbTogXCJjb21tb246V2luZG93Wm9vbVwiLFxuXHRcdFdpbmRvd1pvb21JbjogXCJjb21tb246V2luZG93Wm9vbUluXCIsXG5cdFx0V2luZG93Wm9vbU91dDogXCJjb21tb246V2luZG93Wm9vbU91dFwiLFxuXHRcdFdpbmRvd1pvb21SZXNldDogXCJjb21tb246V2luZG93Wm9vbVJlc2V0XCIsXG5cdH0pLFxufSk7XG4iLCAiLypcbiBfICAgICBfXyAgICAgXyBfX1xufCB8ICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qKlxuICogTG9ncyBhIG1lc3NhZ2UgdG8gdGhlIGNvbnNvbGUgd2l0aCBjdXN0b20gZm9ybWF0dGluZy5cbiAqXG4gKiBAcGFyYW0gbWVzc2FnZSAtIFRoZSBtZXNzYWdlIHRvIGJlIGxvZ2dlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlYnVnTG9nKG1lc3NhZ2U6IGFueSkge1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZVxuICAgIGNvbnNvbGUubG9nKFxuICAgICAgICAnJWMgd2FpbHMzICVjICcgKyBtZXNzYWdlICsgJyAnLFxuICAgICAgICAnYmFja2dyb3VuZDogI2FhMDAwMDsgY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDNweCAwcHggMHB4IDNweDsgcGFkZGluZzogMXB4OyBmb250LXNpemU6IDAuN3JlbScsXG4gICAgICAgICdiYWNrZ3JvdW5kOiAjMDA5OTAwOyBjb2xvcjogI2ZmZjsgYm9yZGVyLXJhZGl1czogMHB4IDNweCAzcHggMHB4OyBwYWRkaW5nOiAxcHg7IGZvbnQtc2l6ZTogMC43cmVtJ1xuICAgICk7XG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgdGhlIHdlYnZpZXcgc3VwcG9ydHMgdGhlIHtAbGluayBNb3VzZUV2ZW50I2J1dHRvbnN9IHByb3BlcnR5LlxuICogTG9va2luZyBhdCB5b3UgbWFjT1MgSGlnaCBTaWVycmEhXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjYW5UcmFja0J1dHRvbnMoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIChuZXcgTW91c2VFdmVudCgnbW91c2Vkb3duJykpLmJ1dHRvbnMgPT09IDA7XG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgdGhlIGJyb3dzZXIgc3VwcG9ydHMgcmVtb3ZpbmcgbGlzdGVuZXJzIGJ5IHRyaWdnZXJpbmcgYW4gQWJvcnRTaWduYWxcbiAqIChzZWUgaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvQVBJL0V2ZW50VGFyZ2V0L2FkZEV2ZW50TGlzdGVuZXIjc2lnbmFsKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbkFib3J0TGlzdGVuZXJzKCkge1xuICAgIGlmICghRXZlbnRUYXJnZXQgfHwgIUFib3J0U2lnbmFsIHx8ICFBYm9ydENvbnRyb2xsZXIpXG4gICAgICAgIHJldHVybiBmYWxzZTtcblxuICAgIGxldCByZXN1bHQgPSB0cnVlO1xuXG4gICAgY29uc3QgdGFyZ2V0ID0gbmV3IEV2ZW50VGFyZ2V0KCk7XG4gICAgY29uc3QgY29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICB0YXJnZXQuYWRkRXZlbnRMaXN0ZW5lcigndGVzdCcsICgpID0+IHsgcmVzdWx0ID0gZmFsc2U7IH0sIHsgc2lnbmFsOiBjb250cm9sbGVyLnNpZ25hbCB9KTtcbiAgICBjb250cm9sbGVyLmFib3J0KCk7XG4gICAgdGFyZ2V0LmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCd0ZXN0JykpO1xuXG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgY2xvc2VzdCBIVE1MRWxlbWVudCBhbmNlc3RvciBvZiBhbiBldmVudCdzIHRhcmdldC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV2ZW50VGFyZ2V0KGV2ZW50OiBFdmVudCk6IEhUTUxFbGVtZW50IHtcbiAgICBpZiAoZXZlbnQudGFyZ2V0IGluc3RhbmNlb2YgSFRNTEVsZW1lbnQpIHtcbiAgICAgICAgcmV0dXJuIGV2ZW50LnRhcmdldDtcbiAgICB9IGVsc2UgaWYgKCEoZXZlbnQudGFyZ2V0IGluc3RhbmNlb2YgSFRNTEVsZW1lbnQpICYmIGV2ZW50LnRhcmdldCBpbnN0YW5jZW9mIE5vZGUpIHtcbiAgICAgICAgcmV0dXJuIGV2ZW50LnRhcmdldC5wYXJlbnRFbGVtZW50ID8/IGRvY3VtZW50LmJvZHk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGRvY3VtZW50LmJvZHk7XG4gICAgfVxufVxuXG4vKioqXG4gVGhpcyB0ZWNobmlxdWUgZm9yIHByb3BlciBsb2FkIGRldGVjdGlvbiBpcyB0YWtlbiBmcm9tIEhUTVg6XG5cbiBCU0QgMi1DbGF1c2UgTGljZW5zZVxuXG4gQ29weXJpZ2h0IChjKSAyMDIwLCBCaWcgU2t5IFNvZnR3YXJlXG4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cblxuIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dFxuIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUgbWV0OlxuXG4gMS4gUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLCB0aGlzXG4gbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuXG5cbiAyLiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsXG4gdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvblxuIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLlxuXG4gVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUyBcIkFTIElTXCJcbiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFXG4gSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFXG4gRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRVxuIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMXG4gREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1JcbiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUlxuIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksXG4gT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRIRSBVU0VcbiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLlxuXG4gKioqL1xuXG5sZXQgaXNSZWFkeSA9IGZhbHNlO1xuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsICgpID0+IHsgaXNSZWFkeSA9IHRydWUgfSk7XG5cbmV4cG9ydCBmdW5jdGlvbiB3aGVuUmVhZHkoY2FsbGJhY2s6ICgpID0+IHZvaWQpIHtcbiAgICBpZiAoaXNSZWFkeSB8fCBkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnY29tcGxldGUnKSB7XG4gICAgICAgIGNhbGxiYWNrKCk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGNhbGxiYWNrKTtcbiAgICB9XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7bmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcbmltcG9ydCB0eXBlIHsgU2NyZWVuIH0gZnJvbSBcIi4vc2NyZWVucy5qc1wiO1xuXG4vLyBEcm9wIHRhcmdldCBjb25zdGFudHNcbmNvbnN0IERST1BfVEFSR0VUX0FUVFJJQlVURSA9ICdkYXRhLWZpbGUtZHJvcC10YXJnZXQnO1xuY29uc3QgRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTID0gJ2ZpbGUtZHJvcC10YXJnZXQtYWN0aXZlJztcbmxldCBjdXJyZW50RHJvcFRhcmdldDogRWxlbWVudCB8IG51bGwgPSBudWxsO1xuXG5jb25zdCBQb3NpdGlvbk1ldGhvZCAgICAgICAgICAgICAgICAgICAgPSAwO1xuY29uc3QgQ2VudGVyTWV0aG9kICAgICAgICAgICAgICAgICAgICAgID0gMTtcbmNvbnN0IENsb3NlTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICA9IDI7XG5jb25zdCBEaXNhYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kICAgICAgPSAzO1xuY29uc3QgRW5hYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kICAgICAgID0gNDtcbmNvbnN0IEZvY3VzTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICA9IDU7XG5jb25zdCBGb3JjZVJlbG9hZE1ldGhvZCAgICAgICAgICAgICAgICAgPSA2O1xuY29uc3QgRnVsbHNjcmVlbk1ldGhvZCAgICAgICAgICAgICAgICAgID0gNztcbmNvbnN0IEdldFNjcmVlbk1ldGhvZCAgICAgICAgICAgICAgICAgICA9IDg7XG5jb25zdCBHZXRab29tTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSA5O1xuY29uc3QgSGVpZ2h0TWV0aG9kICAgICAgICAgICAgICAgICAgICAgID0gMTA7XG5jb25zdCBIaWRlTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSAxMTtcbmNvbnN0IElzRm9jdXNlZE1ldGhvZCAgICAgICAgICAgICAgICAgICA9IDEyO1xuY29uc3QgSXNGdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgICAgID0gMTM7XG5jb25zdCBJc01heGltaXNlZE1ldGhvZCAgICAgICAgICAgICAgICAgPSAxNDtcbmNvbnN0IElzTWluaW1pc2VkTWV0aG9kICAgICAgICAgICAgICAgICA9IDE1O1xuY29uc3QgTWF4aW1pc2VNZXRob2QgICAgICAgICAgICAgICAgICAgID0gMTY7XG5jb25zdCBNaW5pbWlzZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgPSAxNztcbmNvbnN0IE5hbWVNZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDE4O1xuY29uc3QgT3BlbkRldlRvb2xzTWV0aG9kICAgICAgICAgICAgICAgID0gMTk7XG5jb25zdCBSZWxhdGl2ZVBvc2l0aW9uTWV0aG9kICAgICAgICAgICAgPSAyMDtcbmNvbnN0IFJlbG9hZE1ldGhvZCAgICAgICAgICAgICAgICAgICAgICA9IDIxO1xuY29uc3QgUmVzaXphYmxlTWV0aG9kICAgICAgICAgICAgICAgICAgID0gMjI7XG5jb25zdCBSZXN0b3JlTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSAyMztcbmNvbnN0IFNldFBvc2l0aW9uTWV0aG9kICAgICAgICAgICAgICAgICA9IDI0O1xuY29uc3QgU2V0QWx3YXlzT25Ub3BNZXRob2QgICAgICAgICAgICAgID0gMjU7XG5jb25zdCBTZXRCYWNrZ3JvdW5kQ29sb3VyTWV0aG9kICAgICAgICAgPSAyNjtcbmNvbnN0IFNldEZyYW1lbGVzc01ldGhvZCAgICAgICAgICAgICAgICA9IDI3O1xuY29uc3QgU2V0RnVsbHNjcmVlbkJ1dHRvbkVuYWJsZWRNZXRob2QgID0gMjg7XG5jb25zdCBTZXRNYXhTaXplTWV0aG9kICAgICAgICAgICAgICAgICAgPSAyOTtcbmNvbnN0IFNldE1pblNpemVNZXRob2QgICAgICAgICAgICAgICAgICA9IDMwO1xuY29uc3QgU2V0UmVsYXRpdmVQb3NpdGlvbk1ldGhvZCAgICAgICAgID0gMzE7XG5jb25zdCBTZXRSZXNpemFibGVNZXRob2QgICAgICAgICAgICAgICAgPSAzMjtcbmNvbnN0IFNldFNpemVNZXRob2QgICAgICAgICAgICAgICAgICAgICA9IDMzO1xuY29uc3QgU2V0VGl0bGVNZXRob2QgICAgICAgICAgICAgICAgICAgID0gMzQ7XG5jb25zdCBTZXRab29tTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSAzNTtcbmNvbnN0IFNob3dNZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDM2O1xuY29uc3QgU2l6ZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgID0gMzc7XG5jb25zdCBUb2dnbGVGdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgPSAzODtcbmNvbnN0IFRvZ2dsZU1heGltaXNlTWV0aG9kICAgICAgICAgICAgICA9IDM5O1xuY29uc3QgVG9nZ2xlRnJhbWVsZXNzTWV0aG9kICAgICAgICAgICAgID0gNDA7IFxuY29uc3QgVW5GdWxsc2NyZWVuTWV0aG9kICAgICAgICAgICAgICAgID0gNDE7XG5jb25zdCBVbk1heGltaXNlTWV0aG9kICAgICAgICAgICAgICAgICAgPSA0MjtcbmNvbnN0IFVuTWluaW1pc2VNZXRob2QgICAgICAgICAgICAgICAgICA9IDQzO1xuY29uc3QgV2lkdGhNZXRob2QgICAgICAgICAgICAgICAgICAgICAgID0gNDQ7XG5jb25zdCBab29tTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSA0NTtcbmNvbnN0IFpvb21Jbk1ldGhvZCAgICAgICAgICAgICAgICAgICAgICA9IDQ2O1xuY29uc3QgWm9vbU91dE1ldGhvZCAgICAgICAgICAgICAgICAgICAgID0gNDc7XG5jb25zdCBab29tUmVzZXRNZXRob2QgICAgICAgICAgICAgICAgICAgPSA0ODtcbmNvbnN0IFNuYXBBc3Npc3RNZXRob2QgICAgICAgICAgICAgICAgICA9IDQ5O1xuY29uc3QgRmlsZXNEcm9wcGVkICAgICAgICAgICAgICAgICAgICAgID0gNTA7XG5jb25zdCBQcmludE1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgPSA1MTtcblxuLyoqXG4gKiBGaW5kcyB0aGUgbmVhcmVzdCBkcm9wIHRhcmdldCBlbGVtZW50IGJ5IHdhbGtpbmcgdXAgdGhlIERPTSB0cmVlLlxuICovXG5mdW5jdGlvbiBnZXREcm9wVGFyZ2V0RWxlbWVudChlbGVtZW50OiBFbGVtZW50IHwgbnVsbCk6IEVsZW1lbnQgfCBudWxsIHtcbiAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiBlbGVtZW50LmNsb3Nlc3QoYFske0RST1BfVEFSR0VUX0FUVFJJQlVURX1dYCk7XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgd2UgY2FuIHVzZSBXZWJWaWV3MidzIHBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzIChXaW5kb3dzKVxuICogQWxzbyBjaGVja3MgdGhhdCBFbmFibGVGaWxlRHJvcCBpcyB0cnVlIGZvciB0aGlzIHdpbmRvdy5cbiAqL1xuZnVuY3Rpb24gY2FuUmVzb2x2ZUZpbGVQYXRocygpOiBib29sZWFuIHtcbiAgICAvLyBNdXN0IGhhdmUgV2ViVmlldzIncyBwb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyBBUEkgKFdpbmRvd3Mgb25seSlcbiAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLmNocm9tZT8ud2Vidmlldz8ucG9zdE1lc3NhZ2VXaXRoQWRkaXRpb25hbE9iamVjdHMgPT0gbnVsbCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIC8vIE11c3QgaGF2ZSBFbmFibGVGaWxlRHJvcCBzZXQgdG8gdHJ1ZSBmb3IgdGhpcyB3aW5kb3dcbiAgICAvLyBUaGlzIGZsYWcgaXMgc2V0IGJ5IHRoZSBHbyBiYWNrZW5kIGR1cmluZyBydW50aW1lIGluaXRpYWxpemF0aW9uXG4gICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmZsYWdzPy5lbmFibGVGaWxlRHJvcCA9PT0gdHJ1ZTtcbn1cblxuLyoqXG4gKiBTZW5kIGZpbGUgZHJvcCB0byBiYWNrZW5kIHZpYSBXZWJWaWV3MiAoV2luZG93cyBvbmx5KVxuICovXG5mdW5jdGlvbiByZXNvbHZlRmlsZVBhdGhzKHg6IG51bWJlciwgeTogbnVtYmVyLCBmaWxlczogRmlsZVtdKTogdm9pZCB7XG4gICAgaWYgKCh3aW5kb3cgYXMgYW55KS5jaHJvbWU/LndlYnZpZXc/LnBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzKSB7XG4gICAgICAgICh3aW5kb3cgYXMgYW55KS5jaHJvbWUud2Vidmlldy5wb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyhgZmlsZTpkcm9wOiR7eH06JHt5fWAsIGZpbGVzKTtcbiAgICB9XG59XG5cbi8vIE5hdGl2ZSBkcmFnIHN0YXRlIChMaW51eC9tYWNPUyBpbnRlcmNlcHQgRE9NIGRyYWcgZXZlbnRzKVxubGV0IG5hdGl2ZURyYWdBY3RpdmUgPSBmYWxzZTtcblxuLyoqXG4gKiBDbGVhbnMgdXAgbmF0aXZlIGRyYWcgc3RhdGUgYW5kIGhvdmVyIGVmZmVjdHMuXG4gKiBDYWxsZWQgb24gZHJvcCBvciB3aGVuIGRyYWcgbGVhdmVzIHRoZSB3aW5kb3cuXG4gKi9cbmZ1bmN0aW9uIGNsZWFudXBOYXRpdmVEcmFnKCk6IHZvaWQge1xuICAgIG5hdGl2ZURyYWdBY3RpdmUgPSBmYWxzZTtcbiAgICBpZiAoY3VycmVudERyb3BUYXJnZXQpIHtcbiAgICAgICAgY3VycmVudERyb3BUYXJnZXQuY2xhc3NMaXN0LnJlbW92ZShEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICBjdXJyZW50RHJvcFRhcmdldCA9IG51bGw7XG4gICAgfVxufVxuXG4vKipcbiAqIENhbGxlZCBmcm9tIEdvIHdoZW4gYSBmaWxlIGRyYWcgZW50ZXJzIHRoZSB3aW5kb3cgb24gTGludXgvbWFjT1MuXG4gKi9cbmZ1bmN0aW9uIGhhbmRsZURyYWdFbnRlcigpOiB2b2lkIHtcbiAgICAvLyBDaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgIGlmICgod2luZG93IGFzIGFueSkuX3dhaWxzPy5mbGFncz8uZW5hYmxlRmlsZURyb3AgPT09IGZhbHNlKSB7XG4gICAgICAgIHJldHVybjsgLy8gRmlsZSBkcm9wcyBkaXNhYmxlZCwgZG9uJ3QgYWN0aXZhdGUgZHJhZyBzdGF0ZVxuICAgIH1cbiAgICBuYXRpdmVEcmFnQWN0aXZlID0gdHJ1ZTtcbn1cblxuLyoqXG4gKiBDYWxsZWQgZnJvbSBHbyB3aGVuIGEgZmlsZSBkcmFnIGxlYXZlcyB0aGUgd2luZG93IG9uIExpbnV4L21hY09TLlxuICovXG5mdW5jdGlvbiBoYW5kbGVEcmFnTGVhdmUoKTogdm9pZCB7XG4gICAgY2xlYW51cE5hdGl2ZURyYWcoKTtcbn1cblxuLyoqXG4gKiBDYWxsZWQgZnJvbSBHbyBkdXJpbmcgZmlsZSBkcmFnIHRvIHVwZGF0ZSBob3ZlciBzdGF0ZSBvbiBMaW51eC9tYWNPUy5cbiAqIEBwYXJhbSB4IC0gWCBjb29yZGluYXRlIGluIENTUyBwaXhlbHNcbiAqIEBwYXJhbSB5IC0gWSBjb29yZGluYXRlIGluIENTUyBwaXhlbHNcbiAqL1xuZnVuY3Rpb24gaGFuZGxlRHJhZ092ZXIoeDogbnVtYmVyLCB5OiBudW1iZXIpOiB2b2lkIHtcbiAgICBpZiAoIW5hdGl2ZURyYWdBY3RpdmUpIHJldHVybjtcbiAgICBcbiAgICAvLyBDaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgIGlmICgod2luZG93IGFzIGFueSkuX3dhaWxzPy5mbGFncz8uZW5hYmxlRmlsZURyb3AgPT09IGZhbHNlKSB7XG4gICAgICAgIHJldHVybjsgLy8gRmlsZSBkcm9wcyBkaXNhYmxlZCwgZG9uJ3Qgc2hvdyBob3ZlciBlZmZlY3RzXG4gICAgfVxuICAgIFxuICAgIGNvbnN0IHRhcmdldEVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHgsIHkpO1xuICAgIGNvbnN0IGRyb3BUYXJnZXQgPSBnZXREcm9wVGFyZ2V0RWxlbWVudCh0YXJnZXRFbGVtZW50KTtcbiAgICBcbiAgICBpZiAoY3VycmVudERyb3BUYXJnZXQgJiYgY3VycmVudERyb3BUYXJnZXQgIT09IGRyb3BUYXJnZXQpIHtcbiAgICAgICAgY3VycmVudERyb3BUYXJnZXQuY2xhc3NMaXN0LnJlbW92ZShEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgIH1cbiAgICBcbiAgICBpZiAoZHJvcFRhcmdldCkge1xuICAgICAgICBkcm9wVGFyZ2V0LmNsYXNzTGlzdC5hZGQoRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTKTtcbiAgICAgICAgY3VycmVudERyb3BUYXJnZXQgPSBkcm9wVGFyZ2V0O1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gbnVsbDtcbiAgICB9XG59XG5cblxuXG4vLyBFeHBvcnQgdGhlIGhhbmRsZXJzIGZvciB1c2UgYnkgR28gdmlhIGluZGV4LnRzXG5leHBvcnQgeyBoYW5kbGVEcmFnRW50ZXIsIGhhbmRsZURyYWdMZWF2ZSwgaGFuZGxlRHJhZ092ZXIgfTtcblxuLyoqXG4gKiBBIHJlY29yZCBkZXNjcmliaW5nIHRoZSBwb3NpdGlvbiBvZiBhIHdpbmRvdy5cbiAqL1xuaW50ZXJmYWNlIFBvc2l0aW9uIHtcbiAgICAvKiogVGhlIGhvcml6b250YWwgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy4gKi9cbiAgICB4OiBudW1iZXI7XG4gICAgLyoqIFRoZSB2ZXJ0aWNhbCBwb3NpdGlvbiBvZiB0aGUgd2luZG93LiAqL1xuICAgIHk6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBBIHJlY29yZCBkZXNjcmliaW5nIHRoZSBzaXplIG9mIGEgd2luZG93LlxuICovXG5pbnRlcmZhY2UgU2l6ZSB7XG4gICAgLyoqIFRoZSB3aWR0aCBvZiB0aGUgd2luZG93LiAqL1xuICAgIHdpZHRoOiBudW1iZXI7XG4gICAgLyoqIFRoZSBoZWlnaHQgb2YgdGhlIHdpbmRvdy4gKi9cbiAgICBoZWlnaHQ6IG51bWJlcjtcbn1cblxuLy8gUHJpdmF0ZSBmaWVsZCBuYW1lcy5cbmNvbnN0IGNhbGxlclN5bSA9IFN5bWJvbChcImNhbGxlclwiKTtcblxuY2xhc3MgV2luZG93IHtcbiAgICAvLyBQcml2YXRlIGZpZWxkcy5cbiAgICBwcml2YXRlIFtjYWxsZXJTeW1dOiAobWVzc2FnZTogbnVtYmVyLCBhcmdzPzogYW55KSA9PiBQcm9taXNlPGFueT47XG5cbiAgICAvKipcbiAgICAgKiBJbml0aWFsaXNlcyBhIHdpbmRvdyBvYmplY3Qgd2l0aCB0aGUgc3BlY2lmaWVkIG5hbWUuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEBwYXJhbSBuYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHRhcmdldCB3aW5kb3cuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IobmFtZTogc3RyaW5nID0gJycpIHtcbiAgICAgICAgdGhpc1tjYWxsZXJTeW1dID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5XaW5kb3csIG5hbWUpXG5cbiAgICAgICAgLy8gYmluZCBpbnN0YW5jZSBtZXRob2QgdG8gbWFrZSB0aGVtIGVhc2lseSB1c2FibGUgaW4gZXZlbnQgaGFuZGxlcnNcbiAgICAgICAgZm9yIChjb25zdCBtZXRob2Qgb2YgT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoV2luZG93LnByb3RvdHlwZSkpIHtcbiAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICBtZXRob2QgIT09IFwiY29uc3RydWN0b3JcIlxuICAgICAgICAgICAgICAgICYmIHR5cGVvZiAodGhpcyBhcyBhbnkpW21ldGhvZF0gPT09IFwiZnVuY3Rpb25cIlxuICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgKHRoaXMgYXMgYW55KVttZXRob2RdID0gKHRoaXMgYXMgYW55KVttZXRob2RdLmJpbmQodGhpcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXRzIHRoZSBzcGVjaWZpZWQgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIG5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgd2luZG93IHRvIGdldC5cbiAgICAgKiBAcmV0dXJucyBUaGUgY29ycmVzcG9uZGluZyB3aW5kb3cgb2JqZWN0LlxuICAgICAqL1xuICAgIEdldChuYW1lOiBzdHJpbmcpOiBXaW5kb3cge1xuICAgICAgICByZXR1cm4gbmV3IFdpbmRvdyhuYW1lKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgYWJzb2x1dGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBQb3NpdGlvbigpOiBQcm9taXNlPFBvc2l0aW9uPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUG9zaXRpb25NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENlbnRlcnMgdGhlIHdpbmRvdyBvbiB0aGUgc2NyZWVuLlxuICAgICAqL1xuICAgIENlbnRlcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShDZW50ZXJNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENsb3NlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIENsb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKENsb3NlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEaXNhYmxlcyBtaW4vbWF4IHNpemUgY29uc3RyYWludHMuXG4gICAgICovXG4gICAgRGlzYWJsZVNpemVDb25zdHJhaW50cygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShEaXNhYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBFbmFibGVzIG1pbi9tYXggc2l6ZSBjb25zdHJhaW50cy5cbiAgICAgKi9cbiAgICBFbmFibGVTaXplQ29uc3RyYWludHMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRW5hYmxlU2l6ZUNvbnN0cmFpbnRzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGb2N1c2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgRm9jdXMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRm9jdXNNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZvcmNlcyB0aGUgd2luZG93IHRvIHJlbG9hZCB0aGUgcGFnZSBhc3NldHMuXG4gICAgICovXG4gICAgRm9yY2VSZWxvYWQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRm9yY2VSZWxvYWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFN3aXRjaGVzIHRoZSB3aW5kb3cgdG8gZnVsbHNjcmVlbiBtb2RlLlxuICAgICAqL1xuICAgIEZ1bGxzY3JlZW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRnVsbHNjcmVlbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgc2NyZWVuIHRoYXQgdGhlIHdpbmRvdyBpcyBvbi5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBzY3JlZW4gdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgb24uXG4gICAgICovXG4gICAgR2V0U2NyZWVuKCk6IFByb21pc2U8U2NyZWVuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oR2V0U2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBjdXJyZW50IHpvb20gbGV2ZWwgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IHpvb20gbGV2ZWwuXG4gICAgICovXG4gICAgR2V0Wm9vbSgpOiBQcm9taXNlPG51bWJlcj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEdldFpvb21NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgSGVpZ2h0KCk6IFByb21pc2U8bnVtYmVyPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSGVpZ2h0TWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBIaWRlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIEhpZGUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSGlkZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0cnVlIGlmIHRoZSB3aW5kb3cgaXMgZm9jdXNlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgZm9jdXNlZC5cbiAgICAgKi9cbiAgICBJc0ZvY3VzZWQoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSXNGb2N1c2VkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyBmdWxsc2NyZWVuLlxuICAgICAqXG4gICAgICogQHJldHVybnMgV2hldGhlciB0aGUgd2luZG93IGlzIGN1cnJlbnRseSBmdWxsc2NyZWVuLlxuICAgICAqL1xuICAgIElzRnVsbHNjcmVlbigpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShJc0Z1bGxzY3JlZW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIG1heGltaXNlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgbWF4aW1pc2VkLlxuICAgICAqL1xuICAgIElzTWF4aW1pc2VkKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKElzTWF4aW1pc2VkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyBtaW5pbWlzZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBXaGV0aGVyIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IG1pbmltaXNlZC5cbiAgICAgKi9cbiAgICBJc01pbmltaXNlZCgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShJc01pbmltaXNlZE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWF4aW1pc2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgTWF4aW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oTWF4aW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE1pbmltaXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIE1pbmltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKE1pbmltaXNlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBuYW1lIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgbmFtZSBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIE5hbWUoKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShOYW1lTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBPcGVucyB0aGUgZGV2ZWxvcG1lbnQgdG9vbHMgcGFuZS5cbiAgICAgKi9cbiAgICBPcGVuRGV2VG9vbHMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oT3BlbkRldlRvb2xzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93IHRvIHRoZSBzY3JlZW4uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFJlbGF0aXZlUG9zaXRpb24oKTogUHJvbWlzZTxQb3NpdGlvbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFJlbGF0aXZlUG9zaXRpb25NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbG9hZHMgdGhlIHBhZ2UgYXNzZXRzLlxuICAgICAqL1xuICAgIFJlbG9hZCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZWxvYWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIHJlc2l6YWJsZS5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgcmVzaXphYmxlLlxuICAgICAqL1xuICAgIFJlc2l6YWJsZSgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZXNpemFibGVNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlc3RvcmVzIHRoZSB3aW5kb3cgdG8gaXRzIHByZXZpb3VzIHN0YXRlIGlmIGl0IHdhcyBwcmV2aW91c2x5IG1pbmltaXNlZCwgbWF4aW1pc2VkIG9yIGZ1bGxzY3JlZW4uXG4gICAgICovXG4gICAgUmVzdG9yZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZXN0b3JlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHggLSBUaGUgZGVzaXJlZCBob3Jpem9udGFsIGFic29sdXRlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIHkgLSBUaGUgZGVzaXJlZCB2ZXJ0aWNhbCBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldFBvc2l0aW9uKHg6IG51bWJlciwgeTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0UG9zaXRpb25NZXRob2QsIHsgeCwgeSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSB3aW5kb3cgdG8gYmUgYWx3YXlzIG9uIHRvcC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBhbHdheXNPblRvcCAtIFdoZXRoZXIgdGhlIHdpbmRvdyBzaG91bGQgc3RheSBvbiB0b3AuXG4gICAgICovXG4gICAgU2V0QWx3YXlzT25Ub3AoYWx3YXlzT25Ub3A6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRBbHdheXNPblRvcE1ldGhvZCwgeyBhbHdheXNPblRvcCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBiYWNrZ3JvdW5kIGNvbG91ciBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHIgLSBUaGUgZGVzaXJlZCByZWQgY29tcG9uZW50IG9mIHRoZSB3aW5kb3cgYmFja2dyb3VuZC5cbiAgICAgKiBAcGFyYW0gZyAtIFRoZSBkZXNpcmVkIGdyZWVuIGNvbXBvbmVudCBvZiB0aGUgd2luZG93IGJhY2tncm91bmQuXG4gICAgICogQHBhcmFtIGIgLSBUaGUgZGVzaXJlZCBibHVlIGNvbXBvbmVudCBvZiB0aGUgd2luZG93IGJhY2tncm91bmQuXG4gICAgICogQHBhcmFtIGEgLSBUaGUgZGVzaXJlZCBhbHBoYSBjb21wb25lbnQgb2YgdGhlIHdpbmRvdyBiYWNrZ3JvdW5kLlxuICAgICAqL1xuICAgIFNldEJhY2tncm91bmRDb2xvdXIocjogbnVtYmVyLCBnOiBudW1iZXIsIGI6IG51bWJlciwgYTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0QmFja2dyb3VuZENvbG91ck1ldGhvZCwgeyByLCBnLCBiLCBhIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgdGhlIHdpbmRvdyBmcmFtZSBhbmQgdGl0bGUgYmFyLlxuICAgICAqXG4gICAgICogQHBhcmFtIGZyYW1lbGVzcyAtIFdoZXRoZXIgdGhlIHdpbmRvdyBzaG91bGQgYmUgZnJhbWVsZXNzLlxuICAgICAqL1xuICAgIFNldEZyYW1lbGVzcyhmcmFtZWxlc3M6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRGcmFtZWxlc3NNZXRob2QsIHsgZnJhbWVsZXNzIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERpc2FibGVzIHRoZSBzeXN0ZW0gZnVsbHNjcmVlbiBidXR0b24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZW5hYmxlZCAtIFdoZXRoZXIgdGhlIGZ1bGxzY3JlZW4gYnV0dG9uIHNob3VsZCBiZSBlbmFibGVkLlxuICAgICAqL1xuICAgIFNldEZ1bGxzY3JlZW5CdXR0b25FbmFibGVkKGVuYWJsZWQ6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRGdWxsc2NyZWVuQnV0dG9uRW5hYmxlZE1ldGhvZCwgeyBlbmFibGVkIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIG1heGltdW0gc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHdpZHRoIC0gVGhlIGRlc2lyZWQgbWF4aW11bSB3aWR0aCBvZiB0aGUgd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgLSBUaGUgZGVzaXJlZCBtYXhpbXVtIGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldE1heFNpemUod2lkdGg6IG51bWJlciwgaGVpZ2h0OiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRNYXhTaXplTWV0aG9kLCB7IHdpZHRoLCBoZWlnaHQgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgbWluaW11bSBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gd2lkdGggLSBUaGUgZGVzaXJlZCBtaW5pbXVtIHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIGhlaWdodCAtIFRoZSBkZXNpcmVkIG1pbmltdW0gaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0TWluU2l6ZSh3aWR0aDogbnVtYmVyLCBoZWlnaHQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldE1pblNpemVNZXRob2QsIHsgd2lkdGgsIGhlaWdodCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93IHRvIHRoZSBzY3JlZW4uXG4gICAgICpcbiAgICAgKiBAcGFyYW0geCAtIFRoZSBkZXNpcmVkIGhvcml6b250YWwgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0geSAtIFRoZSBkZXNpcmVkIHZlcnRpY2FsIHJlbGF0aXZlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0UmVsYXRpdmVQb3NpdGlvbih4OiBudW1iZXIsIHk6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFJlbGF0aXZlUG9zaXRpb25NZXRob2QsIHsgeCwgeSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyByZXNpemFibGUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gcmVzaXphYmxlIC0gV2hldGhlciB0aGUgd2luZG93IHNob3VsZCBiZSByZXNpemFibGUuXG4gICAgICovXG4gICAgU2V0UmVzaXphYmxlKHJlc2l6YWJsZTogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFJlc2l6YWJsZU1ldGhvZCwgeyByZXNpemFibGUgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHdpZHRoIC0gVGhlIGRlc2lyZWQgd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0gaGVpZ2h0IC0gVGhlIGRlc2lyZWQgaGVpZ2h0IG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0U2l6ZSh3aWR0aDogbnVtYmVyLCBoZWlnaHQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFNpemVNZXRob2QsIHsgd2lkdGgsIGhlaWdodCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSB0aXRsZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHRpdGxlIC0gVGhlIGRlc2lyZWQgdGl0bGUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRUaXRsZSh0aXRsZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0VGl0bGVNZXRob2QsIHsgdGl0bGUgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHpvb20gLSBUaGUgZGVzaXJlZCB6b29tIGxldmVsLlxuICAgICAqL1xuICAgIFNldFpvb20oem9vbTogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0Wm9vbU1ldGhvZCwgeyB6b29tIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNob3dzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2hvdygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTaG93TWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCBzaXplIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2l6ZSgpOiBQcm9taXNlPFNpemU+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTaXplTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUb2dnbGVzIHRoZSB3aW5kb3cgYmV0d2VlbiBmdWxsc2NyZWVuIGFuZCBub3JtYWwuXG4gICAgICovXG4gICAgVG9nZ2xlRnVsbHNjcmVlbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShUb2dnbGVGdWxsc2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUb2dnbGVzIHRoZSB3aW5kb3cgYmV0d2VlbiBtYXhpbWlzZWQgYW5kIG5vcm1hbC5cbiAgICAgKi9cbiAgICBUb2dnbGVNYXhpbWlzZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShUb2dnbGVNYXhpbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVG9nZ2xlcyB0aGUgd2luZG93IGJldHdlZW4gZnJhbWVsZXNzIGFuZCBub3JtYWwuXG4gICAgICovXG4gICAgVG9nZ2xlRnJhbWVsZXNzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFRvZ2dsZUZyYW1lbGVzc01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVW4tZnVsbHNjcmVlbnMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBVbkZ1bGxzY3JlZW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVW5GdWxsc2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBVbi1tYXhpbWlzZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBVbk1heGltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFVuTWF4aW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVuLW1pbmltaXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFVuTWluaW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVW5NaW5pbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgV2lkdGgoKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShXaWR0aE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogWm9vbXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBab29tKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFpvb21NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEluY3JlYXNlcyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2VidmlldyBjb250ZW50LlxuICAgICAqL1xuICAgIFpvb21JbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShab29tSW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERlY3JlYXNlcyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2VidmlldyBjb250ZW50LlxuICAgICAqL1xuICAgIFpvb21PdXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oWm9vbU91dE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVzZXRzIHRoZSB6b29tIGxldmVsIG9mIHRoZSB3ZWJ2aWV3IGNvbnRlbnQuXG4gICAgICovXG4gICAgWm9vbVJlc2V0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFpvb21SZXNldE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSGFuZGxlcyBmaWxlIGRyb3BzIG9yaWdpbmF0aW5nIGZyb20gcGxhdGZvcm0tc3BlY2lmaWMgY29kZSAoZS5nLiwgbWFjT1MvTGludXggbmF0aXZlIGRyYWctYW5kLWRyb3ApLlxuICAgICAqIEdhdGhlcnMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRyb3AgdGFyZ2V0IGVsZW1lbnQgYW5kIHNlbmRzIGl0IGJhY2sgdG8gdGhlIEdvIGJhY2tlbmQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gZmlsZW5hbWVzIC0gQW4gYXJyYXkgb2YgZmlsZSBwYXRocyAoc3RyaW5ncykgdGhhdCB3ZXJlIGRyb3BwZWQuXG4gICAgICogQHBhcmFtIHggLSBUaGUgeC1jb29yZGluYXRlIG9mIHRoZSBkcm9wIGV2ZW50IChDU1MgcGl4ZWxzKS5cbiAgICAgKiBAcGFyYW0geSAtIFRoZSB5LWNvb3JkaW5hdGUgb2YgdGhlIGRyb3AgZXZlbnQgKENTUyBwaXhlbHMpLlxuICAgICAqL1xuICAgIEhhbmRsZVBsYXRmb3JtRmlsZURyb3AoZmlsZW5hbWVzOiBzdHJpbmdbXSwgeDogbnVtYmVyLCB5OiBudW1iZXIpOiB2b2lkIHtcbiAgICAgICAgLy8gQ2hlY2sgaWYgZmlsZSBkcm9wcyBhcmUgZW5hYmxlZCBmb3IgdGhpcyB3aW5kb3dcbiAgICAgICAgaWYgKCh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmZsYWdzPy5lbmFibGVGaWxlRHJvcCA9PT0gZmFsc2UpIHtcbiAgICAgICAgICAgIHJldHVybjsgLy8gRmlsZSBkcm9wcyBkaXNhYmxlZCwgaWdub3JlIHRoZSBkcm9wXG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHgsIHkpO1xuICAgICAgICBjb25zdCBkcm9wVGFyZ2V0ID0gZ2V0RHJvcFRhcmdldEVsZW1lbnQoZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKCFkcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICAvLyBEcm9wIHdhcyBub3Qgb24gYSBkZXNpZ25hdGVkIGRyb3AgdGFyZ2V0IC0gaWdub3JlXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBlbGVtZW50RGV0YWlscyA9IHtcbiAgICAgICAgICAgIGlkOiBkcm9wVGFyZ2V0LmlkLFxuICAgICAgICAgICAgY2xhc3NMaXN0OiBBcnJheS5mcm9tKGRyb3BUYXJnZXQuY2xhc3NMaXN0KSxcbiAgICAgICAgICAgIGF0dHJpYnV0ZXM6IHt9IGFzIHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH0sXG4gICAgICAgIH07XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZHJvcFRhcmdldC5hdHRyaWJ1dGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBhdHRyID0gZHJvcFRhcmdldC5hdHRyaWJ1dGVzW2ldO1xuICAgICAgICAgICAgZWxlbWVudERldGFpbHMuYXR0cmlidXRlc1thdHRyLm5hbWVdID0gYXR0ci52YWx1ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICBmaWxlbmFtZXMsXG4gICAgICAgICAgICB4LFxuICAgICAgICAgICAgeSxcbiAgICAgICAgICAgIGVsZW1lbnREZXRhaWxzLFxuICAgICAgICB9O1xuXG4gICAgICAgIHRoaXNbY2FsbGVyU3ltXShGaWxlc0Ryb3BwZWQsIHBheWxvYWQpO1xuICAgICAgICBcbiAgICAgICAgLy8gQ2xlYW4gdXAgbmF0aXZlIGRyYWcgc3RhdGUgYWZ0ZXIgZHJvcFxuICAgICAgICBjbGVhbnVwTmF0aXZlRHJhZygpO1xuICAgIH1cbiAgXG4gICAgLyogVHJpZ2dlcnMgV2luZG93cyAxMSBTbmFwIEFzc2lzdCBmZWF0dXJlIChXaW5kb3dzIG9ubHkpLlxuICAgICAqIFRoaXMgaXMgZXF1aXZhbGVudCB0byBwcmVzc2luZyBXaW4rWiBhbmQgc2hvd3Mgc25hcCBsYXlvdXQgb3B0aW9ucy5cbiAgICAgKi9cbiAgICBTbmFwQXNzaXN0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNuYXBBc3Npc3RNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE9wZW5zIHRoZSBwcmludCBkaWFsb2cgZm9yIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgUHJpbnQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUHJpbnRNZXRob2QpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBUaGUgd2luZG93IHdpdGhpbiB3aGljaCB0aGUgc2NyaXB0IGlzIHJ1bm5pbmcuXG4gKi9cbmNvbnN0IHRoaXNXaW5kb3cgPSBuZXcgV2luZG93KCcnKTtcblxuLyoqXG4gKiBTZXRzIHVwIGdsb2JhbCBkcmFnIGFuZCBkcm9wIGV2ZW50IGxpc3RlbmVycyBmb3IgZmlsZSBkcm9wcy5cbiAqIEhhbmRsZXMgdmlzdWFsIGZlZWRiYWNrIChob3ZlciBzdGF0ZSkgYW5kIGZpbGUgZHJvcCBwcm9jZXNzaW5nLlxuICovXG5mdW5jdGlvbiBzZXR1cERyb3BUYXJnZXRMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3QgZG9jRWxlbWVudCA9IGRvY3VtZW50LmRvY3VtZW50RWxlbWVudDtcbiAgICBsZXQgZHJhZ0VudGVyQ291bnRlciA9IDA7XG5cbiAgICBkb2NFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2RyYWdlbnRlcicsIChldmVudCkgPT4ge1xuICAgICAgICBpZiAoIWV2ZW50LmRhdGFUcmFuc2Zlcj8udHlwZXMuaW5jbHVkZXMoJ0ZpbGVzJykpIHtcbiAgICAgICAgICAgIHJldHVybjsgLy8gT25seSBoYW5kbGUgZmlsZSBkcmFncywgbGV0IG90aGVyIGRyYWdzIHBhc3MgdGhyb3VnaFxuICAgICAgICB9XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7IC8vIEFsd2F5cyBwcmV2ZW50IGRlZmF1bHQgdG8gc3RvcCBicm93c2VyIG5hdmlnYXRpb25cbiAgICAgICAgLy8gT24gV2luZG93cywgY2hlY2sgaWYgZmlsZSBkcm9wcyBhcmUgZW5hYmxlZCBmb3IgdGhpcyB3aW5kb3dcbiAgICAgICAgaWYgKCh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmZsYWdzPy5lbmFibGVGaWxlRHJvcCA9PT0gZmFsc2UpIHtcbiAgICAgICAgICAgIGV2ZW50LmRhdGFUcmFuc2Zlci5kcm9wRWZmZWN0ID0gJ25vbmUnOyAvLyBTaG93IFwibm8gZHJvcFwiIGN1cnNvclxuICAgICAgICAgICAgcmV0dXJuOyAvLyBGaWxlIGRyb3BzIGRpc2FibGVkLCBkb24ndCBzaG93IGhvdmVyIGVmZmVjdHNcbiAgICAgICAgfVxuICAgICAgICBkcmFnRW50ZXJDb3VudGVyKys7XG4gICAgICAgIFxuICAgICAgICBjb25zdCB0YXJnZXRFbGVtZW50ID0gZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZKTtcbiAgICAgICAgY29uc3QgZHJvcFRhcmdldCA9IGdldERyb3BUYXJnZXRFbGVtZW50KHRhcmdldEVsZW1lbnQpO1xuXG4gICAgICAgIC8vIFVwZGF0ZSBob3ZlciBzdGF0ZVxuICAgICAgICBpZiAoY3VycmVudERyb3BUYXJnZXQgJiYgY3VycmVudERyb3BUYXJnZXQgIT09IGRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0LmNsYXNzTGlzdC5yZW1vdmUoRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChkcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICBkcm9wVGFyZ2V0LmNsYXNzTGlzdC5hZGQoRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTKTtcbiAgICAgICAgICAgIGV2ZW50LmRhdGFUcmFuc2Zlci5kcm9wRWZmZWN0ID0gJ2NvcHknO1xuICAgICAgICAgICAgY3VycmVudERyb3BUYXJnZXQgPSBkcm9wVGFyZ2V0O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnbm9uZSc7XG4gICAgICAgICAgICBjdXJyZW50RHJvcFRhcmdldCA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9LCBmYWxzZSk7XG5cbiAgICBkb2NFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2RyYWdvdmVyJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGlmICghZXZlbnQuZGF0YVRyYW5zZmVyPy50eXBlcy5pbmNsdWRlcygnRmlsZXMnKSkge1xuICAgICAgICAgICAgcmV0dXJuOyAvLyBPbmx5IGhhbmRsZSBmaWxlIGRyYWdzXG4gICAgICAgIH1cbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTsgLy8gQWx3YXlzIHByZXZlbnQgZGVmYXVsdCB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxuICAgICAgICAvLyBPbiBXaW5kb3dzLCBjaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgICAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZmxhZ3M/LmVuYWJsZUZpbGVEcm9wID09PSBmYWxzZSkge1xuICAgICAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnbm9uZSc7IC8vIFNob3cgXCJubyBkcm9wXCIgY3Vyc29yXG4gICAgICAgICAgICByZXR1cm47IC8vIEZpbGUgZHJvcHMgZGlzYWJsZWQsIGRvbid0IHNob3cgaG92ZXIgZWZmZWN0c1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBVcGRhdGUgZHJvcCB0YXJnZXQgYXMgY3Vyc29yIG1vdmVzXG4gICAgICAgIGNvbnN0IHRhcmdldEVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KGV2ZW50LmNsaWVudFgsIGV2ZW50LmNsaWVudFkpO1xuICAgICAgICBjb25zdCBkcm9wVGFyZ2V0ID0gZ2V0RHJvcFRhcmdldEVsZW1lbnQodGFyZ2V0RWxlbWVudCk7XG4gICAgICAgIFxuICAgICAgICBpZiAoY3VycmVudERyb3BUYXJnZXQgJiYgY3VycmVudERyb3BUYXJnZXQgIT09IGRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0LmNsYXNzTGlzdC5yZW1vdmUoRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTKTtcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgaWYgKGRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgIGlmICghZHJvcFRhcmdldC5jbGFzc0xpc3QuY29udGFpbnMoRFJPUF9UQVJHRVRfQUNUSVZFX0NMQVNTKSkge1xuICAgICAgICAgICAgICAgIGRyb3BUYXJnZXQuY2xhc3NMaXN0LmFkZChEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnY29weSc7XG4gICAgICAgICAgICBjdXJyZW50RHJvcFRhcmdldCA9IGRyb3BUYXJnZXQ7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdub25lJztcbiAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gbnVsbDtcbiAgICAgICAgfVxuICAgIH0sIGZhbHNlKTtcblxuICAgIGRvY0VsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignZHJhZ2xlYXZlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGlmICghZXZlbnQuZGF0YVRyYW5zZmVyPy50eXBlcy5pbmNsdWRlcygnRmlsZXMnKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7IC8vIEFsd2F5cyBwcmV2ZW50IGRlZmF1bHQgdG8gc3RvcCBicm93c2VyIG5hdmlnYXRpb25cbiAgICAgICAgLy8gT24gV2luZG93cywgY2hlY2sgaWYgZmlsZSBkcm9wcyBhcmUgZW5hYmxlZCBmb3IgdGhpcyB3aW5kb3dcbiAgICAgICAgaWYgKCh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmZsYWdzPy5lbmFibGVGaWxlRHJvcCA9PT0gZmFsc2UpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgLy8gT24gTGludXgvV2ViS2l0R1RLIGFuZCBtYWNPUywgZHJhZ2xlYXZlIGZpcmVzIGltbWVkaWF0ZWx5IHdpdGggcmVsYXRlZFRhcmdldD1udWxsIHdoZW4gbmF0aXZlXG4gICAgICAgIC8vIGRyYWcgaGFuZGxpbmcgaXMgaW52b2x2ZWQuIElnbm9yZSB0aGVzZSBzcHVyaW91cyBldmVudHMgLSB3ZSdsbCBjbGVhbiB1cCBvbiBkcm9wIGluc3RlYWQuXG4gICAgICAgIGlmIChldmVudC5yZWxhdGVkVGFyZ2V0ID09PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIGRyYWdFbnRlckNvdW50ZXItLTtcbiAgICAgICAgXG4gICAgICAgIGlmIChkcmFnRW50ZXJDb3VudGVyID09PSAwIHx8IFxuICAgICAgICAgICAgKGN1cnJlbnREcm9wVGFyZ2V0ICYmICFjdXJyZW50RHJvcFRhcmdldC5jb250YWlucyhldmVudC5yZWxhdGVkVGFyZ2V0IGFzIE5vZGUpKSkge1xuICAgICAgICAgICAgaWYgKGN1cnJlbnREcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgY3VycmVudERyb3BUYXJnZXQuY2xhc3NMaXN0LnJlbW92ZShEUk9QX1RBUkdFVF9BQ1RJVkVfQ0xBU1MpO1xuICAgICAgICAgICAgICAgIGN1cnJlbnREcm9wVGFyZ2V0ID0gbnVsbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGRyYWdFbnRlckNvdW50ZXIgPSAwO1xuICAgICAgICB9XG4gICAgfSwgZmFsc2UpO1xuXG4gICAgZG9jRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCdkcm9wJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGlmICghZXZlbnQuZGF0YVRyYW5zZmVyPy50eXBlcy5pbmNsdWRlcygnRmlsZXMnKSkge1xuICAgICAgICAgICAgcmV0dXJuOyAvLyBPbmx5IGhhbmRsZSBmaWxlIGRyb3BzXG4gICAgICAgIH1cbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTsgLy8gQWx3YXlzIHByZXZlbnQgZGVmYXVsdCB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxuICAgICAgICAvLyBPbiBXaW5kb3dzLCBjaGVjayBpZiBmaWxlIGRyb3BzIGFyZSBlbmFibGVkIGZvciB0aGlzIHdpbmRvd1xuICAgICAgICBpZiAoKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZmxhZ3M/LmVuYWJsZUZpbGVEcm9wID09PSBmYWxzZSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGRyYWdFbnRlckNvdW50ZXIgPSAwO1xuICAgICAgICBcbiAgICAgICAgaWYgKGN1cnJlbnREcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICBjdXJyZW50RHJvcFRhcmdldC5jbGFzc0xpc3QucmVtb3ZlKERST1BfVEFSR0VUX0FDVElWRV9DTEFTUyk7XG4gICAgICAgICAgICBjdXJyZW50RHJvcFRhcmdldCA9IG51bGw7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBPbiBXaW5kb3dzLCBoYW5kbGUgZmlsZSBkcm9wcyB2aWEgSmF2YVNjcmlwdFxuICAgICAgICAvLyBPbiBtYWNPUy9MaW51eCwgbmF0aXZlIGNvZGUgd2lsbCBjYWxsIEhhbmRsZVBsYXRmb3JtRmlsZURyb3BcbiAgICAgICAgaWYgKGNhblJlc29sdmVGaWxlUGF0aHMoKSkge1xuICAgICAgICAgICAgY29uc3QgZmlsZXM6IEZpbGVbXSA9IFtdO1xuICAgICAgICAgICAgaWYgKGV2ZW50LmRhdGFUcmFuc2Zlci5pdGVtcykge1xuICAgICAgICAgICAgICAgIGZvciAoY29uc3QgaXRlbSBvZiBldmVudC5kYXRhVHJhbnNmZXIuaXRlbXMpIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGl0ZW0ua2luZCA9PT0gJ2ZpbGUnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBmaWxlID0gaXRlbS5nZXRBc0ZpbGUoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChmaWxlKSBmaWxlcy5wdXNoKGZpbGUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIGlmIChldmVudC5kYXRhVHJhbnNmZXIuZmlsZXMpIHtcbiAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZXZlbnQuZGF0YVRyYW5zZmVyLmZpbGVzKSB7XG4gICAgICAgICAgICAgICAgICAgIGZpbGVzLnB1c2goZmlsZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgXG4gICAgICAgICAgICBpZiAoZmlsZXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgICAgIHJlc29sdmVGaWxlUGF0aHMoZXZlbnQuY2xpZW50WCwgZXZlbnQuY2xpZW50WSwgZmlsZXMpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSwgZmFsc2UpO1xufVxuXG4vLyBJbml0aWFsaXplIGxpc3RlbmVycyB3aGVuIHRoZSBzY3JpcHQgbG9hZHNcbmlmICh0eXBlb2Ygd2luZG93ICE9PSBcInVuZGVmaW5lZFwiICYmIHR5cGVvZiBkb2N1bWVudCAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgIHNldHVwRHJvcFRhcmdldExpc3RlbmVycygpO1xufVxuXG5leHBvcnQgZGVmYXVsdCB0aGlzV2luZG93O1xuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgKiBhcyBSdW50aW1lIGZyb20gXCIuLi9Ad2FpbHNpby9ydW50aW1lL3NyY1wiO1xuXG4vLyBOT1RFOiB0aGUgZm9sbG93aW5nIG1ldGhvZHMgTVVTVCBiZSBpbXBvcnRlZCBleHBsaWNpdGx5IGJlY2F1c2Ugb2YgaG93IGVzYnVpbGQgaW5qZWN0aW9uIHdvcmtzXG5pbXBvcnQgeyBFbmFibGUgYXMgRW5hYmxlV01MIH0gZnJvbSBcIi4uL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3dtbFwiO1xuaW1wb3J0IHsgZGVidWdMb2cgfSBmcm9tIFwiLi4vQHdhaWxzaW8vcnVudGltZS9zcmMvdXRpbHNcIjtcblxud2luZG93LndhaWxzID0gUnVudGltZTtcbkVuYWJsZVdNTCgpO1xuXG5pZiAoREVCVUcpIHtcbiAgICBkZWJ1Z0xvZyhcIldhaWxzIFJ1bnRpbWUgTG9hZGVkXCIpXG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5TeXN0ZW0pO1xuXG5jb25zdCBTeXN0ZW1Jc0RhcmtNb2RlID0gMDtcbmNvbnN0IFN5c3RlbUVudmlyb25tZW50ID0gMTtcbmNvbnN0IFN5c3RlbUNhcGFiaWxpdGllcyA9IDI7XG5cbmNvbnN0IF9pbnZva2UgPSAoZnVuY3Rpb24gKCkge1xuICAgIHRyeSB7XG4gICAgICAgIC8vIFdpbmRvd3MgV2ViVmlldzJcbiAgICAgICAgaWYgKCh3aW5kb3cgYXMgYW55KS5jaHJvbWU/LndlYnZpZXc/LnBvc3RNZXNzYWdlKSB7XG4gICAgICAgICAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLmNocm9tZS53ZWJ2aWV3LnBvc3RNZXNzYWdlLmJpbmQoKHdpbmRvdyBhcyBhbnkpLmNocm9tZS53ZWJ2aWV3KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBtYWNPUy9pT1MgV0tXZWJWaWV3XG4gICAgICAgIGVsc2UgaWYgKCh3aW5kb3cgYXMgYW55KS53ZWJraXQ/Lm1lc3NhZ2VIYW5kbGVycz8uWydleHRlcm5hbCddPy5wb3N0TWVzc2FnZSkge1xuICAgICAgICAgICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS53ZWJraXQubWVzc2FnZUhhbmRsZXJzWydleHRlcm5hbCddLnBvc3RNZXNzYWdlLmJpbmQoKHdpbmRvdyBhcyBhbnkpLndlYmtpdC5tZXNzYWdlSGFuZGxlcnNbJ2V4dGVybmFsJ10pO1xuICAgICAgICB9XG4gICAgICAgIC8vIEFuZHJvaWQgV2ViVmlldyAtIHVzZXMgYWRkSmF2YXNjcmlwdEludGVyZmFjZSB3aGljaCBleHBvc2VzIHdpbmRvdy53YWlscy5pbnZva2VcbiAgICAgICAgZWxzZSBpZiAoKHdpbmRvdyBhcyBhbnkpLndhaWxzPy5pbnZva2UpIHtcbiAgICAgICAgICAgIHJldHVybiAobXNnOiBhbnkpID0+ICh3aW5kb3cgYXMgYW55KS53YWlscy5pbnZva2UodHlwZW9mIG1zZyA9PT0gJ3N0cmluZycgPyBtc2cgOiBKU09OLnN0cmluZ2lmeShtc2cpKTtcbiAgICAgICAgfVxuICAgIH0gY2F0Y2goZSkge31cblxuICAgIGNvbnNvbGUud2FybignXFxuJWNcdTI2QTBcdUZFMEYgQnJvd3NlciBFbnZpcm9ubWVudCBEZXRlY3RlZCAlY1xcblxcbiVjT25seSBVSSBwcmV2aWV3cyBhcmUgYXZhaWxhYmxlIGluIHRoZSBicm93c2VyLiBGb3IgZnVsbCBmdW5jdGlvbmFsaXR5LCBwbGVhc2UgcnVuIHRoZSBhcHBsaWNhdGlvbiBpbiBkZXNrdG9wIG1vZGUuXFxuTW9yZSBpbmZvcm1hdGlvbiBhdDogaHR0cHM6Ly92My53YWlscy5pby9sZWFybi9idWlsZC8jdXNpbmctYS1icm93c2VyLWZvci1kZXZlbG9wbWVudFxcbicsXG4gICAgICAgICdiYWNrZ3JvdW5kOiAjZmZmZmZmOyBjb2xvcjogIzAwMDAwMDsgZm9udC13ZWlnaHQ6IGJvbGQ7IHBhZGRpbmc6IDRweCA4cHg7IGJvcmRlci1yYWRpdXM6IDRweDsgYm9yZGVyOiAycHggc29saWQgIzAwMDAwMDsnLFxuICAgICAgICAnYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7JyxcbiAgICAgICAgJ2NvbG9yOiAjZmZmZmZmOyBmb250LXN0eWxlOiBpdGFsaWM7IGZvbnQtd2VpZ2h0OiBib2xkOycpO1xuICAgIHJldHVybiBudWxsO1xufSkoKTtcblxuZXhwb3J0IGZ1bmN0aW9uIGludm9rZShtc2c6IGFueSk6IHZvaWQge1xuICAgIF9pbnZva2U/Lihtc2cpO1xufVxuXG4vKipcbiAqIFJldHJpZXZlcyB0aGUgc3lzdGVtIGRhcmsgbW9kZSBzdGF0dXMuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gYSBib29sZWFuIHZhbHVlIGluZGljYXRpbmcgaWYgdGhlIHN5c3RlbSBpcyBpbiBkYXJrIG1vZGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0RhcmtNb2RlKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgIHJldHVybiBjYWxsKFN5c3RlbUlzRGFya01vZGUpO1xufVxuXG4vKipcbiAqIEZldGNoZXMgdGhlIGNhcGFiaWxpdGllcyBvZiB0aGUgYXBwbGljYXRpb24gZnJvbSB0aGUgc2VydmVyLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGFuIG9iamVjdCBjb250YWluaW5nIHRoZSBjYXBhYmlsaXRpZXMuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBDYXBhYmlsaXRpZXMoKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBhbnk+PiB7XG4gICAgcmV0dXJuIGNhbGwoU3lzdGVtQ2FwYWJpbGl0aWVzKTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBPU0luZm8ge1xuICAgIC8qKiBUaGUgYnJhbmRpbmcgb2YgdGhlIE9TLiAqL1xuICAgIEJyYW5kaW5nOiBzdHJpbmc7XG4gICAgLyoqIFRoZSBJRCBvZiB0aGUgT1MuICovXG4gICAgSUQ6IHN0cmluZztcbiAgICAvKiogVGhlIG5hbWUgb2YgdGhlIE9TLiAqL1xuICAgIE5hbWU6IHN0cmluZztcbiAgICAvKiogVGhlIHZlcnNpb24gb2YgdGhlIE9TLiAqL1xuICAgIFZlcnNpb246IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBFbnZpcm9ubWVudEluZm8ge1xuICAgIC8qKiBUaGUgYXJjaGl0ZWN0dXJlIG9mIHRoZSBzeXN0ZW0uICovXG4gICAgQXJjaDogc3RyaW5nO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBhcHBsaWNhdGlvbiBpcyBydW5uaW5nIGluIGRlYnVnIG1vZGUsIG90aGVyd2lzZSBmYWxzZS4gKi9cbiAgICBEZWJ1ZzogYm9vbGVhbjtcbiAgICAvKiogVGhlIG9wZXJhdGluZyBzeXN0ZW0gaW4gdXNlLiAqL1xuICAgIE9TOiBzdHJpbmc7XG4gICAgLyoqIERldGFpbHMgb2YgdGhlIG9wZXJhdGluZyBzeXN0ZW0uICovXG4gICAgT1NJbmZvOiBPU0luZm87XG4gICAgLyoqIEFkZGl0aW9uYWwgcGxhdGZvcm0gaW5mb3JtYXRpb24uICovXG4gICAgUGxhdGZvcm1JbmZvOiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xufVxuXG4vKipcbiAqIFJldHJpZXZlcyBlbnZpcm9ubWVudCBkZXRhaWxzLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGFuIG9iamVjdCBjb250YWluaW5nIE9TIGFuZCBzeXN0ZW0gYXJjaGl0ZWN0dXJlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gRW52aXJvbm1lbnQoKTogUHJvbWlzZTxFbnZpcm9ubWVudEluZm8+IHtcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1FbnZpcm9ubWVudCk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IG9wZXJhdGluZyBzeXN0ZW0gaXMgV2luZG93cy5cbiAqXG4gKiBAcmV0dXJuIFRydWUgaWYgdGhlIG9wZXJhdGluZyBzeXN0ZW0gaXMgV2luZG93cywgb3RoZXJ3aXNlIGZhbHNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNXaW5kb3dzKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uT1MgPT09IFwid2luZG93c1wiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBvcGVyYXRpbmcgc3lzdGVtIGlzIExpbnV4LlxuICpcbiAqIEByZXR1cm5zIFJldHVybnMgdHJ1ZSBpZiB0aGUgY3VycmVudCBvcGVyYXRpbmcgc3lzdGVtIGlzIExpbnV4LCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0xpbnV4KCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uT1MgPT09IFwibGludXhcIjtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgZW52aXJvbm1lbnQgaXMgYSBtYWNPUyBvcGVyYXRpbmcgc3lzdGVtLlxuICpcbiAqIEByZXR1cm5zIFRydWUgaWYgdGhlIGVudmlyb25tZW50IGlzIG1hY09TLCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc01hYygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/Lk9TID09PSBcImRhcndpblwiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBhcmNoaXRlY3R1cmUgaXMgQU1ENjQuXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBhcmNoaXRlY3R1cmUgaXMgQU1ENjQsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzQU1ENjQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5fd2FpbHM/LmVudmlyb25tZW50Py5BcmNoID09PSBcImFtZDY0XCI7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IGFyY2hpdGVjdHVyZSBpcyBBUk0uXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgY3VycmVudCBhcmNoaXRlY3R1cmUgaXMgQVJNLCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0FSTSgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/LkFyY2ggPT09IFwiYXJtXCI7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IGVudmlyb25tZW50IGlzIEFSTTY0IGFyY2hpdGVjdHVyZS5cbiAqXG4gKiBAcmV0dXJucyBSZXR1cm5zIHRydWUgaWYgdGhlIGVudmlyb25tZW50IGlzIEFSTTY0IGFyY2hpdGVjdHVyZSwgb3RoZXJ3aXNlIHJldHVybnMgZmFsc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc0FSTTY0KCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uQXJjaCA9PT0gXCJhcm02NFwiO1xufVxuXG4vKipcbiAqIFJlcG9ydHMgd2hldGhlciB0aGUgYXBwIGlzIGJlaW5nIHJ1biBpbiBkZWJ1ZyBtb2RlLlxuICpcbiAqIEByZXR1cm5zIFRydWUgaWYgdGhlIGFwcCBpcyBiZWluZyBydW4gaW4gZGVidWcgbW9kZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzRGVidWcoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIEJvb2xlYW4oKHdpbmRvdyBhcyBhbnkpLl93YWlscz8uZW52aXJvbm1lbnQ/LkRlYnVnKTtcbn1cblxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lcyB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcbmltcG9ydCB7IElzRGVidWcgfSBmcm9tIFwiLi9zeXN0ZW0uanNcIjtcbmltcG9ydCB7IGV2ZW50VGFyZ2V0IH0gZnJvbSBcIi4vdXRpbHMuanNcIjtcblxuLy8gc2V0dXBcbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdjb250ZXh0bWVudScsIGNvbnRleHRNZW51SGFuZGxlcik7XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkNvbnRleHRNZW51KTtcblxuY29uc3QgQ29udGV4dE1lbnVPcGVuID0gMDtcblxuZnVuY3Rpb24gb3BlbkNvbnRleHRNZW51KGlkOiBzdHJpbmcsIHg6IG51bWJlciwgeTogbnVtYmVyLCBkYXRhOiBhbnkpOiB2b2lkIHtcbiAgICB2b2lkIGNhbGwoQ29udGV4dE1lbnVPcGVuLCB7aWQsIHgsIHksIGRhdGF9KTtcbn1cblxuZnVuY3Rpb24gY29udGV4dE1lbnVIYW5kbGVyKGV2ZW50OiBNb3VzZUV2ZW50KSB7XG4gICAgY29uc3QgdGFyZ2V0ID0gZXZlbnRUYXJnZXQoZXZlbnQpO1xuXG4gICAgLy8gQ2hlY2sgZm9yIGN1c3RvbSBjb250ZXh0IG1lbnVcbiAgICBjb25zdCBjdXN0b21Db250ZXh0TWVudSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRhcmdldCkuZ2V0UHJvcGVydHlWYWx1ZShcIi0tY3VzdG9tLWNvbnRleHRtZW51XCIpLnRyaW0oKTtcblxuICAgIGlmIChjdXN0b21Db250ZXh0TWVudSkge1xuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICBjb25zdCBkYXRhID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUodGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1jdXN0b20tY29udGV4dG1lbnUtZGF0YVwiKTtcbiAgICAgICAgb3BlbkNvbnRleHRNZW51KGN1c3RvbUNvbnRleHRNZW51LCBldmVudC5jbGllbnRYLCBldmVudC5jbGllbnRZLCBkYXRhKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBwcm9jZXNzRGVmYXVsdENvbnRleHRNZW51KGV2ZW50LCB0YXJnZXQpO1xuICAgIH1cbn1cblxuXG4vKlxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBhdXRvOyAoZGVmYXVsdCkgd2lsbCBzaG93IHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudSBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZSBPUiB0ZXh0IGhhcyBiZWVuIHNlbGVjdGVkIE9SIGVsZW1lbnQgaXMgaW5wdXQgb3IgdGV4dGFyZWFcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogc2hvdzsgd2lsbCBhbHdheXMgc2hvdyB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogaGlkZTsgd2lsbCBhbHdheXMgaGlkZSB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcblxuVGhpcyBydWxlIGlzIGluaGVyaXRlZCBsaWtlIG5vcm1hbCBDU1MgcnVsZXMsIHNvIG5lc3Rpbmcgd29ya3MgYXMgZXhwZWN0ZWRcbiovXG5mdW5jdGlvbiBwcm9jZXNzRGVmYXVsdENvbnRleHRNZW51KGV2ZW50OiBNb3VzZUV2ZW50LCB0YXJnZXQ6IEhUTUxFbGVtZW50KSB7XG4gICAgLy8gRGVidWcgYnVpbGRzIGFsd2F5cyBzaG93IHRoZSBtZW51XG4gICAgaWYgKElzRGVidWcoKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gUHJvY2VzcyBkZWZhdWx0IGNvbnRleHQgbWVudVxuICAgIHN3aXRjaCAod2luZG93LmdldENvbXB1dGVkU3R5bGUodGFyZ2V0KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1kZWZhdWx0LWNvbnRleHRtZW51XCIpLnRyaW0oKSkge1xuICAgICAgICBjYXNlICdzaG93JzpcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY2FzZSAnaGlkZSc6XG4gICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGlmIGNvbnRlbnRFZGl0YWJsZSBpcyB0cnVlXG4gICAgaWYgKHRhcmdldC5pc0NvbnRlbnRFZGl0YWJsZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgaWYgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZFxuICAgIGNvbnN0IHNlbGVjdGlvbiA9IHdpbmRvdy5nZXRTZWxlY3Rpb24oKTtcbiAgICBjb25zdCBoYXNTZWxlY3Rpb24gPSBzZWxlY3Rpb24gJiYgc2VsZWN0aW9uLnRvU3RyaW5nKCkubGVuZ3RoID4gMDtcbiAgICBpZiAoaGFzU2VsZWN0aW9uKSB7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2VsZWN0aW9uLnJhbmdlQ291bnQ7IGkrKykge1xuICAgICAgICAgICAgY29uc3QgcmFuZ2UgPSBzZWxlY3Rpb24uZ2V0UmFuZ2VBdChpKTtcbiAgICAgICAgICAgIGNvbnN0IHJlY3RzID0gcmFuZ2UuZ2V0Q2xpZW50UmVjdHMoKTtcbiAgICAgICAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgcmVjdHMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICAgICAgICBjb25zdCByZWN0ID0gcmVjdHNbal07XG4gICAgICAgICAgICAgICAgaWYgKGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQocmVjdC5sZWZ0LCByZWN0LnRvcCkgPT09IHRhcmdldCkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgaWYgdGFnIGlzIGlucHV0IG9yIHRleHRhcmVhLlxuICAgIGlmICh0YXJnZXQgaW5zdGFuY2VvZiBIVE1MSW5wdXRFbGVtZW50IHx8IHRhcmdldCBpbnN0YW5jZW9mIEhUTUxUZXh0QXJlYUVsZW1lbnQpIHtcbiAgICAgICAgaWYgKGhhc1NlbGVjdGlvbiB8fCAoIXRhcmdldC5yZWFkT25seSAmJiAhdGFyZ2V0LmRpc2FibGVkKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLy8gaGlkZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qKlxuICogUmV0cmlldmVzIHRoZSB2YWx1ZSBhc3NvY2lhdGVkIHdpdGggdGhlIHNwZWNpZmllZCBrZXkgZnJvbSB0aGUgZmxhZyBtYXAuXG4gKlxuICogQHBhcmFtIGtleSAtIFRoZSBrZXkgdG8gcmV0cmlldmUgdGhlIHZhbHVlIGZvci5cbiAqIEByZXR1cm4gVGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGUgc3BlY2lmaWVkIGtleS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEZsYWcoa2V5OiBzdHJpbmcpOiBhbnkge1xuICAgIHRyeSB7XG4gICAgICAgIHJldHVybiB3aW5kb3cuX3dhaWxzLmZsYWdzW2tleV07XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJVbmFibGUgdG8gcmV0cmlldmUgZmxhZyAnXCIgKyBrZXkgKyBcIic6IFwiICsgZSwgeyBjYXVzZTogZSB9KTtcbiAgICB9XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IGludm9rZSwgSXNXaW5kb3dzIH0gZnJvbSBcIi4vc3lzdGVtLmpzXCI7XG5pbXBvcnQgeyBHZXRGbGFnIH0gZnJvbSBcIi4vZmxhZ3MuanNcIjtcbmltcG9ydCB7IGNhblRyYWNrQnV0dG9ucywgZXZlbnRUYXJnZXQgfSBmcm9tIFwiLi91dGlscy5qc1wiO1xuXG4vLyBTZXR1cFxubGV0IGNhbkRyYWcgPSBmYWxzZTtcbmxldCBkcmFnZ2luZyA9IGZhbHNlO1xuXG5sZXQgcmVzaXphYmxlID0gZmFsc2U7XG5sZXQgY2FuUmVzaXplID0gZmFsc2U7XG5sZXQgcmVzaXppbmcgPSBmYWxzZTtcbmxldCByZXNpemVFZGdlOiBzdHJpbmcgPSBcIlwiO1xubGV0IGRlZmF1bHRDdXJzb3IgPSBcImF1dG9cIjtcblxubGV0IGJ1dHRvbnMgPSAwO1xuY29uc3QgYnV0dG9uc1RyYWNrZWQgPSBjYW5UcmFja0J1dHRvbnMoKTtcblxud2luZG93Ll93YWlscyA9IHdpbmRvdy5fd2FpbHMgfHwge307XG53aW5kb3cuX3dhaWxzLnNldFJlc2l6YWJsZSA9ICh2YWx1ZTogYm9vbGVhbik6IHZvaWQgPT4ge1xuICAgIHJlc2l6YWJsZSA9IHZhbHVlO1xuICAgIGlmICghcmVzaXphYmxlKSB7XG4gICAgICAgIC8vIFN0b3AgcmVzaXppbmcgaWYgaW4gcHJvZ3Jlc3MuXG4gICAgICAgIGNhblJlc2l6ZSA9IHJlc2l6aW5nID0gZmFsc2U7XG4gICAgICAgIHNldFJlc2l6ZSgpO1xuICAgIH1cbn07XG5cbi8vIERlZmVyIGF0dGFjaGluZyBtb3VzZSBsaXN0ZW5lcnMgdW50aWwgd2Uga25vdyB3ZSdyZSBub3Qgb24gbW9iaWxlLlxubGV0IGRyYWdJbml0RG9uZSA9IGZhbHNlO1xuZnVuY3Rpb24gaXNNb2JpbGUoKTogYm9vbGVhbiB7XG4gICAgY29uc3Qgb3MgPSAod2luZG93IGFzIGFueSkuX3dhaWxzPy5lbnZpcm9ubWVudD8uT1M7XG4gICAgaWYgKG9zID09PSBcImlvc1wiIHx8IG9zID09PSBcImFuZHJvaWRcIikgcmV0dXJuIHRydWU7XG4gICAgLy8gRmFsbGJhY2sgaGV1cmlzdGljIGlmIGVudmlyb25tZW50IG5vdCB5ZXQgc2V0XG4gICAgY29uc3QgdWEgPSBuYXZpZ2F0b3IudXNlckFnZW50IHx8IG5hdmlnYXRvci52ZW5kb3IgfHwgKHdpbmRvdyBhcyBhbnkpLm9wZXJhIHx8IFwiXCI7XG4gICAgcmV0dXJuIC9hbmRyb2lkfGlwaG9uZXxpcGFkfGlwb2R8aWVtb2JpbGV8d3BkZXNrdG9wL2kudGVzdCh1YSk7XG59XG5mdW5jdGlvbiB0cnlJbml0RHJhZ0hhbmRsZXJzKCk6IHZvaWQge1xuICAgIGlmIChkcmFnSW5pdERvbmUpIHJldHVybjtcbiAgICBpZiAoaXNNb2JpbGUoKSkgcmV0dXJuO1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZWRvd24nLCB1cGRhdGUsIHsgY2FwdHVyZTogdHJ1ZSB9KTtcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2Vtb3ZlJywgdXBkYXRlLCB7IGNhcHR1cmU6IHRydWUgfSk7XG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNldXAnLCB1cGRhdGUsIHsgY2FwdHVyZTogdHJ1ZSB9KTtcbiAgICBmb3IgKGNvbnN0IGV2IG9mIFsnY2xpY2snLCAnY29udGV4dG1lbnUnLCAnZGJsY2xpY2snXSkge1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihldiwgc3VwcHJlc3NFdmVudCwgeyBjYXB0dXJlOiB0cnVlIH0pO1xuICAgIH1cbiAgICBkcmFnSW5pdERvbmUgPSB0cnVlO1xufVxuLy8gQXR0ZW1wdCBpbW1lZGlhdGUgaW5pdCAoaW4gY2FzZSBlbnZpcm9ubWVudCBhbHJlYWR5IHByZXNlbnQpXG50cnlJbml0RHJhZ0hhbmRsZXJzKCk7XG4vLyBBbHNvIGF0dGVtcHQgb24gRE9NIHJlYWR5XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgdHJ5SW5pdERyYWdIYW5kbGVycywgeyBvbmNlOiB0cnVlIH0pO1xuLy8gQXMgYSBsYXN0IHJlc29ydCwgcG9sbCBmb3IgZW52aXJvbm1lbnQgZm9yIGEgc2hvcnQgcGVyaW9kXG5sZXQgZHJhZ0VudlBvbGxzID0gMDtcbmNvbnN0IGRyYWdFbnZQb2xsID0gd2luZG93LnNldEludGVydmFsKCgpID0+IHtcbiAgICBpZiAoZHJhZ0luaXREb25lKSB7IHdpbmRvdy5jbGVhckludGVydmFsKGRyYWdFbnZQb2xsKTsgcmV0dXJuOyB9XG4gICAgdHJ5SW5pdERyYWdIYW5kbGVycygpO1xuICAgIGlmICgrK2RyYWdFbnZQb2xscyA+IDEwMCkgeyB3aW5kb3cuY2xlYXJJbnRlcnZhbChkcmFnRW52UG9sbCk7IH1cbn0sIDUwKTtcblxuZnVuY3Rpb24gc3VwcHJlc3NFdmVudChldmVudDogRXZlbnQpIHtcbiAgICAvLyBTdXBwcmVzcyBjbGljayBldmVudHMgd2hpbGUgcmVzaXppbmcgb3IgZHJhZ2dpbmcuXG4gICAgaWYgKGRyYWdnaW5nIHx8IHJlc2l6aW5nKSB7XG4gICAgICAgIGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpO1xuICAgICAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICB9XG59XG5cbi8vIFVzZSBjb25zdGFudHMgdG8gYXZvaWQgY29tcGFyaW5nIHN0cmluZ3MgbXVsdGlwbGUgdGltZXMuXG5jb25zdCBNb3VzZURvd24gPSAwO1xuY29uc3QgTW91c2VVcCAgID0gMTtcbmNvbnN0IE1vdXNlTW92ZSA9IDI7XG5cbmZ1bmN0aW9uIHVwZGF0ZShldmVudDogTW91c2VFdmVudCkge1xuICAgIC8vIFdpbmRvd3Mgc3VwcHJlc3NlcyBtb3VzZSBldmVudHMgYXQgdGhlIGVuZCBvZiBkcmFnZ2luZyBvciByZXNpemluZyxcbiAgICAvLyBzbyB3ZSBuZWVkIHRvIGJlIHNtYXJ0IGFuZCBzeW50aGVzaXplIGJ1dHRvbiBldmVudHMuXG5cbiAgICBsZXQgZXZlbnRUeXBlOiBudW1iZXIsIGV2ZW50QnV0dG9ucyA9IGV2ZW50LmJ1dHRvbnM7XG4gICAgc3dpdGNoIChldmVudC50eXBlKSB7XG4gICAgICAgIGNhc2UgJ21vdXNlZG93bic6XG4gICAgICAgICAgICBldmVudFR5cGUgPSBNb3VzZURvd247XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnMgfCAoMSA8PCBldmVudC5idXR0b24pOyB9XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAnbW91c2V1cCc6XG4gICAgICAgICAgICBldmVudFR5cGUgPSBNb3VzZVVwO1xuICAgICAgICAgICAgaWYgKCFidXR0b25zVHJhY2tlZCkgeyBldmVudEJ1dHRvbnMgPSBidXR0b25zICYgfigxIDw8IGV2ZW50LmJ1dHRvbik7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgZXZlbnRUeXBlID0gTW91c2VNb3ZlO1xuICAgICAgICAgICAgaWYgKCFidXR0b25zVHJhY2tlZCkgeyBldmVudEJ1dHRvbnMgPSBidXR0b25zOyB9XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG5cbiAgICBsZXQgcmVsZWFzZWQgPSBidXR0b25zICYgfmV2ZW50QnV0dG9ucztcbiAgICBsZXQgcHJlc3NlZCA9IGV2ZW50QnV0dG9ucyAmIH5idXR0b25zO1xuXG4gICAgYnV0dG9ucyA9IGV2ZW50QnV0dG9ucztcblxuICAgIC8vIFN5bnRoZXNpemUgYSByZWxlYXNlLXByZXNzIHNlcXVlbmNlIGlmIHdlIGRldGVjdCBhIHByZXNzIG9mIGFuIGFscmVhZHkgcHJlc3NlZCBidXR0b24uXG4gICAgaWYgKGV2ZW50VHlwZSA9PT0gTW91c2VEb3duICYmICEocHJlc3NlZCAmIGV2ZW50LmJ1dHRvbikpIHtcbiAgICAgICAgcmVsZWFzZWQgfD0gKDEgPDwgZXZlbnQuYnV0dG9uKTtcbiAgICAgICAgcHJlc3NlZCB8PSAoMSA8PCBldmVudC5idXR0b24pO1xuICAgIH1cblxuICAgIC8vIFN1cHByZXNzIGFsbCBidXR0b24gZXZlbnRzIGR1cmluZyBkcmFnZ2luZyBhbmQgcmVzaXppbmcsXG4gICAgLy8gdW5sZXNzIHRoaXMgaXMgYSBtb3VzZXVwIGV2ZW50IHRoYXQgaXMgZW5kaW5nIGEgZHJhZyBhY3Rpb24uXG4gICAgaWYgKFxuICAgICAgICBldmVudFR5cGUgIT09IE1vdXNlTW92ZSAvLyBGYXN0IHBhdGggZm9yIG1vdXNlbW92ZVxuICAgICAgICAmJiByZXNpemluZ1xuICAgICAgICB8fCAoXG4gICAgICAgICAgICBkcmFnZ2luZ1xuICAgICAgICAgICAgJiYgKFxuICAgICAgICAgICAgICAgIGV2ZW50VHlwZSA9PT0gTW91c2VEb3duXG4gICAgICAgICAgICAgICAgfHwgZXZlbnQuYnV0dG9uICE9PSAwXG4gICAgICAgICAgICApXG4gICAgICAgIClcbiAgICApIHtcbiAgICAgICAgZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH1cblxuICAgIC8vIEhhbmRsZSByZWxlYXNlc1xuICAgIGlmIChyZWxlYXNlZCAmIDEpIHsgcHJpbWFyeVVwKGV2ZW50KTsgfVxuICAgIC8vIEhhbmRsZSBwcmVzc2VzXG4gICAgaWYgKHByZXNzZWQgJiAxKSB7IHByaW1hcnlEb3duKGV2ZW50KTsgfVxuXG4gICAgLy8gSGFuZGxlIG1vdXNlbW92ZVxuICAgIGlmIChldmVudFR5cGUgPT09IE1vdXNlTW92ZSkgeyBvbk1vdXNlTW92ZShldmVudCk7IH07XG59XG5cbmZ1bmN0aW9uIHByaW1hcnlEb3duKGV2ZW50OiBNb3VzZUV2ZW50KTogdm9pZCB7XG4gICAgLy8gUmVzZXQgcmVhZGluZXNzIHN0YXRlLlxuICAgIGNhbkRyYWcgPSBmYWxzZTtcbiAgICBjYW5SZXNpemUgPSBmYWxzZTtcblxuICAgIC8vIElnbm9yZSByZXBlYXRlZCBjbGlja3Mgb24gbWFjT1MgYW5kIExpbnV4LlxuICAgIGlmICghSXNXaW5kb3dzKCkpIHtcbiAgICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICdtb3VzZWRvd24nICYmIGV2ZW50LmJ1dHRvbiA9PT0gMCAmJiBldmVudC5kZXRhaWwgIT09IDEpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmIChyZXNpemVFZGdlKSB7XG4gICAgICAgIC8vIFJlYWR5IHRvIHJlc2l6ZSBpZiB0aGUgcHJpbWFyeSBidXR0b24gd2FzIHByZXNzZWQgZm9yIHRoZSBmaXJzdCB0aW1lLlxuICAgICAgICBjYW5SZXNpemUgPSB0cnVlO1xuICAgICAgICAvLyBEbyBub3Qgc3RhcnQgZHJhZyBvcGVyYXRpb25zIHdoZW4gb24gcmVzaXplIGVkZ2VzLlxuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gUmV0cmlldmUgdGFyZ2V0IGVsZW1lbnRcbiAgICBjb25zdCB0YXJnZXQgPSBldmVudFRhcmdldChldmVudCk7XG5cbiAgICAvLyBSZWFkeSB0byBkcmFnIGlmIHRoZSBwcmltYXJ5IGJ1dHRvbiB3YXMgcHJlc3NlZCBmb3IgdGhlIGZpcnN0IHRpbWUgb24gYSBkcmFnZ2FibGUgZWxlbWVudC5cbiAgICAvLyBJZ25vcmUgY2xpY2tzIG9uIHRoZSBzY3JvbGxiYXIuXG4gICAgY29uc3Qgc3R5bGUgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0YXJnZXQpO1xuICAgIGNhbkRyYWcgPSAoXG4gICAgICAgIHN0eWxlLmdldFByb3BlcnR5VmFsdWUoXCItLXdhaWxzLWRyYWdnYWJsZVwiKS50cmltKCkgPT09IFwiZHJhZ1wiXG4gICAgICAgICYmIChcbiAgICAgICAgICAgIGV2ZW50Lm9mZnNldFggLSBwYXJzZUZsb2F0KHN0eWxlLnBhZGRpbmdMZWZ0KSA8IHRhcmdldC5jbGllbnRXaWR0aFxuICAgICAgICAgICAgJiYgZXZlbnQub2Zmc2V0WSAtIHBhcnNlRmxvYXQoc3R5bGUucGFkZGluZ1RvcCkgPCB0YXJnZXQuY2xpZW50SGVpZ2h0XG4gICAgICAgIClcbiAgICApO1xufVxuXG5mdW5jdGlvbiBwcmltYXJ5VXAoZXZlbnQ6IE1vdXNlRXZlbnQpIHtcbiAgICAvLyBTdG9wIGRyYWdnaW5nIGFuZCByZXNpemluZy5cbiAgICBjYW5EcmFnID0gZmFsc2U7XG4gICAgZHJhZ2dpbmcgPSBmYWxzZTtcbiAgICBjYW5SZXNpemUgPSBmYWxzZTtcbiAgICByZXNpemluZyA9IGZhbHNlO1xufVxuXG5jb25zdCBjdXJzb3JGb3JFZGdlID0gT2JqZWN0LmZyZWV6ZSh7XG4gICAgXCJzZS1yZXNpemVcIjogXCJud3NlLXJlc2l6ZVwiLFxuICAgIFwic3ctcmVzaXplXCI6IFwibmVzdy1yZXNpemVcIixcbiAgICBcIm53LXJlc2l6ZVwiOiBcIm53c2UtcmVzaXplXCIsXG4gICAgXCJuZS1yZXNpemVcIjogXCJuZXN3LXJlc2l6ZVwiLFxuICAgIFwidy1yZXNpemVcIjogXCJldy1yZXNpemVcIixcbiAgICBcIm4tcmVzaXplXCI6IFwibnMtcmVzaXplXCIsXG4gICAgXCJzLXJlc2l6ZVwiOiBcIm5zLXJlc2l6ZVwiLFxuICAgIFwiZS1yZXNpemVcIjogXCJldy1yZXNpemVcIixcbn0pXG5cbmZ1bmN0aW9uIHNldFJlc2l6ZShlZGdlPzoga2V5b2YgdHlwZW9mIGN1cnNvckZvckVkZ2UpOiB2b2lkIHtcbiAgICBpZiAoZWRnZSkge1xuICAgICAgICBpZiAoIXJlc2l6ZUVkZ2UpIHsgZGVmYXVsdEN1cnNvciA9IGRvY3VtZW50LmJvZHkuc3R5bGUuY3Vyc29yOyB9XG4gICAgICAgIGRvY3VtZW50LmJvZHkuc3R5bGUuY3Vyc29yID0gY3Vyc29yRm9yRWRnZVtlZGdlXTtcbiAgICB9IGVsc2UgaWYgKCFlZGdlICYmIHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSBkZWZhdWx0Q3Vyc29yO1xuICAgIH1cblxuICAgIHJlc2l6ZUVkZ2UgPSBlZGdlIHx8IFwiXCI7XG59XG5cbmZ1bmN0aW9uIG9uTW91c2VNb3ZlKGV2ZW50OiBNb3VzZUV2ZW50KTogdm9pZCB7XG4gICAgaWYgKGNhblJlc2l6ZSAmJiByZXNpemVFZGdlKSB7XG4gICAgICAgIC8vIFN0YXJ0IHJlc2l6aW5nLlxuICAgICAgICByZXNpemluZyA9IHRydWU7XG4gICAgICAgIGludm9rZShcIndhaWxzOnJlc2l6ZTpcIiArIHJlc2l6ZUVkZ2UpO1xuICAgIH0gZWxzZSBpZiAoY2FuRHJhZykge1xuICAgICAgICAvLyBTdGFydCBkcmFnZ2luZy5cbiAgICAgICAgZHJhZ2dpbmcgPSB0cnVlO1xuICAgICAgICBpbnZva2UoXCJ3YWlsczpkcmFnXCIpO1xuICAgIH1cblxuICAgIGlmIChkcmFnZ2luZyB8fCByZXNpemluZykge1xuICAgICAgICAvLyBFaXRoZXIgZHJhZyBvciByZXNpemUgaXMgb25nb2luZyxcbiAgICAgICAgLy8gcmVzZXQgcmVhZGluZXNzIGFuZCBzdG9wIHByb2Nlc3NpbmcuXG4gICAgICAgIGNhbkRyYWcgPSBjYW5SZXNpemUgPSBmYWxzZTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmICghcmVzaXphYmxlIHx8ICFJc1dpbmRvd3MoKSkge1xuICAgICAgICBpZiAocmVzaXplRWRnZSkgeyBzZXRSZXNpemUoKTsgfVxuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgcmVzaXplSGFuZGxlSGVpZ2h0ID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVIZWlnaHRcIikgfHwgNTtcbiAgICBjb25zdCByZXNpemVIYW5kbGVXaWR0aCA9IEdldEZsYWcoXCJzeXN0ZW0ucmVzaXplSGFuZGxlV2lkdGhcIikgfHwgNTtcblxuICAgIC8vIEV4dHJhIHBpeGVscyBmb3IgdGhlIGNvcm5lciBhcmVhcy5cbiAgICBjb25zdCBjb3JuZXJFeHRyYSA9IEdldEZsYWcoXCJyZXNpemVDb3JuZXJFeHRyYVwiKSB8fCAxMDtcblxuICAgIGNvbnN0IHJpZ2h0Qm9yZGVyID0gKHdpbmRvdy5vdXRlcldpZHRoIC0gZXZlbnQuY2xpZW50WCkgPCByZXNpemVIYW5kbGVXaWR0aDtcbiAgICBjb25zdCBsZWZ0Qm9yZGVyID0gZXZlbnQuY2xpZW50WCA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xuICAgIGNvbnN0IHRvcEJvcmRlciA9IGV2ZW50LmNsaWVudFkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XG4gICAgY29uc3QgYm90dG9tQm9yZGVyID0gKHdpbmRvdy5vdXRlckhlaWdodCAtIGV2ZW50LmNsaWVudFkpIDwgcmVzaXplSGFuZGxlSGVpZ2h0O1xuXG4gICAgLy8gQWRqdXN0IGZvciBjb3JuZXIgYXJlYXMuXG4gICAgY29uc3QgcmlnaHRDb3JuZXIgPSAod2luZG93Lm91dGVyV2lkdGggLSBldmVudC5jbGllbnRYKSA8IChyZXNpemVIYW5kbGVXaWR0aCArIGNvcm5lckV4dHJhKTtcbiAgICBjb25zdCBsZWZ0Q29ybmVyID0gZXZlbnQuY2xpZW50WCA8IChyZXNpemVIYW5kbGVXaWR0aCArIGNvcm5lckV4dHJhKTtcbiAgICBjb25zdCB0b3BDb3JuZXIgPSBldmVudC5jbGllbnRZIDwgKHJlc2l6ZUhhbmRsZUhlaWdodCArIGNvcm5lckV4dHJhKTtcbiAgICBjb25zdCBib3R0b21Db3JuZXIgPSAod2luZG93Lm91dGVySGVpZ2h0IC0gZXZlbnQuY2xpZW50WSkgPCAocmVzaXplSGFuZGxlSGVpZ2h0ICsgY29ybmVyRXh0cmEpO1xuXG4gICAgaWYgKCFsZWZ0Q29ybmVyICYmICF0b3BDb3JuZXIgJiYgIWJvdHRvbUNvcm5lciAmJiAhcmlnaHRDb3JuZXIpIHtcbiAgICAgICAgLy8gT3B0aW1pc2F0aW9uOiBvdXQgb2YgYWxsIGNvcm5lciBhcmVhcyBpbXBsaWVzIG91dCBvZiBib3JkZXJzLlxuICAgICAgICBzZXRSZXNpemUoKTtcbiAgICB9XG4gICAgLy8gRGV0ZWN0IGNvcm5lcnMuXG4gICAgZWxzZSBpZiAocmlnaHRDb3JuZXIgJiYgYm90dG9tQ29ybmVyKSBzZXRSZXNpemUoXCJzZS1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAobGVmdENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInN3LXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIHRvcENvcm5lcikgc2V0UmVzaXplKFwibnctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHRvcENvcm5lciAmJiByaWdodENvcm5lcikgc2V0UmVzaXplKFwibmUtcmVzaXplXCIpO1xuICAgIC8vIERldGVjdCBib3JkZXJzLlxuICAgIGVsc2UgaWYgKGxlZnRCb3JkZXIpIHNldFJlc2l6ZShcInctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHRvcEJvcmRlcikgc2V0UmVzaXplKFwibi1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAoYm90dG9tQm9yZGVyKSBzZXRSZXNpemUoXCJzLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChyaWdodEJvcmRlcikgc2V0UmVzaXplKFwiZS1yZXNpemVcIik7XG4gICAgLy8gT3V0IG9mIGJvcmRlciBhcmVhLlxuICAgIGVsc2Ugc2V0UmVzaXplKCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQXBwbGljYXRpb24pO1xuXG5jb25zdCBIaWRlTWV0aG9kID0gMDtcbmNvbnN0IFNob3dNZXRob2QgPSAxO1xuY29uc3QgUXVpdE1ldGhvZCA9IDI7XG5cbi8qKlxuICogSGlkZXMgYSBjZXJ0YWluIG1ldGhvZCBieSBjYWxsaW5nIHRoZSBIaWRlTWV0aG9kIGZ1bmN0aW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSGlkZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICByZXR1cm4gY2FsbChIaWRlTWV0aG9kKTtcbn1cblxuLyoqXG4gKiBDYWxscyB0aGUgU2hvd01ldGhvZCBhbmQgcmV0dXJucyB0aGUgcmVzdWx0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gU2hvdygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICByZXR1cm4gY2FsbChTaG93TWV0aG9kKTtcbn1cblxuLyoqXG4gKiBDYWxscyB0aGUgUXVpdE1ldGhvZCB0byB0ZXJtaW5hdGUgdGhlIHByb2dyYW0uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBRdWl0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKFF1aXRNZXRob2QpO1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBDYW5jZWxsYWJsZVByb21pc2UsIHR5cGUgQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVycyB9IGZyb20gXCIuL2NhbmNlbGxhYmxlLmpzXCI7XG5pbXBvcnQgeyBuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lcyB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gXCIuL25hbm9pZC5qc1wiO1xuXG4vLyBTZXR1cFxud2luZG93Ll93YWlscyA9IHdpbmRvdy5fd2FpbHMgfHwge307XG5cbnR5cGUgUHJvbWlzZVJlc29sdmVycyA9IE9taXQ8Q2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxhbnk+LCBcInByb21pc2VcIiB8IFwib25jYW5jZWxsZWRcIj5cblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQ2FsbCk7XG5jb25zdCBjYW5jZWxDYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5DYW5jZWxDYWxsKTtcbmNvbnN0IGNhbGxSZXNwb25zZXMgPSBuZXcgTWFwPHN0cmluZywgUHJvbWlzZVJlc29sdmVycz4oKTtcblxuY29uc3QgQ2FsbEJpbmRpbmcgPSAwO1xuY29uc3QgQ2FuY2VsTWV0aG9kID0gMFxuXG4vKipcbiAqIEhvbGRzIGFsbCByZXF1aXJlZCBpbmZvcm1hdGlvbiBmb3IgYSBiaW5kaW5nIGNhbGwuXG4gKiBNYXkgcHJvdmlkZSBlaXRoZXIgYSBtZXRob2QgSUQgb3IgYSBtZXRob2QgbmFtZSwgYnV0IG5vdCBib3RoLlxuICovXG5leHBvcnQgdHlwZSBDYWxsT3B0aW9ucyA9IHtcbiAgICAvKiogVGhlIG51bWVyaWMgSUQgb2YgdGhlIGJvdW5kIG1ldGhvZCB0byBjYWxsLiAqL1xuICAgIG1ldGhvZElEOiBudW1iZXI7XG4gICAgLyoqIFRoZSBmdWxseSBxdWFsaWZpZWQgbmFtZSBvZiB0aGUgYm91bmQgbWV0aG9kIHRvIGNhbGwuICovXG4gICAgbWV0aG9kTmFtZT86IG5ldmVyO1xuICAgIC8qKiBBcmd1bWVudHMgdG8gYmUgcGFzc2VkIGludG8gdGhlIGJvdW5kIG1ldGhvZC4gKi9cbiAgICBhcmdzOiBhbnlbXTtcbn0gfCB7XG4gICAgLyoqIFRoZSBudW1lcmljIElEIG9mIHRoZSBib3VuZCBtZXRob2QgdG8gY2FsbC4gKi9cbiAgICBtZXRob2RJRD86IG5ldmVyO1xuICAgIC8qKiBUaGUgZnVsbHkgcXVhbGlmaWVkIG5hbWUgb2YgdGhlIGJvdW5kIG1ldGhvZCB0byBjYWxsLiAqL1xuICAgIG1ldGhvZE5hbWU6IHN0cmluZztcbiAgICAvKiogQXJndW1lbnRzIHRvIGJlIHBhc3NlZCBpbnRvIHRoZSBib3VuZCBtZXRob2QuICovXG4gICAgYXJnczogYW55W107XG59O1xuXG4vKipcbiAqIEV4Y2VwdGlvbiBjbGFzcyB0aGF0IHdpbGwgYmUgdGhyb3duIGluIGNhc2UgdGhlIGJvdW5kIG1ldGhvZCByZXR1cm5zIGFuIGVycm9yLlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgUnVudGltZUVycm9yI25hbWV9IHByb3BlcnR5IGlzIFwiUnVudGltZUVycm9yXCIuXG4gKi9cbmV4cG9ydCBjbGFzcyBSdW50aW1lRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gICAgLyoqXG4gICAgICogQ29uc3RydWN0cyBhIG5ldyBSdW50aW1lRXJyb3IgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIG1lc3NhZ2UgLSBUaGUgZXJyb3IgbWVzc2FnZS5cbiAgICAgKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgdG8gYmUgZm9yd2FyZGVkIHRvIHRoZSBFcnJvciBjb25zdHJ1Y3Rvci5cbiAgICAgKi9cbiAgICBjb25zdHJ1Y3RvcihtZXNzYWdlPzogc3RyaW5nLCBvcHRpb25zPzogRXJyb3JPcHRpb25zKSB7XG4gICAgICAgIHN1cGVyKG1lc3NhZ2UsIG9wdGlvbnMpO1xuICAgICAgICB0aGlzLm5hbWUgPSBcIlJ1bnRpbWVFcnJvclwiO1xuICAgIH1cbn1cblxuLyoqXG4gKiBHZW5lcmF0ZXMgYSB1bmlxdWUgSUQgdXNpbmcgdGhlIG5hbm9pZCBsaWJyYXJ5LlxuICpcbiAqIEByZXR1cm5zIEEgdW5pcXVlIElEIHRoYXQgZG9lcyBub3QgZXhpc3QgaW4gdGhlIGNhbGxSZXNwb25zZXMgc2V0LlxuICovXG5mdW5jdGlvbiBnZW5lcmF0ZUlEKCk6IHN0cmluZyB7XG4gICAgbGV0IHJlc3VsdDtcbiAgICBkbyB7XG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xuICAgIH0gd2hpbGUgKGNhbGxSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKlxuICogQ2FsbCBhIGJvdW5kIG1ldGhvZCBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIGNhbGwgb3B0aW9ucy5cbiAqXG4gKiBJbiBjYXNlIG9mIGZhaWx1cmUsIHRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgcmVqZWN0IHdpdGggYW4gZXhjZXB0aW9uXG4gKiBhbW9uZyBSZWZlcmVuY2VFcnJvciAodW5rbm93biBtZXRob2QpLCBUeXBlRXJyb3IgKHdyb25nIGFyZ3VtZW50IGNvdW50IG9yIHR5cGUpLFxuICoge0BsaW5rIFJ1bnRpbWVFcnJvcn0gKG1ldGhvZCByZXR1cm5lZCBhbiBlcnJvciksIG9yIG90aGVyIChuZXR3b3JrIG9yIGludGVybmFsIGVycm9ycykuXG4gKiBUaGUgZXhjZXB0aW9uIG1pZ2h0IGhhdmUgYSBcImNhdXNlXCIgZmllbGQgd2l0aCB0aGUgdmFsdWUgcmV0dXJuZWRcbiAqIGJ5IHRoZSBhcHBsaWNhdGlvbi0gb3Igc2VydmljZS1sZXZlbCBlcnJvciBtYXJzaGFsaW5nIGZ1bmN0aW9ucy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIEEgbWV0aG9kIGNhbGwgZGVzY3JpcHRvci5cbiAqIEByZXR1cm5zIFRoZSByZXN1bHQgb2YgdGhlIGNhbGwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYWxsKG9wdGlvbnM6IENhbGxPcHRpb25zKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIGNvbnN0IGlkID0gZ2VuZXJhdGVJRCgpO1xuXG4gICAgY29uc3QgcmVzdWx0ID0gQ2FuY2VsbGFibGVQcm9taXNlLndpdGhSZXNvbHZlcnM8YW55PigpO1xuICAgIGNhbGxSZXNwb25zZXMuc2V0KGlkLCB7IHJlc29sdmU6IHJlc3VsdC5yZXNvbHZlLCByZWplY3Q6IHJlc3VsdC5yZWplY3QgfSk7XG5cbiAgICBjb25zdCByZXF1ZXN0ID0gY2FsbChDYWxsQmluZGluZywgT2JqZWN0LmFzc2lnbih7IFwiY2FsbC1pZFwiOiBpZCB9LCBvcHRpb25zKSk7XG4gICAgbGV0IHJ1bm5pbmcgPSB0cnVlO1xuXG4gICAgcmVxdWVzdC50aGVuKChyZXMpID0+IHtcbiAgICAgICAgcnVubmluZyA9IGZhbHNlO1xuICAgICAgICBjYWxsUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgICAgIHJlc3VsdC5yZXNvbHZlKHJlcyk7XG4gICAgfSwgKGVycikgPT4ge1xuICAgICAgICBydW5uaW5nID0gZmFsc2U7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcbiAgICAgICAgcmVzdWx0LnJlamVjdChlcnIpO1xuICAgIH0pO1xuXG4gICAgY29uc3QgY2FuY2VsID0gKCkgPT4ge1xuICAgICAgICBjYWxsUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgICAgIHJldHVybiBjYW5jZWxDYWxsKENhbmNlbE1ldGhvZCwge1wiY2FsbC1pZFwiOiBpZH0pLmNhdGNoKChlcnIpID0+IHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJFcnJvciB3aGlsZSByZXF1ZXN0aW5nIGJpbmRpbmcgY2FsbCBjYW5jZWxsYXRpb246XCIsIGVycik7XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICByZXN1bHQub25jYW5jZWxsZWQgPSAoKSA9PiB7XG4gICAgICAgIGlmIChydW5uaW5nKSB7XG4gICAgICAgICAgICByZXR1cm4gY2FuY2VsKCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICByZXR1cm4gcmVxdWVzdC50aGVuKGNhbmNlbCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcmV0dXJuIHJlc3VsdC5wcm9taXNlO1xufVxuXG4vKipcbiAqIENhbGxzIGEgYm91bmQgbWV0aG9kIGJ5IG5hbWUgd2l0aCB0aGUgc3BlY2lmaWVkIGFyZ3VtZW50cy5cbiAqIFNlZSB7QGxpbmsgQ2FsbH0gZm9yIGRldGFpbHMuXG4gKlxuICogQHBhcmFtIG1ldGhvZE5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgbWV0aG9kIGluIHRoZSBmb3JtYXQgJ3BhY2thZ2Uuc3RydWN0Lm1ldGhvZCcuXG4gKiBAcGFyYW0gYXJncyAtIFRoZSBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgbWV0aG9kLlxuICogQHJldHVybnMgVGhlIHJlc3VsdCBvZiB0aGUgbWV0aG9kIGNhbGwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCeU5hbWUobWV0aG9kTmFtZTogc3RyaW5nLCAuLi5hcmdzOiBhbnlbXSk6IENhbmNlbGxhYmxlUHJvbWlzZTxhbnk+IHtcbiAgICByZXR1cm4gQ2FsbCh7IG1ldGhvZE5hbWUsIGFyZ3MgfSk7XG59XG5cbi8qKlxuICogQ2FsbHMgYSBtZXRob2QgYnkgaXRzIG51bWVyaWMgSUQgd2l0aCB0aGUgc3BlY2lmaWVkIGFyZ3VtZW50cy5cbiAqIFNlZSB7QGxpbmsgQ2FsbH0gZm9yIGRldGFpbHMuXG4gKlxuICogQHBhcmFtIG1ldGhvZElEIC0gVGhlIElEIG9mIHRoZSBtZXRob2QgdG8gY2FsbC5cbiAqIEBwYXJhbSBhcmdzIC0gVGhlIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2QuXG4gKiBAcmV0dXJuIFRoZSByZXN1bHQgb2YgdGhlIG1ldGhvZCBjYWxsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gQnlJRChtZXRob2RJRDogbnVtYmVyLCAuLi5hcmdzOiBhbnlbXSk6IENhbmNlbGxhYmxlUHJvbWlzZTxhbnk+IHtcbiAgICByZXR1cm4gQ2FsbCh7IG1ldGhvZElELCBhcmdzIH0pO1xufVxuIiwgIi8vIFNvdXJjZTogaHR0cHM6Ly9naXRodWIuY29tL2luc3BlY3QtanMvaXMtY2FsbGFibGVcblxuLy8gVGhlIE1JVCBMaWNlbnNlIChNSVQpXG4vL1xuLy8gQ29weXJpZ2h0IChjKSAyMDE1IEpvcmRhbiBIYXJiYW5kXG4vL1xuLy8gUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weVxuLy8gb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgXCJTb2Z0d2FyZVwiKSwgdG8gZGVhbFxuLy8gaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0c1xuLy8gdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbFxuLy8gY29waWVzIG9mIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25zIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzXG4vLyBmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOlxuLy9cbi8vIFRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbFxuLy8gY29waWVzIG9yIHN1YnN0YW50aWFsIHBvcnRpb25zIG9mIHRoZSBTb2Z0d2FyZS5cbi8vXG4vLyBUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELCBFWFBSRVNTIE9SXG4vLyBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSxcbi8vIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRVxuLy8gQVVUSE9SUyBPUiBDT1BZUklHSFQgSE9MREVSUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUiBPVEhFUlxuLy8gTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSxcbi8vIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFXG4vLyBTT0ZUV0FSRS5cblxudmFyIGZuVG9TdHIgPSBGdW5jdGlvbi5wcm90b3R5cGUudG9TdHJpbmc7XG52YXIgcmVmbGVjdEFwcGx5OiB0eXBlb2YgUmVmbGVjdC5hcHBseSB8IGZhbHNlIHwgbnVsbCA9IHR5cGVvZiBSZWZsZWN0ID09PSAnb2JqZWN0JyAmJiBSZWZsZWN0ICE9PSBudWxsICYmIFJlZmxlY3QuYXBwbHk7XG52YXIgYmFkQXJyYXlMaWtlOiBhbnk7XG52YXIgaXNDYWxsYWJsZU1hcmtlcjogYW55O1xuaWYgKHR5cGVvZiByZWZsZWN0QXBwbHkgPT09ICdmdW5jdGlvbicgJiYgdHlwZW9mIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIHRyeSB7XG4gICAgICAgIGJhZEFycmF5TGlrZSA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh7fSwgJ2xlbmd0aCcsIHtcbiAgICAgICAgICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHRocm93IGlzQ2FsbGFibGVNYXJrZXI7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICBpc0NhbGxhYmxlTWFya2VyID0ge307XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby10aHJvdy1saXRlcmFsXG4gICAgICAgIHJlZmxlY3RBcHBseShmdW5jdGlvbiAoKSB7IHRocm93IDQyOyB9LCBudWxsLCBiYWRBcnJheUxpa2UpO1xuICAgIH0gY2F0Y2ggKF8pIHtcbiAgICAgICAgaWYgKF8gIT09IGlzQ2FsbGFibGVNYXJrZXIpIHtcbiAgICAgICAgICAgIHJlZmxlY3RBcHBseSA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9XG59IGVsc2Uge1xuICAgIHJlZmxlY3RBcHBseSA9IG51bGw7XG59XG5cbnZhciBjb25zdHJ1Y3RvclJlZ2V4ID0gL15cXHMqY2xhc3NcXGIvO1xudmFyIGlzRVM2Q2xhc3NGbiA9IGZ1bmN0aW9uIGlzRVM2Q2xhc3NGdW5jdGlvbih2YWx1ZTogYW55KTogYm9vbGVhbiB7XG4gICAgdHJ5IHtcbiAgICAgICAgdmFyIGZuU3RyID0gZm5Ub1N0ci5jYWxsKHZhbHVlKTtcbiAgICAgICAgcmV0dXJuIGNvbnN0cnVjdG9yUmVnZXgudGVzdChmblN0cik7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7IC8vIG5vdCBhIGZ1bmN0aW9uXG4gICAgfVxufTtcblxudmFyIHRyeUZ1bmN0aW9uT2JqZWN0ID0gZnVuY3Rpb24gdHJ5RnVuY3Rpb25Ub1N0cih2YWx1ZTogYW55KTogYm9vbGVhbiB7XG4gICAgdHJ5IHtcbiAgICAgICAgaWYgKGlzRVM2Q2xhc3NGbih2YWx1ZSkpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgICAgIGZuVG9TdHIuY2FsbCh2YWx1ZSk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbn07XG52YXIgdG9TdHIgPSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nO1xudmFyIG9iamVjdENsYXNzID0gJ1tvYmplY3QgT2JqZWN0XSc7XG52YXIgZm5DbGFzcyA9ICdbb2JqZWN0IEZ1bmN0aW9uXSc7XG52YXIgZ2VuQ2xhc3MgPSAnW29iamVjdCBHZW5lcmF0b3JGdW5jdGlvbl0nO1xudmFyIGRkYUNsYXNzID0gJ1tvYmplY3QgSFRNTEFsbENvbGxlY3Rpb25dJzsgLy8gSUUgMTFcbnZhciBkZGFDbGFzczIgPSAnW29iamVjdCBIVE1MIGRvY3VtZW50LmFsbCBjbGFzc10nO1xudmFyIGRkYUNsYXNzMyA9ICdbb2JqZWN0IEhUTUxDb2xsZWN0aW9uXSc7IC8vIElFIDktMTBcbnZhciBoYXNUb1N0cmluZ1RhZyA9IHR5cGVvZiBTeW1ib2wgPT09ICdmdW5jdGlvbicgJiYgISFTeW1ib2wudG9TdHJpbmdUYWc7IC8vIGJldHRlcjogdXNlIGBoYXMtdG9zdHJpbmd0YWdgXG5cbnZhciBpc0lFNjggPSAhKDAgaW4gWyxdKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby1zcGFyc2UtYXJyYXlzLCBjb21tYS1zcGFjaW5nXG5cbnZhciBpc0REQTogKHZhbHVlOiBhbnkpID0+IGJvb2xlYW4gPSBmdW5jdGlvbiBpc0RvY3VtZW50RG90QWxsKCkgeyByZXR1cm4gZmFsc2U7IH07XG5pZiAodHlwZW9mIGRvY3VtZW50ID09PSAnb2JqZWN0Jykge1xuICAgIC8vIEZpcmVmb3ggMyBjYW5vbmljYWxpemVzIEREQSB0byB1bmRlZmluZWQgd2hlbiBpdCdzIG5vdCBhY2Nlc3NlZCBkaXJlY3RseVxuICAgIHZhciBhbGwgPSBkb2N1bWVudC5hbGw7XG4gICAgaWYgKHRvU3RyLmNhbGwoYWxsKSA9PT0gdG9TdHIuY2FsbChkb2N1bWVudC5hbGwpKSB7XG4gICAgICAgIGlzRERBID0gZnVuY3Rpb24gaXNEb2N1bWVudERvdEFsbCh2YWx1ZSkge1xuICAgICAgICAgICAgLyogZ2xvYmFscyBkb2N1bWVudDogZmFsc2UgKi9cbiAgICAgICAgICAgIC8vIGluIElFIDYtOCwgdHlwZW9mIGRvY3VtZW50LmFsbCBpcyBcIm9iamVjdFwiIGFuZCBpdCdzIHRydXRoeVxuICAgICAgICAgICAgaWYgKChpc0lFNjggfHwgIXZhbHVlKSAmJiAodHlwZW9mIHZhbHVlID09PSAndW5kZWZpbmVkJyB8fCB0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnKSkge1xuICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBzdHIgPSB0b1N0ci5jYWxsKHZhbHVlKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgICAgICAgICAgICAgIHN0ciA9PT0gZGRhQ2xhc3NcbiAgICAgICAgICAgICAgICAgICAgICAgIHx8IHN0ciA9PT0gZGRhQ2xhc3MyXG4gICAgICAgICAgICAgICAgICAgICAgICB8fCBzdHIgPT09IGRkYUNsYXNzMyAvLyBvcGVyYSAxMi4xNlxuICAgICAgICAgICAgICAgICAgICAgICAgfHwgc3RyID09PSBvYmplY3RDbGFzcyAvLyBJRSA2LThcbiAgICAgICAgICAgICAgICAgICAgKSAmJiB2YWx1ZSgnJykgPT0gbnVsbDsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBlcWVxZXFcbiAgICAgICAgICAgICAgICB9IGNhdGNoIChlKSB7IC8qKi8gfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9O1xuICAgIH1cbn1cblxuZnVuY3Rpb24gaXNDYWxsYWJsZVJlZkFwcGx5PFQ+KHZhbHVlOiBUIHwgdW5rbm93bik6IHZhbHVlIGlzICguLi5hcmdzOiBhbnlbXSkgPT4gYW55ICB7XG4gICAgaWYgKGlzRERBKHZhbHVlKSkgeyByZXR1cm4gdHJ1ZTsgfVxuICAgIGlmICghdmFsdWUpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gJ2Z1bmN0aW9uJyAmJiB0eXBlb2YgdmFsdWUgIT09ICdvYmplY3QnKSB7IHJldHVybiBmYWxzZTsgfVxuICAgIHRyeSB7XG4gICAgICAgIChyZWZsZWN0QXBwbHkgYXMgYW55KSh2YWx1ZSwgbnVsbCwgYmFkQXJyYXlMaWtlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChlICE9PSBpc0NhbGxhYmxlTWFya2VyKSB7IHJldHVybiBmYWxzZTsgfVxuICAgIH1cbiAgICByZXR1cm4gIWlzRVM2Q2xhc3NGbih2YWx1ZSkgJiYgdHJ5RnVuY3Rpb25PYmplY3QodmFsdWUpO1xufVxuXG5mdW5jdGlvbiBpc0NhbGxhYmxlTm9SZWZBcHBseTxUPih2YWx1ZTogVCB8IHVua25vd24pOiB2YWx1ZSBpcyAoLi4uYXJnczogYW55W10pID0+IGFueSB7XG4gICAgaWYgKGlzRERBKHZhbHVlKSkgeyByZXR1cm4gdHJ1ZTsgfVxuICAgIGlmICghdmFsdWUpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gJ2Z1bmN0aW9uJyAmJiB0eXBlb2YgdmFsdWUgIT09ICdvYmplY3QnKSB7IHJldHVybiBmYWxzZTsgfVxuICAgIGlmIChoYXNUb1N0cmluZ1RhZykgeyByZXR1cm4gdHJ5RnVuY3Rpb25PYmplY3QodmFsdWUpOyB9XG4gICAgaWYgKGlzRVM2Q2xhc3NGbih2YWx1ZSkpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgdmFyIHN0ckNsYXNzID0gdG9TdHIuY2FsbCh2YWx1ZSk7XG4gICAgaWYgKHN0ckNsYXNzICE9PSBmbkNsYXNzICYmIHN0ckNsYXNzICE9PSBnZW5DbGFzcyAmJiAhKC9eXFxbb2JqZWN0IEhUTUwvKS50ZXN0KHN0ckNsYXNzKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICByZXR1cm4gdHJ5RnVuY3Rpb25PYmplY3QodmFsdWUpO1xufTtcblxuZXhwb3J0IGRlZmF1bHQgcmVmbGVjdEFwcGx5ID8gaXNDYWxsYWJsZVJlZkFwcGx5IDogaXNDYWxsYWJsZU5vUmVmQXBwbHk7XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCBpc0NhbGxhYmxlIGZyb20gXCIuL2NhbGxhYmxlLmpzXCI7XG5cbi8qKlxuICogRXhjZXB0aW9uIGNsYXNzIHRoYXQgd2lsbCBiZSB1c2VkIGFzIHJlamVjdGlvbiByZWFzb25cbiAqIGluIGNhc2UgYSB7QGxpbmsgQ2FuY2VsbGFibGVQcm9taXNlfSBpcyBjYW5jZWxsZWQgc3VjY2Vzc2Z1bGx5LlxuICpcbiAqIFRoZSB2YWx1ZSBvZiB0aGUge0BsaW5rIG5hbWV9IHByb3BlcnR5IGlzIHRoZSBzdHJpbmcgYFwiQ2FuY2VsRXJyb3JcImAuXG4gKiBUaGUgdmFsdWUgb2YgdGhlIHtAbGluayBjYXVzZX0gcHJvcGVydHkgaXMgdGhlIGNhdXNlIHBhc3NlZCB0byB0aGUgY2FuY2VsIG1ldGhvZCwgaWYgYW55LlxuICovXG5leHBvcnQgY2xhc3MgQ2FuY2VsRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gICAgLyoqXG4gICAgICogQ29uc3RydWN0cyBhIG5ldyBgQ2FuY2VsRXJyb3JgIGluc3RhbmNlLlxuICAgICAqIEBwYXJhbSBtZXNzYWdlIC0gVGhlIGVycm9yIG1lc3NhZ2UuXG4gICAgICogQHBhcmFtIG9wdGlvbnMgLSBPcHRpb25zIHRvIGJlIGZvcndhcmRlZCB0byB0aGUgRXJyb3IgY29uc3RydWN0b3IuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IobWVzc2FnZT86IHN0cmluZywgb3B0aW9ucz86IEVycm9yT3B0aW9ucykge1xuICAgICAgICBzdXBlcihtZXNzYWdlLCBvcHRpb25zKTtcbiAgICAgICAgdGhpcy5uYW1lID0gXCJDYW5jZWxFcnJvclwiO1xuICAgIH1cbn1cblxuLyoqXG4gKiBFeGNlcHRpb24gY2xhc3MgdGhhdCB3aWxsIGJlIHJlcG9ydGVkIGFzIGFuIHVuaGFuZGxlZCByZWplY3Rpb25cbiAqIGluIGNhc2UgYSB7QGxpbmsgQ2FuY2VsbGFibGVQcm9taXNlfSByZWplY3RzIGFmdGVyIGJlaW5nIGNhbmNlbGxlZCxcbiAqIG9yIHdoZW4gdGhlIGBvbmNhbmNlbGxlZGAgY2FsbGJhY2sgdGhyb3dzIG9yIHJlamVjdHMuXG4gKlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgbmFtZX0gcHJvcGVydHkgaXMgdGhlIHN0cmluZyBgXCJDYW5jZWxsZWRSZWplY3Rpb25FcnJvclwiYC5cbiAqIFRoZSB2YWx1ZSBvZiB0aGUge0BsaW5rIGNhdXNlfSBwcm9wZXJ0eSBpcyB0aGUgcmVhc29uIHRoZSBwcm9taXNlIHJlamVjdGVkIHdpdGguXG4gKlxuICogQmVjYXVzZSB0aGUgb3JpZ2luYWwgcHJvbWlzZSB3YXMgY2FuY2VsbGVkLFxuICogYSB3cmFwcGVyIHByb21pc2Ugd2lsbCBiZSBwYXNzZWQgdG8gdGhlIHVuaGFuZGxlZCByZWplY3Rpb24gbGlzdGVuZXIgaW5zdGVhZC5cbiAqIFRoZSB7QGxpbmsgcHJvbWlzZX0gcHJvcGVydHkgaG9sZHMgYSByZWZlcmVuY2UgdG8gdGhlIG9yaWdpbmFsIHByb21pc2UuXG4gKi9cbmV4cG9ydCBjbGFzcyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvciBleHRlbmRzIEVycm9yIHtcbiAgICAvKipcbiAgICAgKiBIb2xkcyBhIHJlZmVyZW5jZSB0byB0aGUgcHJvbWlzZSB0aGF0IHdhcyBjYW5jZWxsZWQgYW5kIHRoZW4gcmVqZWN0ZWQuXG4gICAgICovXG4gICAgcHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+O1xuXG4gICAgLyoqXG4gICAgICogQ29uc3RydWN0cyBhIG5ldyBgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3JgIGluc3RhbmNlLlxuICAgICAqIEBwYXJhbSBwcm9taXNlIC0gVGhlIHByb21pc2UgdGhhdCBjYXVzZWQgdGhlIGVycm9yIG9yaWdpbmFsbHkuXG4gICAgICogQHBhcmFtIHJlYXNvbiAtIFRoZSByZWplY3Rpb24gcmVhc29uLlxuICAgICAqIEBwYXJhbSBpbmZvIC0gQW4gb3B0aW9uYWwgaW5mb3JtYXRpdmUgbWVzc2FnZSBzcGVjaWZ5aW5nIHRoZSBjaXJjdW1zdGFuY2VzIGluIHdoaWNoIHRoZSBlcnJvciB3YXMgdGhyb3duLlxuICAgICAqICAgICAgICAgICAgICAgRGVmYXVsdHMgdG8gdGhlIHN0cmluZyBgXCJVbmhhbmRsZWQgcmVqZWN0aW9uIGluIGNhbmNlbGxlZCBwcm9taXNlLlwiYC5cbiAgICAgKi9cbiAgICBjb25zdHJ1Y3Rvcihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4sIHJlYXNvbj86IGFueSwgaW5mbz86IHN0cmluZykge1xuICAgICAgICBzdXBlcigoaW5mbyA/PyBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gY2FuY2VsbGVkIHByb21pc2UuXCIpICsgXCIgUmVhc29uOiBcIiArIGVycm9yTWVzc2FnZShyZWFzb24pLCB7IGNhdXNlOiByZWFzb24gfSk7XG4gICAgICAgIHRoaXMucHJvbWlzZSA9IHByb21pc2U7XG4gICAgICAgIHRoaXMubmFtZSA9IFwiQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3JcIjtcbiAgICB9XG59XG5cbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlUmVzb2x2ZXI8VD4gPSAodmFsdWU6IFQgfCBQcm9taXNlTGlrZTxUPiB8IENhbmNlbGxhYmxlUHJvbWlzZUxpa2U8VD4pID0+IHZvaWQ7XG50eXBlIENhbmNlbGxhYmxlUHJvbWlzZVJlamVjdG9yID0gKHJlYXNvbj86IGFueSkgPT4gdm9pZDtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlQ2FuY2VsbGVyID0gKGNhdXNlPzogYW55KSA9PiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG50eXBlIENhbmNlbGxhYmxlUHJvbWlzZUV4ZWN1dG9yPFQ+ID0gKHJlc29sdmU6IENhbmNlbGxhYmxlUHJvbWlzZVJlc29sdmVyPFQ+LCByZWplY3Q6IENhbmNlbGxhYmxlUHJvbWlzZVJlamVjdG9yKSA9PiB2b2lkO1xuXG5leHBvcnQgaW50ZXJmYWNlIENhbmNlbGxhYmxlUHJvbWlzZUxpa2U8VD4ge1xuICAgIHRoZW48VFJlc3VsdDEgPSBULCBUUmVzdWx0MiA9IG5ldmVyPihvbmZ1bGZpbGxlZD86ICgodmFsdWU6IFQpID0+IFRSZXN1bHQxIHwgUHJvbWlzZUxpa2U8VFJlc3VsdDE+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0MT4pIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25yZWplY3RlZD86ICgocmVhc29uOiBhbnkpID0+IFRSZXN1bHQyIHwgUHJvbWlzZUxpa2U8VFJlc3VsdDI+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0Mj4pIHwgdW5kZWZpbmVkIHwgbnVsbCk6IENhbmNlbGxhYmxlUHJvbWlzZUxpa2U8VFJlc3VsdDEgfCBUUmVzdWx0Mj47XG4gICAgY2FuY2VsKGNhdXNlPzogYW55KTogdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+O1xufVxuXG4vKipcbiAqIFdyYXBzIGEgY2FuY2VsbGFibGUgcHJvbWlzZSBhbG9uZyB3aXRoIGl0cyByZXNvbHV0aW9uIG1ldGhvZHMuXG4gKiBUaGUgYG9uY2FuY2VsbGVkYCBmaWVsZCB3aWxsIGJlIG51bGwgaW5pdGlhbGx5IGJ1dCBtYXkgYmUgc2V0IHRvIHByb3ZpZGUgYSBjdXN0b20gY2FuY2VsbGF0aW9uIGZ1bmN0aW9uLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4ge1xuICAgIHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZTxUPjtcbiAgICByZXNvbHZlOiBDYW5jZWxsYWJsZVByb21pc2VSZXNvbHZlcjxUPjtcbiAgICByZWplY3Q6IENhbmNlbGxhYmxlUHJvbWlzZVJlamVjdG9yO1xuICAgIG9uY2FuY2VsbGVkOiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIgfCBudWxsO1xufVxuXG5pbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUge1xuICAgIHJlYWRvbmx5IHJvb3Q6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlO1xuICAgIHJlc29sdmluZzogYm9vbGVhbjtcbiAgICBzZXR0bGVkOiBib29sZWFuO1xuICAgIHJlYXNvbj86IENhbmNlbEVycm9yO1xufVxuXG4vLyBQcml2YXRlIGZpZWxkIG5hbWVzLlxuY29uc3QgYmFycmllclN5bSA9IFN5bWJvbChcImJhcnJpZXJcIik7XG5jb25zdCBjYW5jZWxJbXBsU3ltID0gU3ltYm9sKFwiY2FuY2VsSW1wbFwiKTtcbmNvbnN0IHNwZWNpZXM6IHR5cGVvZiBTeW1ib2wuc3BlY2llcyA9IFN5bWJvbC5zcGVjaWVzID8/IFN5bWJvbChcInNwZWNpZXNQb2x5ZmlsbFwiKTtcblxuLyoqXG4gKiBBIHByb21pc2Ugd2l0aCBhbiBhdHRhY2hlZCBtZXRob2QgZm9yIGNhbmNlbGxpbmcgbG9uZy1ydW5uaW5nIG9wZXJhdGlvbnMgKHNlZSB7QGxpbmsgQ2FuY2VsbGFibGVQcm9taXNlI2NhbmNlbH0pLlxuICogQ2FuY2VsbGF0aW9uIGNhbiBvcHRpb25hbGx5IGJlIGJvdW5kIHRvIGFuIHtAbGluayBBYm9ydFNpZ25hbH1cbiAqIGZvciBiZXR0ZXIgY29tcG9zYWJpbGl0eSAoc2VlIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2UjY2FuY2VsT259KS5cbiAqXG4gKiBDYW5jZWxsaW5nIGEgcGVuZGluZyBwcm9taXNlIHdpbGwgcmVzdWx0IGluIGFuIGltbWVkaWF0ZSByZWplY3Rpb25cbiAqIHdpdGggYW4gaW5zdGFuY2Ugb2Yge0BsaW5rIENhbmNlbEVycm9yfSBhcyByZWFzb24sXG4gKiBidXQgd2hvZXZlciBzdGFydGVkIHRoZSBwcm9taXNlIHdpbGwgYmUgcmVzcG9uc2libGVcbiAqIGZvciBhY3R1YWxseSBhYm9ydGluZyB0aGUgdW5kZXJseWluZyBvcGVyYXRpb24uXG4gKiBUbyB0aGlzIHB1cnBvc2UsIHRoZSBjb25zdHJ1Y3RvciBhbmQgYWxsIGNoYWluaW5nIG1ldGhvZHNcbiAqIGFjY2VwdCBvcHRpb25hbCBjYW5jZWxsYXRpb24gY2FsbGJhY2tzLlxuICpcbiAqIElmIGEgYENhbmNlbGxhYmxlUHJvbWlzZWAgc3RpbGwgcmVzb2x2ZXMgYWZ0ZXIgaGF2aW5nIGJlZW4gY2FuY2VsbGVkLFxuICogdGhlIHJlc3VsdCB3aWxsIGJlIGRpc2NhcmRlZC4gSWYgaXQgcmVqZWN0cywgdGhlIHJlYXNvblxuICogd2lsbCBiZSByZXBvcnRlZCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLFxuICogd3JhcHBlZCBpbiBhIHtAbGluayBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcn0gaW5zdGFuY2UuXG4gKiBUbyBmYWNpbGl0YXRlIHRoZSBoYW5kbGluZyBvZiBjYW5jZWxsYXRpb24gcmVxdWVzdHMsXG4gKiBjYW5jZWxsZWQgYENhbmNlbGxhYmxlUHJvbWlzZWBzIHdpbGwgX25vdF8gcmVwb3J0IHVuaGFuZGxlZCBgQ2FuY2VsRXJyb3Jgc1xuICogd2hvc2UgYGNhdXNlYCBmaWVsZCBpcyB0aGUgc2FtZSBhcyB0aGUgb25lIHdpdGggd2hpY2ggdGhlIGN1cnJlbnQgcHJvbWlzZSB3YXMgY2FuY2VsbGVkLlxuICpcbiAqIEFsbCB1c3VhbCBwcm9taXNlIG1ldGhvZHMgYXJlIGRlZmluZWQgYW5kIHJldHVybiBhIGBDYW5jZWxsYWJsZVByb21pc2VgXG4gKiB3aG9zZSBjYW5jZWwgbWV0aG9kIHdpbGwgY2FuY2VsIHRoZSBwYXJlbnQgb3BlcmF0aW9uIGFzIHdlbGwsIHByb3BhZ2F0aW5nIHRoZSBjYW5jZWxsYXRpb24gcmVhc29uXG4gKiB1cHdhcmRzIHRocm91Z2ggcHJvbWlzZSBjaGFpbnMuXG4gKiBDb252ZXJzZWx5LCBjYW5jZWxsaW5nIGEgcHJvbWlzZSB3aWxsIG5vdCBhdXRvbWF0aWNhbGx5IGNhbmNlbCBkZXBlbmRlbnQgcHJvbWlzZXMgZG93bnN0cmVhbTpcbiAqIGBgYHRzXG4gKiBsZXQgcm9vdCA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4geyAuLi4gfSk7XG4gKiBsZXQgY2hpbGQxID0gcm9vdC50aGVuKCgpID0+IHsgLi4uIH0pO1xuICogbGV0IGNoaWxkMiA9IGNoaWxkMS50aGVuKCgpID0+IHsgLi4uIH0pO1xuICogbGV0IGNoaWxkMyA9IHJvb3QuY2F0Y2goKCkgPT4geyAuLi4gfSk7XG4gKiBjaGlsZDEuY2FuY2VsKCk7IC8vIENhbmNlbHMgY2hpbGQxIGFuZCByb290LCBidXQgbm90IGNoaWxkMiBvciBjaGlsZDNcbiAqIGBgYFxuICogQ2FuY2VsbGluZyBhIHByb21pc2UgdGhhdCBoYXMgYWxyZWFkeSBzZXR0bGVkIGlzIHNhZmUgYW5kIGhhcyBubyBjb25zZXF1ZW5jZS5cbiAqXG4gKiBUaGUgYGNhbmNlbGAgbWV0aG9kIHJldHVybnMgYSBwcm9taXNlIHRoYXQgX2Fsd2F5cyBmdWxmaWxsc19cbiAqIGFmdGVyIHRoZSB3aG9sZSBjaGFpbiBoYXMgcHJvY2Vzc2VkIHRoZSBjYW5jZWwgcmVxdWVzdFxuICogYW5kIGFsbCBhdHRhY2hlZCBjYWxsYmFja3MgdXAgdG8gdGhhdCBtb21lbnQgaGF2ZSBydW4uXG4gKlxuICogQWxsIEVTMjAyNCBwcm9taXNlIG1ldGhvZHMgKHN0YXRpYyBhbmQgaW5zdGFuY2UpIGFyZSBkZWZpbmVkIG9uIENhbmNlbGxhYmxlUHJvbWlzZSxcbiAqIGJ1dCBhY3R1YWwgYXZhaWxhYmlsaXR5IG1heSB2YXJ5IHdpdGggT1Mvd2VidmlldyB2ZXJzaW9uLlxuICpcbiAqIEluIGxpbmUgd2l0aCB0aGUgcHJvcG9zYWwgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RjMzkvcHJvcG9zYWwtcm0tYnVpbHRpbi1zdWJjbGFzc2luZyxcbiAqIGBDYW5jZWxsYWJsZVByb21pc2VgIGRvZXMgbm90IHN1cHBvcnQgdHJhbnNwYXJlbnQgc3ViY2xhc3NpbmcuXG4gKiBFeHRlbmRlcnMgc2hvdWxkIHRha2UgY2FyZSB0byBwcm92aWRlIHRoZWlyIG93biBtZXRob2QgaW1wbGVtZW50YXRpb25zLlxuICogVGhpcyBtaWdodCBiZSByZWNvbnNpZGVyZWQgaW4gY2FzZSB0aGUgcHJvcG9zYWwgaXMgcmV0aXJlZC5cbiAqXG4gKiBDYW5jZWxsYWJsZVByb21pc2UgaXMgYSB3cmFwcGVyIGFyb3VuZCB0aGUgRE9NIFByb21pc2Ugb2JqZWN0XG4gKiBhbmQgaXMgY29tcGxpYW50IHdpdGggdGhlIFtQcm9taXNlcy9BKyBzcGVjaWZpY2F0aW9uXShodHRwczovL3Byb21pc2VzYXBsdXMuY29tLylcbiAqIChpdCBwYXNzZXMgdGhlIFtjb21wbGlhbmNlIHN1aXRlXShodHRwczovL2dpdGh1Yi5jb20vcHJvbWlzZXMtYXBsdXMvcHJvbWlzZXMtdGVzdHMpKVxuICogaWYgc28gaXMgdGhlIHVuZGVybHlpbmcgaW1wbGVtZW50YXRpb24uXG4gKi9cbmV4cG9ydCBjbGFzcyBDYW5jZWxsYWJsZVByb21pc2U8VD4gZXh0ZW5kcyBQcm9taXNlPFQ+IGltcGxlbWVudHMgUHJvbWlzZUxpa2U8VD4sIENhbmNlbGxhYmxlUHJvbWlzZUxpa2U8VD4ge1xuICAgIC8vIFByaXZhdGUgZmllbGRzLlxuICAgIC8qKiBAaW50ZXJuYWwgKi9cbiAgICBwcml2YXRlIFtiYXJyaWVyU3ltXSE6IFBhcnRpYWw8UHJvbWlzZVdpdGhSZXNvbHZlcnM8dm9pZD4+IHwgbnVsbDtcbiAgICAvKiogQGludGVybmFsICovXG4gICAgcHJpdmF0ZSByZWFkb25seSBbY2FuY2VsSW1wbFN5bV0hOiAocmVhc29uOiBDYW5jZWxFcnJvcikgPT4gdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+O1xuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBgQ2FuY2VsbGFibGVQcm9taXNlYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBleGVjdXRvciAtIEEgY2FsbGJhY2sgdXNlZCB0byBpbml0aWFsaXplIHRoZSBwcm9taXNlLiBUaGlzIGNhbGxiYWNrIGlzIHBhc3NlZCB0d28gYXJndW1lbnRzOlxuICAgICAqICAgICAgICAgICAgICAgICAgIGEgYHJlc29sdmVgIGNhbGxiYWNrIHVzZWQgdG8gcmVzb2x2ZSB0aGUgcHJvbWlzZSB3aXRoIGEgdmFsdWVcbiAgICAgKiAgICAgICAgICAgICAgICAgICBvciB0aGUgcmVzdWx0IG9mIGFub3RoZXIgcHJvbWlzZSAocG9zc2libHkgY2FuY2VsbGFibGUpLFxuICAgICAqICAgICAgICAgICAgICAgICAgIGFuZCBhIGByZWplY3RgIGNhbGxiYWNrIHVzZWQgdG8gcmVqZWN0IHRoZSBwcm9taXNlIHdpdGggYSBwcm92aWRlZCByZWFzb24gb3IgZXJyb3IuXG4gICAgICogICAgICAgICAgICAgICAgICAgSWYgdGhlIHZhbHVlIHByb3ZpZGVkIHRvIHRoZSBgcmVzb2x2ZWAgY2FsbGJhY2sgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiAgICAgICAgICAgICAgICAgICAoaXQgaGFzIGEgYHRoZW5gIF9hbmRfIGEgYGNhbmNlbGAgbWV0aG9kKSxcbiAgICAgKiAgICAgICAgICAgICAgICAgICBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBmb3J3YXJkZWQgdG8gdGhhdCBvYmplY3QgYW5kIHRoZSBvbmNhbmNlbGxlZCB3aWxsIG5vdCBiZSBpbnZva2VkIGFueW1vcmUuXG4gICAgICogICAgICAgICAgICAgICAgICAgSWYgYW55IG9uZSBvZiB0aGUgdHdvIGNhbGxiYWNrcyBpcyBjYWxsZWQgX2FmdGVyXyB0aGUgcHJvbWlzZSBoYXMgYmVlbiBjYW5jZWxsZWQsXG4gICAgICogICAgICAgICAgICAgICAgICAgdGhlIHByb3ZpZGVkIHZhbHVlcyB3aWxsIGJlIGNhbmNlbGxlZCBhbmQgcmVzb2x2ZWQgYXMgdXN1YWwsXG4gICAgICogICAgICAgICAgICAgICAgICAgYnV0IHRoZWlyIHJlc3VsdHMgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICogICAgICAgICAgICAgICAgICAgSG93ZXZlciwgaWYgdGhlIHJlc29sdXRpb24gcHJvY2VzcyB1bHRpbWF0ZWx5IGVuZHMgdXAgaW4gYSByZWplY3Rpb25cbiAgICAgKiAgICAgICAgICAgICAgICAgICB0aGF0IGlzIG5vdCBkdWUgdG8gY2FuY2VsbGF0aW9uLCB0aGUgcmVqZWN0aW9uIHJlYXNvblxuICAgICAqICAgICAgICAgICAgICAgICAgIHdpbGwgYmUgd3JhcHBlZCBpbiBhIHtAbGluayBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcn1cbiAgICAgKiAgICAgICAgICAgICAgICAgICBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqIEBwYXJhbSBvbmNhbmNlbGxlZCAtIEl0IGlzIHRoZSBjYWxsZXIncyByZXNwb25zaWJpbGl0eSB0byBlbnN1cmUgdGhhdCBhbnkgb3BlcmF0aW9uXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgc3RhcnRlZCBieSB0aGUgZXhlY3V0b3IgaXMgcHJvcGVybHkgaGFsdGVkIHVwb24gY2FuY2VsbGF0aW9uLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIFRoaXMgb3B0aW9uYWwgY2FsbGJhY2sgY2FuIGJlIHVzZWQgdG8gdGhhdCBwdXJwb3NlLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIEl0IHdpbGwgYmUgY2FsbGVkIF9zeW5jaHJvbm91c2x5XyB3aXRoIGEgY2FuY2VsbGF0aW9uIGNhdXNlXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgd2hlbiBjYW5jZWxsYXRpb24gaXMgcmVxdWVzdGVkLCBfYWZ0ZXJfIHRoZSBwcm9taXNlIGhhcyBhbHJlYWR5IHJlamVjdGVkXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgd2l0aCBhIHtAbGluayBDYW5jZWxFcnJvcn0sIGJ1dCBfYmVmb3JlX1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgIGFueSB7QGxpbmsgdGhlbn0ve0BsaW5rIGNhdGNofS97QGxpbmsgZmluYWxseX0gY2FsbGJhY2sgcnVucy5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBJZiB0aGUgY2FsbGJhY2sgcmV0dXJucyBhIHRoZW5hYmxlLCB0aGUgcHJvbWlzZSByZXR1cm5lZCBmcm9tIHtAbGluayBjYW5jZWx9XG4gICAgICogICAgICAgICAgICAgICAgICAgICAgd2lsbCBvbmx5IGZ1bGZpbGwgYWZ0ZXIgdGhlIGZvcm1lciBoYXMgc2V0dGxlZC5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBVbmhhbmRsZWQgZXhjZXB0aW9ucyBvciByZWplY3Rpb25zIGZyb20gdGhlIGNhbGxiYWNrIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyB1bmhhbmRsZWQgcmVqZWN0aW9ucy5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBJZiB0aGUgYHJlc29sdmVgIGNhbGxiYWNrIGlzIGNhbGxlZCBiZWZvcmUgY2FuY2VsbGF0aW9uIHdpdGggYSBjYW5jZWxsYWJsZSBwcm9taXNlLFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyBvbiB0aGlzIHByb21pc2Ugd2lsbCBiZSBkaXZlcnRlZCB0byB0aGF0IHByb21pc2UsXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgYW5kIHRoZSBvcmlnaW5hbCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGV4ZWN1dG9yOiBDYW5jZWxsYWJsZVByb21pc2VFeGVjdXRvcjxUPiwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpIHtcbiAgICAgICAgbGV0IHJlc29sdmUhOiAodmFsdWU6IFQgfCBQcm9taXNlTGlrZTxUPikgPT4gdm9pZDtcbiAgICAgICAgbGV0IHJlamVjdCE6IChyZWFzb24/OiBhbnkpID0+IHZvaWQ7XG4gICAgICAgIHN1cGVyKChyZXMsIHJlaikgPT4geyByZXNvbHZlID0gcmVzOyByZWplY3QgPSByZWo7IH0pO1xuXG4gICAgICAgIGlmICgodGhpcy5jb25zdHJ1Y3RvciBhcyBhbnkpW3NwZWNpZXNdICE9PSBQcm9taXNlKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2FuY2VsbGFibGVQcm9taXNlIGRvZXMgbm90IHN1cHBvcnQgdHJhbnNwYXJlbnQgc3ViY2xhc3NpbmcuIFBsZWFzZSByZWZyYWluIGZyb20gb3ZlcnJpZGluZyB0aGUgW1N5bWJvbC5zcGVjaWVzXSBzdGF0aWMgcHJvcGVydHkuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgbGV0IHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4gPSB7XG4gICAgICAgICAgICBwcm9taXNlOiB0aGlzLFxuICAgICAgICAgICAgcmVzb2x2ZSxcbiAgICAgICAgICAgIHJlamVjdCxcbiAgICAgICAgICAgIGdldCBvbmNhbmNlbGxlZCgpIHsgcmV0dXJuIG9uY2FuY2VsbGVkID8/IG51bGw7IH0sXG4gICAgICAgICAgICBzZXQgb25jYW5jZWxsZWQoY2IpIHsgb25jYW5jZWxsZWQgPSBjYiA/PyB1bmRlZmluZWQ7IH1cbiAgICAgICAgfTtcblxuICAgICAgICBjb25zdCBzdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUgPSB7XG4gICAgICAgICAgICBnZXQgcm9vdCgpIHsgcmV0dXJuIHN0YXRlOyB9LFxuICAgICAgICAgICAgcmVzb2x2aW5nOiBmYWxzZSxcbiAgICAgICAgICAgIHNldHRsZWQ6IGZhbHNlXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gU2V0dXAgY2FuY2VsbGF0aW9uIHN5c3RlbS5cbiAgICAgICAgdm9pZCBPYmplY3QuZGVmaW5lUHJvcGVydGllcyh0aGlzLCB7XG4gICAgICAgICAgICBbYmFycmllclN5bV06IHtcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHdyaXRhYmxlOiB0cnVlLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBudWxsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgW2NhbmNlbEltcGxTeW1dOiB7XG4gICAgICAgICAgICAgICAgY29uZmlndXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgdmFsdWU6IGNhbmNlbGxlckZvcihwcm9taXNlLCBzdGF0ZSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gUnVuIHRoZSBhY3R1YWwgZXhlY3V0b3IuXG4gICAgICAgIGNvbnN0IHJlamVjdG9yID0gcmVqZWN0b3JGb3IocHJvbWlzZSwgc3RhdGUpO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgZXhlY3V0b3IocmVzb2x2ZXJGb3IocHJvbWlzZSwgc3RhdGUpLCByZWplY3Rvcik7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgaWYgKHN0YXRlLnJlc29sdmluZykge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiVW5oYW5kbGVkIGV4Y2VwdGlvbiBpbiBDYW5jZWxsYWJsZVByb21pc2UgZXhlY3V0b3IuXCIsIGVycik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHJlamVjdG9yKGVycik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDYW5jZWxzIGltbWVkaWF0ZWx5IHRoZSBleGVjdXRpb24gb2YgdGhlIG9wZXJhdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhpcyBwcm9taXNlLlxuICAgICAqIFRoZSBwcm9taXNlIHJlamVjdHMgd2l0aCBhIHtAbGluayBDYW5jZWxFcnJvcn0gaW5zdGFuY2UgYXMgcmVhc29uLFxuICAgICAqIHdpdGggdGhlIHtAbGluayBDYW5jZWxFcnJvciNjYXVzZX0gcHJvcGVydHkgc2V0IHRvIHRoZSBnaXZlbiBhcmd1bWVudCwgaWYgYW55LlxuICAgICAqXG4gICAgICogSGFzIG5vIGVmZmVjdCBpZiBjYWxsZWQgYWZ0ZXIgdGhlIHByb21pc2UgaGFzIGFscmVhZHkgc2V0dGxlZDtcbiAgICAgKiByZXBlYXRlZCBjYWxscyBpbiBwYXJ0aWN1bGFyIGFyZSBzYWZlLCBidXQgb25seSB0aGUgZmlyc3Qgb25lXG4gICAgICogd2lsbCBzZXQgdGhlIGNhbmNlbGxhdGlvbiBjYXVzZS5cbiAgICAgKlxuICAgICAqIFRoZSBgQ2FuY2VsRXJyb3JgIGV4Y2VwdGlvbiBfbmVlZCBub3RfIGJlIGhhbmRsZWQgZXhwbGljaXRseSBfb24gdGhlIHByb21pc2VzIHRoYXQgYXJlIGJlaW5nIGNhbmNlbGxlZDpfXG4gICAgICogY2FuY2VsbGluZyBhIHByb21pc2Ugd2l0aCBubyBhdHRhY2hlZCByZWplY3Rpb24gaGFuZGxlciBkb2VzIG5vdCB0cmlnZ2VyIGFuIHVuaGFuZGxlZCByZWplY3Rpb24gZXZlbnQuXG4gICAgICogVGhlcmVmb3JlLCB0aGUgZm9sbG93aW5nIGlkaW9tcyBhcmUgYWxsIGVxdWFsbHkgY29ycmVjdDpcbiAgICAgKiBgYGB0c1xuICAgICAqIG5ldyBDYW5jZWxsYWJsZVByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4geyAuLi4gfSkuY2FuY2VsKCk7XG4gICAgICogbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KS50aGVuKC4uLikuY2FuY2VsKCk7XG4gICAgICogbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KS50aGVuKC4uLikuY2F0Y2goLi4uKS5jYW5jZWwoKTtcbiAgICAgKiBgYGBcbiAgICAgKiBXaGVuZXZlciBzb21lIGNhbmNlbGxlZCBwcm9taXNlIGluIGEgY2hhaW4gcmVqZWN0cyB3aXRoIGEgYENhbmNlbEVycm9yYFxuICAgICAqIHdpdGggdGhlIHNhbWUgY2FuY2VsbGF0aW9uIGNhdXNlIGFzIGl0c2VsZiwgdGhlIGVycm9yIHdpbGwgYmUgZGlzY2FyZGVkIHNpbGVudGx5LlxuICAgICAqIEhvd2V2ZXIsIHRoZSBgQ2FuY2VsRXJyb3JgIF93aWxsIHN0aWxsIGJlIGRlbGl2ZXJlZF8gdG8gYWxsIGF0dGFjaGVkIHJlamVjdGlvbiBoYW5kbGVyc1xuICAgICAqIGFkZGVkIGJ5IHtAbGluayB0aGVufSBhbmQgcmVsYXRlZCBtZXRob2RzOlxuICAgICAqIGBgYHRzXG4gICAgICogbGV0IGNhbmNlbGxhYmxlID0gbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KTtcbiAgICAgKiBjYW5jZWxsYWJsZS50aGVuKCgpID0+IHsgLi4uIH0pLmNhdGNoKGNvbnNvbGUubG9nKTtcbiAgICAgKiBjYW5jZWxsYWJsZS5jYW5jZWwoKTsgLy8gQSBDYW5jZWxFcnJvciBpcyBwcmludGVkIHRvIHRoZSBjb25zb2xlLlxuICAgICAqIGBgYFxuICAgICAqIElmIHRoZSBgQ2FuY2VsRXJyb3JgIGlzIG5vdCBoYW5kbGVkIGRvd25zdHJlYW0gYnkgdGhlIHRpbWUgaXQgcmVhY2hlc1xuICAgICAqIGEgX25vbi1jYW5jZWxsZWRfIHByb21pc2UsIGl0IF93aWxsXyB0cmlnZ2VyIGFuIHVuaGFuZGxlZCByZWplY3Rpb24gZXZlbnQsXG4gICAgICoganVzdCBsaWtlIG5vcm1hbCByZWplY3Rpb25zIHdvdWxkOlxuICAgICAqIGBgYHRzXG4gICAgICogbGV0IGNhbmNlbGxhYmxlID0gbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KTtcbiAgICAgKiBsZXQgY2hhaW5lZCA9IGNhbmNlbGxhYmxlLnRoZW4oKCkgPT4geyAuLi4gfSkudGhlbigoKSA9PiB7IC4uLiB9KTsgLy8gTm8gY2F0Y2guLi5cbiAgICAgKiBjYW5jZWxsYWJsZS5jYW5jZWwoKTsgLy8gVW5oYW5kbGVkIHJlamVjdGlvbiBldmVudCBvbiBjaGFpbmVkIVxuICAgICAqIGBgYFxuICAgICAqIFRoZXJlZm9yZSwgaXQgaXMgaW1wb3J0YW50IHRvIGVpdGhlciBjYW5jZWwgd2hvbGUgcHJvbWlzZSBjaGFpbnMgZnJvbSB0aGVpciB0YWlsLFxuICAgICAqIGFzIHNob3duIGluIHRoZSBjb3JyZWN0IGlkaW9tcyBhYm92ZSwgb3IgdGFrZSBjYXJlIG9mIGhhbmRsaW5nIGVycm9ycyBldmVyeXdoZXJlLlxuICAgICAqXG4gICAgICogQHJldHVybnMgQSBjYW5jZWxsYWJsZSBwcm9taXNlIHRoYXQgX2Z1bGZpbGxzXyBhZnRlciB0aGUgY2FuY2VsIGNhbGxiYWNrIChpZiBhbnkpXG4gICAgICogYW5kIGFsbCBoYW5kbGVycyBhdHRhY2hlZCB1cCB0byB0aGUgY2FsbCB0byBjYW5jZWwgaGF2ZSBydW4uXG4gICAgICogSWYgdGhlIGNhbmNlbCBjYWxsYmFjayByZXR1cm5zIGEgdGhlbmFibGUsIHRoZSBwcm9taXNlIHJldHVybmVkIGJ5IGBjYW5jZWxgXG4gICAgICogd2lsbCBhbHNvIHdhaXQgZm9yIHRoYXQgdGhlbmFibGUgdG8gc2V0dGxlLlxuICAgICAqIFRoaXMgZW5hYmxlcyBjYWxsZXJzIHRvIHdhaXQgZm9yIHRoZSBjYW5jZWxsZWQgb3BlcmF0aW9uIHRvIHRlcm1pbmF0ZVxuICAgICAqIHdpdGhvdXQgYmVpbmcgZm9yY2VkIHRvIGhhbmRsZSBwb3RlbnRpYWwgZXJyb3JzIGF0IHRoZSBjYWxsIHNpdGUuXG4gICAgICogYGBgdHNcbiAgICAgKiBjYW5jZWxsYWJsZS5jYW5jZWwoKS50aGVuKCgpID0+IHtcbiAgICAgKiAgICAgLy8gQ2xlYW51cCBmaW5pc2hlZCwgaXQncyBzYWZlIHRvIGRvIHNvbWV0aGluZyBlbHNlLlxuICAgICAqIH0sIChlcnIpID0+IHtcbiAgICAgKiAgICAgLy8gVW5yZWFjaGFibGU6IHRoZSBwcm9taXNlIHJldHVybmVkIGZyb20gY2FuY2VsIHdpbGwgbmV2ZXIgcmVqZWN0LlxuICAgICAqIH0pO1xuICAgICAqIGBgYFxuICAgICAqIE5vdGUgdGhhdCB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIF9ub3RfIGhhbmRsZSBpbXBsaWNpdGx5IGFueSByZWplY3Rpb25cbiAgICAgKiB0aGF0IG1pZ2h0IGhhdmUgb2NjdXJyZWQgYWxyZWFkeSBpbiB0aGUgY2FuY2VsbGVkIGNoYWluLlxuICAgICAqIEl0IHdpbGwganVzdCB0cmFjayB3aGV0aGVyIHJlZ2lzdGVyZWQgaGFuZGxlcnMgaGF2ZSBiZWVuIGV4ZWN1dGVkIG9yIG5vdC5cbiAgICAgKiBUaGVyZWZvcmUsIHVuaGFuZGxlZCByZWplY3Rpb25zIHdpbGwgbmV2ZXIgYmUgc2lsZW50bHkgaGFuZGxlZCBieSBjYWxsaW5nIGNhbmNlbC5cbiAgICAgKi9cbiAgICBjYW5jZWwoY2F1c2U/OiBhbnkpOiBDYW5jZWxsYWJsZVByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPigocmVzb2x2ZSkgPT4ge1xuICAgICAgICAgICAgLy8gSU5WQVJJQU5UOiB0aGUgcmVzdWx0IG9mIHRoaXNbY2FuY2VsSW1wbFN5bV0gYW5kIHRoZSBiYXJyaWVyIGRvIG5vdCBldmVyIHJlamVjdC5cbiAgICAgICAgICAgIC8vIFVuZm9ydHVuYXRlbHkgbWFjT1MgSGlnaCBTaWVycmEgZG9lcyBub3Qgc3VwcG9ydCBQcm9taXNlLmFsbFNldHRsZWQuXG4gICAgICAgICAgICBQcm9taXNlLmFsbChbXG4gICAgICAgICAgICAgICAgdGhpc1tjYW5jZWxJbXBsU3ltXShuZXcgQ2FuY2VsRXJyb3IoXCJQcm9taXNlIGNhbmNlbGxlZC5cIiwgeyBjYXVzZSB9KSksXG4gICAgICAgICAgICAgICAgY3VycmVudEJhcnJpZXIodGhpcylcbiAgICAgICAgICAgIF0pLnRoZW4oKCkgPT4gcmVzb2x2ZSgpLCAoKSA9PiByZXNvbHZlKCkpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBCaW5kcyBwcm9taXNlIGNhbmNlbGxhdGlvbiB0byB0aGUgYWJvcnQgZXZlbnQgb2YgdGhlIGdpdmVuIHtAbGluayBBYm9ydFNpZ25hbH0uXG4gICAgICogSWYgdGhlIHNpZ25hbCBoYXMgYWxyZWFkeSBhYm9ydGVkLCB0aGUgcHJvbWlzZSB3aWxsIGJlIGNhbmNlbGxlZCBpbW1lZGlhdGVseS5cbiAgICAgKiBXaGVuIGVpdGhlciBjb25kaXRpb24gaXMgdmVyaWZpZWQsIHRoZSBjYW5jZWxsYXRpb24gY2F1c2Ugd2lsbCBiZSBzZXRcbiAgICAgKiB0byB0aGUgc2lnbmFsJ3MgYWJvcnQgcmVhc29uIChzZWUge0BsaW5rIEFib3J0U2lnbmFsI3JlYXNvbn0pLlxuICAgICAqXG4gICAgICogSGFzIG5vIGVmZmVjdCBpZiBjYWxsZWQgKG9yIGlmIHRoZSBzaWduYWwgYWJvcnRzKSBfYWZ0ZXJfIHRoZSBwcm9taXNlIGhhcyBhbHJlYWR5IHNldHRsZWQuXG4gICAgICogT25seSB0aGUgZmlyc3Qgc2lnbmFsIHRvIGFib3J0IHdpbGwgc2V0IHRoZSBjYW5jZWxsYXRpb24gY2F1c2UuXG4gICAgICpcbiAgICAgKiBGb3IgbW9yZSBkZXRhaWxzIGFib3V0IHRoZSBjYW5jZWxsYXRpb24gcHJvY2VzcyxcbiAgICAgKiBzZWUge0BsaW5rIGNhbmNlbH0gYW5kIHRoZSBgQ2FuY2VsbGFibGVQcm9taXNlYCBjb25zdHJ1Y3Rvci5cbiAgICAgKlxuICAgICAqIFRoaXMgbWV0aG9kIGVuYWJsZXMgYGF3YWl0YGluZyBjYW5jZWxsYWJsZSBwcm9taXNlcyB3aXRob3V0IGhhdmluZ1xuICAgICAqIHRvIHN0b3JlIHRoZW0gZm9yIGZ1dHVyZSBjYW5jZWxsYXRpb24sIGUuZy46XG4gICAgICogYGBgdHNcbiAgICAgKiBhd2FpdCBsb25nUnVubmluZ09wZXJhdGlvbigpLmNhbmNlbE9uKHNpZ25hbCk7XG4gICAgICogYGBgXG4gICAgICogaW5zdGVhZCBvZjpcbiAgICAgKiBgYGB0c1xuICAgICAqIGxldCBwcm9taXNlVG9CZUNhbmNlbGxlZCA9IGxvbmdSdW5uaW5nT3BlcmF0aW9uKCk7XG4gICAgICogYXdhaXQgcHJvbWlzZVRvQmVDYW5jZWxsZWQ7XG4gICAgICogYGBgXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGlzIHByb21pc2UsIGZvciBtZXRob2QgY2hhaW5pbmcuXG4gICAgICovXG4gICAgY2FuY2VsT24oc2lnbmFsOiBBYm9ydFNpZ25hbCk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIGlmIChzaWduYWwuYWJvcnRlZCkge1xuICAgICAgICAgICAgdm9pZCB0aGlzLmNhbmNlbChzaWduYWwucmVhc29uKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc2lnbmFsLmFkZEV2ZW50TGlzdGVuZXIoJ2Fib3J0JywgKCkgPT4gdm9pZCB0aGlzLmNhbmNlbChzaWduYWwucmVhc29uKSwge2NhcHR1cmU6IHRydWV9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF0dGFjaGVzIGNhbGxiYWNrcyBmb3IgdGhlIHJlc29sdXRpb24gYW5kL29yIHJlamVjdGlvbiBvZiB0aGUgYENhbmNlbGxhYmxlUHJvbWlzZWAuXG4gICAgICpcbiAgICAgKiBUaGUgb3B0aW9uYWwgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCB3aWxsIGJlIGludm9rZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsXG4gICAgICogd2l0aCB0aGUgc2FtZSBzZW1hbnRpY3MgYXMgdGhlIGBvbmNhbmNlbGxlZGAgYXJndW1lbnQgb2YgdGhlIGNvbnN0cnVjdG9yLlxuICAgICAqIFdoZW4gdGhlIHBhcmVudCBwcm9taXNlIHJlamVjdHMgb3IgaXMgY2FuY2VsbGVkLCB0aGUgYG9ucmVqZWN0ZWRgIGNhbGxiYWNrIHdpbGwgcnVuLFxuICAgICAqIF9ldmVuIGFmdGVyIHRoZSByZXR1cm5lZCBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZDpfXG4gICAgICogaW4gdGhhdCBjYXNlLCBzaG91bGQgaXQgcmVqZWN0IG9yIHRocm93LCB0aGUgcmVhc29uIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqXG4gICAgICogQHBhcmFtIG9uZnVsZmlsbGVkIFRoZSBjYWxsYmFjayB0byBleGVjdXRlIHdoZW4gdGhlIFByb21pc2UgaXMgcmVzb2x2ZWQuXG4gICAgICogQHBhcmFtIG9ucmVqZWN0ZWQgVGhlIGNhbGxiYWNrIHRvIGV4ZWN1dGUgd2hlbiB0aGUgUHJvbWlzZSBpcyByZWplY3RlZC5cbiAgICAgKiBAcmV0dXJucyBBIGBDYW5jZWxsYWJsZVByb21pc2VgIGZvciB0aGUgY29tcGxldGlvbiBvZiB3aGljaGV2ZXIgY2FsbGJhY2sgaXMgZXhlY3V0ZWQuXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2UgaXMgaG9va2VkIHVwIHRvIHByb3BhZ2F0ZSBjYW5jZWxsYXRpb24gcmVxdWVzdHMgdXAgdGhlIGNoYWluLCBidXQgbm90IGRvd246XG4gICAgICpcbiAgICAgKiAgIC0gaWYgdGhlIHBhcmVudCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgdGhlIGBvbnJlamVjdGVkYCBoYW5kbGVyIHdpbGwgYmUgaW52b2tlZCB3aXRoIGEgYENhbmNlbEVycm9yYFxuICAgICAqICAgICBhbmQgdGhlIHJldHVybmVkIHByb21pc2UgX3dpbGwgcmVzb2x2ZSByZWd1bGFybHlfIHdpdGggaXRzIHJlc3VsdDtcbiAgICAgKiAgIC0gY29udmVyc2VseSwgaWYgdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLCBfdGhlIHBhcmVudCBwcm9taXNlIGlzIGNhbmNlbGxlZCB0b287X1xuICAgICAqICAgICB0aGUgYG9ucmVqZWN0ZWRgIGhhbmRsZXIgd2lsbCBzdGlsbCBiZSBpbnZva2VkIHdpdGggdGhlIHBhcmVudCdzIGBDYW5jZWxFcnJvcmAsXG4gICAgICogICAgIGJ1dCBpdHMgcmVzdWx0IHdpbGwgYmUgZGlzY2FyZGVkXG4gICAgICogICAgIGFuZCB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIHJlamVjdCB3aXRoIGEgYENhbmNlbEVycm9yYCBhcyB3ZWxsLlxuICAgICAqXG4gICAgICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB7QGxpbmsgY2FuY2VsfSB3aWxsIGZ1bGZpbGwgb25seSBhZnRlciBhbGwgYXR0YWNoZWQgaGFuZGxlcnNcbiAgICAgKiB1cCB0aGUgZW50aXJlIHByb21pc2UgY2hhaW4gaGF2ZSBiZWVuIHJ1bi5cbiAgICAgKlxuICAgICAqIElmIGVpdGhlciBjYWxsYmFjayByZXR1cm5zIGEgY2FuY2VsbGFibGUgcHJvbWlzZSxcbiAgICAgKiBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBkaXZlcnRlZCB0byBpdCxcbiAgICAgKiBhbmQgdGhlIHNwZWNpZmllZCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqL1xuICAgIHRoZW48VFJlc3VsdDEgPSBULCBUUmVzdWx0MiA9IG5ldmVyPihvbmZ1bGZpbGxlZD86ICgodmFsdWU6IFQpID0+IFRSZXN1bHQxIHwgUHJvbWlzZUxpa2U8VFJlc3VsdDE+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0MT4pIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25yZWplY3RlZD86ICgocmVhc29uOiBhbnkpID0+IFRSZXN1bHQyIHwgUHJvbWlzZUxpa2U8VFJlc3VsdDI+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0Mj4pIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8VFJlc3VsdDEgfCBUUmVzdWx0Mj4ge1xuICAgICAgICBpZiAoISh0aGlzIGluc3RhbmNlb2YgQ2FuY2VsbGFibGVQcm9taXNlKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNhbmNlbGxhYmxlUHJvbWlzZS5wcm90b3R5cGUudGhlbiBjYWxsZWQgb24gYW4gaW52YWxpZCBvYmplY3QuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gTk9URTogVHlwZVNjcmlwdCdzIGJ1aWx0LWluIHR5cGUgZm9yIHRoZW4gaXMgYnJva2VuLFxuICAgICAgICAvLyBhcyBpdCBhbGxvd3Mgc3BlY2lmeWluZyBhbiBhcmJpdHJhcnkgVFJlc3VsdDEgIT0gVCBldmVuIHdoZW4gb25mdWxmaWxsZWQgaXMgbm90IGEgZnVuY3Rpb24uXG4gICAgICAgIC8vIFdlIGNhbm5vdCBmaXggaXQgaWYgd2Ugd2FudCB0byBDYW5jZWxsYWJsZVByb21pc2UgdG8gaW1wbGVtZW50IFByb21pc2VMaWtlPFQ+LlxuXG4gICAgICAgIGlmICghaXNDYWxsYWJsZShvbmZ1bGZpbGxlZCkpIHsgb25mdWxmaWxsZWQgPSBpZGVudGl0eSBhcyBhbnk7IH1cbiAgICAgICAgaWYgKCFpc0NhbGxhYmxlKG9ucmVqZWN0ZWQpKSB7IG9ucmVqZWN0ZWQgPSB0aHJvd2VyOyB9XG5cbiAgICAgICAgaWYgKG9uZnVsZmlsbGVkID09PSBpZGVudGl0eSAmJiBvbnJlamVjdGVkID09IHRocm93ZXIpIHtcbiAgICAgICAgICAgIC8vIFNob3J0Y3V0IGZvciB0cml2aWFsIGFyZ3VtZW50cy5cbiAgICAgICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlKSA9PiByZXNvbHZlKHRoaXMgYXMgYW55KSk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBiYXJyaWVyOiBQYXJ0aWFsPFByb21pc2VXaXRoUmVzb2x2ZXJzPHZvaWQ+PiA9IHt9O1xuICAgICAgICB0aGlzW2JhcnJpZXJTeW1dID0gYmFycmllcjtcblxuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTxUUmVzdWx0MSB8IFRSZXN1bHQyPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICB2b2lkIHN1cGVyLnRoZW4oXG4gICAgICAgICAgICAgICAgKHZhbHVlKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmICh0aGlzW2JhcnJpZXJTeW1dID09PSBiYXJyaWVyKSB7IHRoaXNbYmFycmllclN5bV0gPSBudWxsOyB9XG4gICAgICAgICAgICAgICAgICAgIGJhcnJpZXIucmVzb2x2ZT8uKCk7XG5cbiAgICAgICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc29sdmUob25mdWxmaWxsZWQhKHZhbHVlKSk7XG4gICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIChyZWFzb24/KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmICh0aGlzW2JhcnJpZXJTeW1dID09PSBiYXJyaWVyKSB7IHRoaXNbYmFycmllclN5bV0gPSBudWxsOyB9XG4gICAgICAgICAgICAgICAgICAgIGJhcnJpZXIucmVzb2x2ZT8uKCk7XG5cbiAgICAgICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc29sdmUob25yZWplY3RlZCEocmVhc29uKSk7XG4gICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICApO1xuICAgICAgICB9LCBhc3luYyAoY2F1c2U/KSA9PiB7XG4gICAgICAgICAgICAvL2NhbmNlbGxlZCA9IHRydWU7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHJldHVybiBvbmNhbmNlbGxlZD8uKGNhdXNlKTtcbiAgICAgICAgICAgIH0gZmluYWxseSB7XG4gICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5jYW5jZWwoY2F1c2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBdHRhY2hlcyBhIGNhbGxiYWNrIGZvciBvbmx5IHRoZSByZWplY3Rpb24gb2YgdGhlIFByb21pc2UuXG4gICAgICpcbiAgICAgKiBUaGUgb3B0aW9uYWwgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCB3aWxsIGJlIGludm9rZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsXG4gICAgICogd2l0aCB0aGUgc2FtZSBzZW1hbnRpY3MgYXMgdGhlIGBvbmNhbmNlbGxlZGAgYXJndW1lbnQgb2YgdGhlIGNvbnN0cnVjdG9yLlxuICAgICAqIFdoZW4gdGhlIHBhcmVudCBwcm9taXNlIHJlamVjdHMgb3IgaXMgY2FuY2VsbGVkLCB0aGUgYG9ucmVqZWN0ZWRgIGNhbGxiYWNrIHdpbGwgcnVuLFxuICAgICAqIF9ldmVuIGFmdGVyIHRoZSByZXR1cm5lZCBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZDpfXG4gICAgICogaW4gdGhhdCBjYXNlLCBzaG91bGQgaXQgcmVqZWN0IG9yIHRocm93LCB0aGUgcmVhc29uIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqXG4gICAgICogSXQgaXMgZXF1aXZhbGVudCB0b1xuICAgICAqIGBgYHRzXG4gICAgICogY2FuY2VsbGFibGVQcm9taXNlLnRoZW4odW5kZWZpbmVkLCBvbnJlamVjdGVkLCBvbmNhbmNlbGxlZCk7XG4gICAgICogYGBgXG4gICAgICogYW5kIHRoZSBzYW1lIGNhdmVhdHMgYXBwbHkuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBBIFByb21pc2UgZm9yIHRoZSBjb21wbGV0aW9uIG9mIHRoZSBjYWxsYmFjay5cbiAgICAgKiBDYW5jZWxsYXRpb24gcmVxdWVzdHMgb24gdGhlIHJldHVybmVkIHByb21pc2VcbiAgICAgKiB3aWxsIHByb3BhZ2F0ZSB1cCB0aGUgY2hhaW4gdG8gdGhlIHBhcmVudCBwcm9taXNlLFxuICAgICAqIGJ1dCBub3QgaW4gdGhlIG90aGVyIGRpcmVjdGlvbi5cbiAgICAgKlxuICAgICAqIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20ge0BsaW5rIGNhbmNlbH0gd2lsbCBmdWxmaWxsIG9ubHkgYWZ0ZXIgYWxsIGF0dGFjaGVkIGhhbmRsZXJzXG4gICAgICogdXAgdGhlIGVudGlyZSBwcm9taXNlIGNoYWluIGhhdmUgYmVlbiBydW4uXG4gICAgICpcbiAgICAgKiBJZiBgb25yZWplY3RlZGAgcmV0dXJucyBhIGNhbmNlbGxhYmxlIHByb21pc2UsXG4gICAgICogY2FuY2VsbGF0aW9uIHJlcXVlc3RzIHdpbGwgYmUgZGl2ZXJ0ZWQgdG8gaXQsXG4gICAgICogYW5kIHRoZSBzcGVjaWZpZWQgYG9uY2FuY2VsbGVkYCBjYWxsYmFjayB3aWxsIGJlIGRpc2NhcmRlZC5cbiAgICAgKiBTZWUge0BsaW5rIHRoZW59IGZvciBtb3JlIGRldGFpbHMuXG4gICAgICovXG4gICAgY2F0Y2g8VFJlc3VsdCA9IG5ldmVyPihvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gKFByb21pc2VMaWtlPFRSZXN1bHQ+IHwgVFJlc3VsdCkpIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8VCB8IFRSZXN1bHQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMudGhlbih1bmRlZmluZWQsIG9ucmVqZWN0ZWQsIG9uY2FuY2VsbGVkKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBdHRhY2hlcyBhIGNhbGxiYWNrIHRoYXQgaXMgaW52b2tlZCB3aGVuIHRoZSBDYW5jZWxsYWJsZVByb21pc2UgaXMgc2V0dGxlZCAoZnVsZmlsbGVkIG9yIHJlamVjdGVkKS4gVGhlXG4gICAgICogcmVzb2x2ZWQgdmFsdWUgY2Fubm90IGJlIGFjY2Vzc2VkIG9yIG1vZGlmaWVkIGZyb20gdGhlIGNhbGxiYWNrLlxuICAgICAqIFRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgc2V0dGxlIGluIHRoZSBzYW1lIHN0YXRlIGFzIHRoZSBvcmlnaW5hbCBvbmVcbiAgICAgKiBhZnRlciB0aGUgcHJvdmlkZWQgY2FsbGJhY2sgaGFzIGNvbXBsZXRlZCBleGVjdXRpb24sXG4gICAgICogdW5sZXNzIHRoZSBjYWxsYmFjayB0aHJvd3Mgb3IgcmV0dXJucyBhIHJlamVjdGluZyBwcm9taXNlLFxuICAgICAqIGluIHdoaWNoIGNhc2UgdGhlIHJldHVybmVkIHByb21pc2Ugd2lsbCByZWplY3QgYXMgd2VsbC5cbiAgICAgKlxuICAgICAqIFRoZSBvcHRpb25hbCBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IHdpbGwgYmUgaW52b2tlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCxcbiAgICAgKiB3aXRoIHRoZSBzYW1lIHNlbWFudGljcyBhcyB0aGUgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCBvZiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogT25jZSB0aGUgcGFyZW50IHByb21pc2Ugc2V0dGxlcywgdGhlIGBvbmZpbmFsbHlgIGNhbGxiYWNrIHdpbGwgcnVuLFxuICAgICAqIF9ldmVuIGFmdGVyIHRoZSByZXR1cm5lZCBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZDpfXG4gICAgICogaW4gdGhhdCBjYXNlLCBzaG91bGQgaXQgcmVqZWN0IG9yIHRocm93LCB0aGUgcmVhc29uIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqXG4gICAgICogVGhpcyBtZXRob2QgaXMgaW1wbGVtZW50ZWQgaW4gdGVybXMgb2Yge0BsaW5rIHRoZW59IGFuZCB0aGUgc2FtZSBjYXZlYXRzIGFwcGx5LlxuICAgICAqIEl0IGlzIHBvbHlmaWxsZWQsIGhlbmNlIGF2YWlsYWJsZSBpbiBldmVyeSBPUy93ZWJ2aWV3IHZlcnNpb24uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBBIFByb21pc2UgZm9yIHRoZSBjb21wbGV0aW9uIG9mIHRoZSBjYWxsYmFjay5cbiAgICAgKiBDYW5jZWxsYXRpb24gcmVxdWVzdHMgb24gdGhlIHJldHVybmVkIHByb21pc2VcbiAgICAgKiB3aWxsIHByb3BhZ2F0ZSB1cCB0aGUgY2hhaW4gdG8gdGhlIHBhcmVudCBwcm9taXNlLFxuICAgICAqIGJ1dCBub3QgaW4gdGhlIG90aGVyIGRpcmVjdGlvbi5cbiAgICAgKlxuICAgICAqIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20ge0BsaW5rIGNhbmNlbH0gd2lsbCBmdWxmaWxsIG9ubHkgYWZ0ZXIgYWxsIGF0dGFjaGVkIGhhbmRsZXJzXG4gICAgICogdXAgdGhlIGVudGlyZSBwcm9taXNlIGNoYWluIGhhdmUgYmVlbiBydW4uXG4gICAgICpcbiAgICAgKiBJZiBgb25maW5hbGx5YCByZXR1cm5zIGEgY2FuY2VsbGFibGUgcHJvbWlzZSxcbiAgICAgKiBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBkaXZlcnRlZCB0byBpdCxcbiAgICAgKiBhbmQgdGhlIHNwZWNpZmllZCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqIFNlZSB7QGxpbmsgdGhlbn0gZm9yIG1vcmUgZGV0YWlscy5cbiAgICAgKi9cbiAgICBmaW5hbGx5KG9uZmluYWxseT86ICgoKSA9PiB2b2lkKSB8IHVuZGVmaW5lZCB8IG51bGwsIG9uY2FuY2VsbGVkPzogQ2FuY2VsbGFibGVQcm9taXNlQ2FuY2VsbGVyKTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgaWYgKCEodGhpcyBpbnN0YW5jZW9mIENhbmNlbGxhYmxlUHJvbWlzZSkpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5jZWxsYWJsZVByb21pc2UucHJvdG90eXBlLmZpbmFsbHkgY2FsbGVkIG9uIGFuIGludmFsaWQgb2JqZWN0LlwiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghaXNDYWxsYWJsZShvbmZpbmFsbHkpKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy50aGVuKG9uZmluYWxseSwgb25maW5hbGx5LCBvbmNhbmNlbGxlZCk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy50aGVuKFxuICAgICAgICAgICAgKHZhbHVlKSA9PiBDYW5jZWxsYWJsZVByb21pc2UucmVzb2x2ZShvbmZpbmFsbHkoKSkudGhlbigoKSA9PiB2YWx1ZSksXG4gICAgICAgICAgICAocmVhc29uPykgPT4gQ2FuY2VsbGFibGVQcm9taXNlLnJlc29sdmUob25maW5hbGx5KCkpLnRoZW4oKCkgPT4geyB0aHJvdyByZWFzb247IH0pLFxuICAgICAgICAgICAgb25jYW5jZWxsZWQsXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogV2UgdXNlIHRoZSBgW1N5bWJvbC5zcGVjaWVzXWAgc3RhdGljIHByb3BlcnR5LCBpZiBhdmFpbGFibGUsXG4gICAgICogdG8gZGlzYWJsZSB0aGUgYnVpbHQtaW4gYXV0b21hdGljIHN1YmNsYXNzaW5nIGZlYXR1cmVzIGZyb20ge0BsaW5rIFByb21pc2V9LlxuICAgICAqIEl0IGlzIGNyaXRpY2FsIGZvciBwZXJmb3JtYW5jZSByZWFzb25zIHRoYXQgZXh0ZW5kZXJzIGRvIG5vdCBvdmVycmlkZSB0aGlzLlxuICAgICAqIE9uY2UgdGhlIHByb3Bvc2FsIGF0IGh0dHBzOi8vZ2l0aHViLmNvbS90YzM5L3Byb3Bvc2FsLXJtLWJ1aWx0aW4tc3ViY2xhc3NpbmdcbiAgICAgKiBpcyBlaXRoZXIgYWNjZXB0ZWQgb3IgcmV0aXJlZCwgdGhpcyBpbXBsZW1lbnRhdGlvbiB3aWxsIGhhdmUgdG8gYmUgcmV2aXNlZCBhY2NvcmRpbmdseS5cbiAgICAgKlxuICAgICAqIEBpZ25vcmVcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBzdGF0aWMgZ2V0IFtzcGVjaWVzXSgpIHtcbiAgICAgICAgcmV0dXJuIFByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IGlzIHJlc29sdmVkIHdpdGggYW4gYXJyYXkgb2YgcmVzdWx0c1xuICAgICAqIHdoZW4gYWxsIG9mIHRoZSBwcm92aWRlZCBQcm9taXNlcyByZXNvbHZlLCBvciByZWplY3RlZCB3aGVuIGFueSBQcm9taXNlIGlzIHJlamVjdGVkLlxuICAgICAqXG4gICAgICogRXZlcnkgb25lIG9mIHRoZSBwcm92aWRlZCBvYmplY3RzIHRoYXQgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiB3aWxsIGJlIGNhbmNlbGxlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgd2l0aCB0aGUgc2FtZSBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBhbGw8VD4odmFsdWVzOiBJdGVyYWJsZTxUIHwgUHJvbWlzZUxpa2U8VD4+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD5bXT47XG4gICAgc3RhdGljIGFsbDxUIGV4dGVuZHMgcmVhZG9ubHkgdW5rbm93bltdIHwgW10+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx7IC1yZWFkb25seSBbUCBpbiBrZXlvZiBUXTogQXdhaXRlZDxUW1BdPjsgfT47XG4gICAgc3RhdGljIGFsbDxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBjb2xsZWN0ZWQubGVuZ3RoID09PSAwXG4gICAgICAgICAgICA/IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKGNvbGxlY3RlZClcbiAgICAgICAgICAgIDogbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdm9pZCBQcm9taXNlLmFsbChjb2xsZWN0ZWQpLnRoZW4ocmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgICAgIH0sIChjYXVzZT8pOiBQcm9taXNlPHZvaWQ+ID0+IGNhbmNlbEFsbChwcm9taXNlLCBjb2xsZWN0ZWQsIGNhdXNlKSk7XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBDYW5jZWxsYWJsZVByb21pc2UgdGhhdCBpcyByZXNvbHZlZCB3aXRoIGFuIGFycmF5IG9mIHJlc3VsdHNcbiAgICAgKiB3aGVuIGFsbCBvZiB0aGUgcHJvdmlkZWQgUHJvbWlzZXMgcmVzb2x2ZSBvciByZWplY3QuXG4gICAgICpcbiAgICAgKiBFdmVyeSBvbmUgb2YgdGhlIHByb3ZpZGVkIG9iamVjdHMgdGhhdCBpcyBhIHRoZW5hYmxlIF9hbmRfIGNhbmNlbGxhYmxlIG9iamVjdFxuICAgICAqIHdpbGwgYmUgY2FuY2VsbGVkIHdoZW4gdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLCB3aXRoIHRoZSBzYW1lIGNhdXNlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIGFsbFNldHRsZWQ8VD4odmFsdWVzOiBJdGVyYWJsZTxUIHwgUHJvbWlzZUxpa2U8VD4+KTogQ2FuY2VsbGFibGVQcm9taXNlPFByb21pc2VTZXR0bGVkUmVzdWx0PEF3YWl0ZWQ8VD4+W10+O1xuICAgIHN0YXRpYyBhbGxTZXR0bGVkPFQgZXh0ZW5kcyByZWFkb25seSB1bmtub3duW10gfCBbXT4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHsgLXJlYWRvbmx5IFtQIGluIGtleW9mIFRdOiBQcm9taXNlU2V0dGxlZFJlc3VsdDxBd2FpdGVkPFRbUF0+PjsgfT47XG4gICAgc3RhdGljIGFsbFNldHRsZWQ8VCBleHRlbmRzIEl0ZXJhYmxlPHVua25vd24+IHwgQXJyYXlMaWtlPHVua25vd24+Pih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4ge1xuICAgICAgICBsZXQgY29sbGVjdGVkID0gQXJyYXkuZnJvbSh2YWx1ZXMpO1xuICAgICAgICBjb25zdCBwcm9taXNlID0gY29sbGVjdGVkLmxlbmd0aCA9PT0gMFxuICAgICAgICAgICAgPyBDYW5jZWxsYWJsZVByb21pc2UucmVzb2x2ZShjb2xsZWN0ZWQpXG4gICAgICAgICAgICA6IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgICAgIHZvaWQgUHJvbWlzZS5hbGxTZXR0bGVkKGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfSwgKGNhdXNlPyk6IFByb21pc2U8dm9pZD4gPT4gY2FuY2VsQWxsKHByb21pc2UsIGNvbGxlY3RlZCwgY2F1c2UpKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVGhlIGFueSBmdW5jdGlvbiByZXR1cm5zIGEgcHJvbWlzZSB0aGF0IGlzIGZ1bGZpbGxlZCBieSB0aGUgZmlyc3QgZ2l2ZW4gcHJvbWlzZSB0byBiZSBmdWxmaWxsZWQsXG4gICAgICogb3IgcmVqZWN0ZWQgd2l0aCBhbiBBZ2dyZWdhdGVFcnJvciBjb250YWluaW5nIGFuIGFycmF5IG9mIHJlamVjdGlvbiByZWFzb25zXG4gICAgICogaWYgYWxsIG9mIHRoZSBnaXZlbiBwcm9taXNlcyBhcmUgcmVqZWN0ZWQuXG4gICAgICogSXQgcmVzb2x2ZXMgYWxsIGVsZW1lbnRzIG9mIHRoZSBwYXNzZWQgaXRlcmFibGUgdG8gcHJvbWlzZXMgYXMgaXQgcnVucyB0aGlzIGFsZ29yaXRobS5cbiAgICAgKlxuICAgICAqIEV2ZXJ5IG9uZSBvZiB0aGUgcHJvdmlkZWQgb2JqZWN0cyB0aGF0IGlzIGEgdGhlbmFibGUgX2FuZF8gY2FuY2VsbGFibGUgb2JqZWN0XG4gICAgICogd2lsbCBiZSBjYW5jZWxsZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIHdpdGggdGhlIHNhbWUgY2F1c2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgYW55PFQ+KHZhbHVlczogSXRlcmFibGU8VCB8IFByb21pc2VMaWtlPFQ+Pik6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFQ+PjtcbiAgICBzdGF0aWMgYW55PFQgZXh0ZW5kcyByZWFkb25seSB1bmtub3duW10gfCBbXT4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VFtudW1iZXJdPj47XG4gICAgc3RhdGljIGFueTxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBjb2xsZWN0ZWQubGVuZ3RoID09PSAwXG4gICAgICAgICAgICA/IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKGNvbGxlY3RlZClcbiAgICAgICAgICAgIDogbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdm9pZCBQcm9taXNlLmFueShjb2xsZWN0ZWQpLnRoZW4ocmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgICAgIH0sIChjYXVzZT8pOiBQcm9taXNlPHZvaWQ+ID0+IGNhbmNlbEFsbChwcm9taXNlLCBjb2xsZWN0ZWQsIGNhdXNlKSk7XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBQcm9taXNlIHRoYXQgaXMgcmVzb2x2ZWQgb3IgcmVqZWN0ZWQgd2hlbiBhbnkgb2YgdGhlIHByb3ZpZGVkIFByb21pc2VzIGFyZSByZXNvbHZlZCBvciByZWplY3RlZC5cbiAgICAgKlxuICAgICAqIEV2ZXJ5IG9uZSBvZiB0aGUgcHJvdmlkZWQgb2JqZWN0cyB0aGF0IGlzIGEgdGhlbmFibGUgX2FuZF8gY2FuY2VsbGFibGUgb2JqZWN0XG4gICAgICogd2lsbCBiZSBjYW5jZWxsZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIHdpdGggdGhlIHNhbWUgY2F1c2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgcmFjZTxUPih2YWx1ZXM6IEl0ZXJhYmxlPFQgfCBQcm9taXNlTGlrZTxUPj4pOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPj47XG4gICAgc3RhdGljIHJhY2U8VCBleHRlbmRzIHJlYWRvbmx5IHVua25vd25bXSB8IFtdPih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUW251bWJlcl0+PjtcbiAgICBzdGF0aWMgcmFjZTxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHZvaWQgUHJvbWlzZS5yYWNlKGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICB9LCAoY2F1c2U/KTogUHJvbWlzZTx2b2lkPiA9PiBjYW5jZWxBbGwocHJvbWlzZSwgY29sbGVjdGVkLCBjYXVzZSkpO1xuICAgICAgICByZXR1cm4gcHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IGNhbmNlbGxlZCBDYW5jZWxsYWJsZVByb21pc2UgZm9yIHRoZSBwcm92aWRlZCBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBjYW5jZWw8VCA9IG5ldmVyPihjYXVzZT86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIGNvbnN0IHAgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KCgpID0+IHt9KTtcbiAgICAgICAgcC5jYW5jZWwoY2F1c2UpO1xuICAgICAgICByZXR1cm4gcDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IGNhbmNlbHNcbiAgICAgKiBhZnRlciB0aGUgc3BlY2lmaWVkIHRpbWVvdXQsIHdpdGggdGhlIHByb3ZpZGVkIGNhdXNlLlxuICAgICAqXG4gICAgICogSWYgdGhlIHtAbGluayBBYm9ydFNpZ25hbC50aW1lb3V0fSBmYWN0b3J5IG1ldGhvZCBpcyBhdmFpbGFibGUsXG4gICAgICogaXQgaXMgdXNlZCB0byBiYXNlIHRoZSB0aW1lb3V0IG9uIF9hY3RpdmVfIHRpbWUgcmF0aGVyIHRoYW4gX2VsYXBzZWRfIHRpbWUuXG4gICAgICogT3RoZXJ3aXNlLCBgdGltZW91dGAgZmFsbHMgYmFjayB0byB7QGxpbmsgc2V0VGltZW91dH0uXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgdGltZW91dDxUID0gbmV2ZXI+KG1pbGxpc2Vjb25kczogbnVtYmVyLCBjYXVzZT86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KCgpID0+IHt9KTtcbiAgICAgICAgaWYgKEFib3J0U2lnbmFsICYmIHR5cGVvZiBBYm9ydFNpZ25hbCA9PT0gJ2Z1bmN0aW9uJyAmJiBBYm9ydFNpZ25hbC50aW1lb3V0ICYmIHR5cGVvZiBBYm9ydFNpZ25hbC50aW1lb3V0ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgICBBYm9ydFNpZ25hbC50aW1lb3V0KG1pbGxpc2Vjb25kcykuYWRkRXZlbnRMaXN0ZW5lcignYWJvcnQnLCAoKSA9PiB2b2lkIHByb21pc2UuY2FuY2VsKGNhdXNlKSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHZvaWQgcHJvbWlzZS5jYW5jZWwoY2F1c2UpLCBtaWxsaXNlY29uZHMpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlIHRoYXQgcmVzb2x2ZXMgYWZ0ZXIgdGhlIHNwZWNpZmllZCB0aW1lb3V0LlxuICAgICAqIFRoZSByZXR1cm5lZCBwcm9taXNlIGNhbiBiZSBjYW5jZWxsZWQgd2l0aG91dCBjb25zZXF1ZW5jZXMuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgc2xlZXAobWlsbGlzZWNvbmRzOiBudW1iZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8dm9pZD47XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBDYW5jZWxsYWJsZVByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlclxuICAgICAqIHRoZSBzcGVjaWZpZWQgdGltZW91dCwgd2l0aCB0aGUgcHJvdmlkZWQgdmFsdWUuXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2UgY2FuIGJlIGNhbmNlbGxlZCB3aXRob3V0IGNvbnNlcXVlbmNlcy5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBzbGVlcDxUPihtaWxsaXNlY29uZHM6IG51bWJlciwgdmFsdWU6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8VD47XG4gICAgc3RhdGljIHNsZWVwPFQgPSB2b2lkPihtaWxsaXNlY29uZHM6IG51bWJlciwgdmFsdWU/OiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgcmV0dXJuIG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKHJlc29sdmUpID0+IHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4gcmVzb2x2ZSh2YWx1ZSEpLCBtaWxsaXNlY29uZHMpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlamVjdGVkIENhbmNlbGxhYmxlUHJvbWlzZSBmb3IgdGhlIHByb3ZpZGVkIHJlYXNvbi5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZWplY3Q8VCA9IG5ldmVyPihyZWFzb24/OiBhbnkpOiBDYW5jZWxsYWJsZVByb21pc2U8VD4ge1xuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTxUPigoXywgcmVqZWN0KSA9PiByZWplY3QocmVhc29uKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyByZXNvbHZlZCBDYW5jZWxsYWJsZVByb21pc2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgcmVzb2x2ZSgpOiBDYW5jZWxsYWJsZVByb21pc2U8dm9pZD47XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyByZXNvbHZlZCBDYW5jZWxsYWJsZVByb21pc2UgZm9yIHRoZSBwcm92aWRlZCB2YWx1ZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZXNvbHZlPFQ+KHZhbHVlOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+O1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgcmVzb2x2ZWQgQ2FuY2VsbGFibGVQcm9taXNlIGZvciB0aGUgcHJvdmlkZWQgdmFsdWUuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgcmVzb2x2ZTxUPih2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+O1xuICAgIHN0YXRpYyByZXNvbHZlPFQgPSB2b2lkPih2YWx1ZT86IFQgfCBQcm9taXNlTGlrZTxUPik6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFQ+PiB7XG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIENhbmNlbGxhYmxlUHJvbWlzZSkge1xuICAgICAgICAgICAgLy8gT3B0aW1pc2UgZm9yIGNhbmNlbGxhYmxlIHByb21pc2VzLlxuICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPGFueT4oKHJlc29sdmUpID0+IHJlc29sdmUodmFsdWUpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IENhbmNlbGxhYmxlUHJvbWlzZSBhbmQgcmV0dXJucyBpdCBpbiBhbiBvYmplY3QsIGFsb25nIHdpdGggaXRzIHJlc29sdmUgYW5kIHJlamVjdCBmdW5jdGlvbnNcbiAgICAgKiBhbmQgYSBnZXR0ZXIvc2V0dGVyIGZvciB0aGUgY2FuY2VsbGF0aW9uIGNhbGxiYWNrLlxuICAgICAqXG4gICAgICogVGhpcyBtZXRob2QgaXMgcG9seWZpbGxlZCwgaGVuY2UgYXZhaWxhYmxlIGluIGV2ZXJ5IE9TL3dlYnZpZXcgdmVyc2lvbi5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyB3aXRoUmVzb2x2ZXJzPFQ+KCk6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4ge1xuICAgICAgICBsZXQgcmVzdWx0OiBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+ID0geyBvbmNhbmNlbGxlZDogbnVsbCB9IGFzIGFueTtcbiAgICAgICAgcmVzdWx0LnByb21pc2UgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHJlc3VsdC5yZXNvbHZlID0gcmVzb2x2ZTtcbiAgICAgICAgICAgIHJlc3VsdC5yZWplY3QgPSByZWplY3Q7XG4gICAgICAgIH0sIChjYXVzZT86IGFueSkgPT4geyByZXN1bHQub25jYW5jZWxsZWQ/LihjYXVzZSk7IH0pO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgY2FsbGJhY2sgdGhhdCBpbXBsZW1lbnRzIHRoZSBjYW5jZWxsYXRpb24gYWxnb3JpdGhtIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS5cbiAqIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20gdGhlIHJlc3VsdGluZyBmdW5jdGlvbiBkb2VzIG5vdCByZWplY3QuXG4gKi9cbmZ1bmN0aW9uIGNhbmNlbGxlckZvcjxUPihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+LCBzdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUpIHtcbiAgICBsZXQgY2FuY2VsbGF0aW9uUHJvbWlzZTogdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+ID0gdW5kZWZpbmVkO1xuXG4gICAgcmV0dXJuIChyZWFzb246IENhbmNlbEVycm9yKTogdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+ID0+IHtcbiAgICAgICAgaWYgKCFzdGF0ZS5zZXR0bGVkKSB7XG4gICAgICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIHN0YXRlLnJlYXNvbiA9IHJlYXNvbjtcbiAgICAgICAgICAgIHByb21pc2UucmVqZWN0KHJlYXNvbik7XG5cbiAgICAgICAgICAgIC8vIEF0dGFjaCBhbiBlcnJvciBoYW5kbGVyIHRoYXQgaWdub3JlcyB0aGlzIHNwZWNpZmljIHJlamVjdGlvbiByZWFzb24gYW5kIG5vdGhpbmcgZWxzZS5cbiAgICAgICAgICAgIC8vIEluIHRoZW9yeSwgYSBzYW5lIHVuZGVybHlpbmcgaW1wbGVtZW50YXRpb24gYXQgdGhpcyBwb2ludFxuICAgICAgICAgICAgLy8gc2hvdWxkIGFsd2F5cyByZWplY3Qgd2l0aCBvdXIgY2FuY2VsbGF0aW9uIHJlYXNvbixcbiAgICAgICAgICAgIC8vIGhlbmNlIHRoZSBoYW5kbGVyIHdpbGwgbmV2ZXIgdGhyb3cuXG4gICAgICAgICAgICB2b2lkIFByb21pc2UucHJvdG90eXBlLnRoZW4uY2FsbChwcm9taXNlLnByb21pc2UsIHVuZGVmaW5lZCwgKGVycikgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChlcnIgIT09IHJlYXNvbikge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBlcnI7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBJZiByZWFzb24gaXMgbm90IHNldCwgdGhlIHByb21pc2UgcmVzb2x2ZWQgcmVndWxhcmx5LCBoZW5jZSB3ZSBtdXN0IG5vdCBjYWxsIG9uY2FuY2VsbGVkLlxuICAgICAgICAvLyBJZiBvbmNhbmNlbGxlZCBpcyB1bnNldCwgbm8gbmVlZCB0byBnbyBhbnkgZnVydGhlci5cbiAgICAgICAgaWYgKCFzdGF0ZS5yZWFzb24gfHwgIXByb21pc2Uub25jYW5jZWxsZWQpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgY2FuY2VsbGF0aW9uUHJvbWlzZSA9IG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlKSA9PiB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHJlc29sdmUocHJvbWlzZS5vbmNhbmNlbGxlZCEoc3RhdGUucmVhc29uIS5jYXVzZSkpO1xuICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHByb21pc2UucHJvbWlzZSwgZXJyLCBcIlVuaGFuZGxlZCBleGNlcHRpb24gaW4gb25jYW5jZWxsZWQgY2FsbGJhY2suXCIpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSkuY2F0Y2goKHJlYXNvbj8pID0+IHtcbiAgICAgICAgICAgIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwcm9taXNlLnByb21pc2UsIHJlYXNvbiwgXCJVbmhhbmRsZWQgcmVqZWN0aW9uIGluIG9uY2FuY2VsbGVkIGNhbGxiYWNrLlwiKSk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIFVuc2V0IG9uY2FuY2VsbGVkIHRvIHByZXZlbnQgcmVwZWF0ZWQgY2FsbHMuXG4gICAgICAgIHByb21pc2Uub25jYW5jZWxsZWQgPSBudWxsO1xuXG4gICAgICAgIHJldHVybiBjYW5jZWxsYXRpb25Qcm9taXNlO1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgY2FsbGJhY2sgdGhhdCBpbXBsZW1lbnRzIHRoZSByZXNvbHV0aW9uIGFsZ29yaXRobSBmb3IgdGhlIGdpdmVuIGNhbmNlbGxhYmxlIHByb21pc2UuXG4gKi9cbmZ1bmN0aW9uIHJlc29sdmVyRm9yPFQ+KHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4sIHN0YXRlOiBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSk6IENhbmNlbGxhYmxlUHJvbWlzZVJlc29sdmVyPFQ+IHtcbiAgICByZXR1cm4gKHZhbHVlKSA9PiB7XG4gICAgICAgIGlmIChzdGF0ZS5yZXNvbHZpbmcpIHsgcmV0dXJuOyB9XG4gICAgICAgIHN0YXRlLnJlc29sdmluZyA9IHRydWU7XG5cbiAgICAgICAgaWYgKHZhbHVlID09PSBwcm9taXNlLnByb21pc2UpIHtcbiAgICAgICAgICAgIGlmIChzdGF0ZS5zZXR0bGVkKSB7IHJldHVybjsgfVxuICAgICAgICAgICAgc3RhdGUuc2V0dGxlZCA9IHRydWU7XG4gICAgICAgICAgICBwcm9taXNlLnJlamVjdChuZXcgVHlwZUVycm9yKFwiQSBwcm9taXNlIGNhbm5vdCBiZSByZXNvbHZlZCB3aXRoIGl0c2VsZi5cIikpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHZhbHVlICE9IG51bGwgJiYgKHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgfHwgdHlwZW9mIHZhbHVlID09PSAnZnVuY3Rpb24nKSkge1xuICAgICAgICAgICAgbGV0IHRoZW46IGFueTtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgdGhlbiA9ICh2YWx1ZSBhcyBhbnkpLnRoZW47XG4gICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBwcm9taXNlLnJlamVjdChlcnIpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGlzQ2FsbGFibGUodGhlbikpIHtcbiAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICBsZXQgY2FuY2VsID0gKHZhbHVlIGFzIGFueSkuY2FuY2VsO1xuICAgICAgICAgICAgICAgICAgICBpZiAoaXNDYWxsYWJsZShjYW5jZWwpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBvbmNhbmNlbGxlZCA9IChjYXVzZT86IGFueSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZmxlY3QuYXBwbHkoY2FuY2VsLCB2YWx1ZSwgW2NhdXNlXSk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHN0YXRlLnJlYXNvbikge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIElmIGFscmVhZHkgY2FuY2VsbGVkLCBwcm9wYWdhdGUgY2FuY2VsbGF0aW9uLlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20gdGhlIGNhbmNlbGxlciBhbGdvcml0aG0gZG9lcyBub3QgcmVqZWN0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gc28gaXQgY2FuIGJlIGRpc2NhcmRlZCBzYWZlbHkuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdm9pZCBjYW5jZWxsZXJGb3IoeyAuLi5wcm9taXNlLCBvbmNhbmNlbGxlZCB9LCBzdGF0ZSkoc3RhdGUucmVhc29uKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvbWlzZS5vbmNhbmNlbGxlZCA9IG9uY2FuY2VsbGVkO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSBjYXRjaCB7fVxuXG4gICAgICAgICAgICAgICAgY29uc3QgbmV3U3RhdGU6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlID0ge1xuICAgICAgICAgICAgICAgICAgICByb290OiBzdGF0ZS5yb290LFxuICAgICAgICAgICAgICAgICAgICByZXNvbHZpbmc6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICBnZXQgc2V0dGxlZCgpIHsgcmV0dXJuIHRoaXMucm9vdC5zZXR0bGVkIH0sXG4gICAgICAgICAgICAgICAgICAgIHNldCBzZXR0bGVkKHZhbHVlKSB7IHRoaXMucm9vdC5zZXR0bGVkID0gdmFsdWU7IH0sXG4gICAgICAgICAgICAgICAgICAgIGdldCByZWFzb24oKSB7IHJldHVybiB0aGlzLnJvb3QucmVhc29uIH1cbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgY29uc3QgcmVqZWN0b3IgPSByZWplY3RvckZvcihwcm9taXNlLCBuZXdTdGF0ZSk7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgUmVmbGVjdC5hcHBseSh0aGVuLCB2YWx1ZSwgW3Jlc29sdmVyRm9yKHByb21pc2UsIG5ld1N0YXRlKSwgcmVqZWN0b3JdKTtcbiAgICAgICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVqZWN0b3IoZXJyKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuOyAvLyBJTVBPUlRBTlQhXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc3RhdGUuc2V0dGxlZCkgeyByZXR1cm47IH1cbiAgICAgICAgc3RhdGUuc2V0dGxlZCA9IHRydWU7XG4gICAgICAgIHByb21pc2UucmVzb2x2ZSh2YWx1ZSk7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgY2FsbGJhY2sgdGhhdCBpbXBsZW1lbnRzIHRoZSByZWplY3Rpb24gYWxnb3JpdGhtIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS5cbiAqL1xuZnVuY3Rpb24gcmVqZWN0b3JGb3I8VD4ocHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiwgc3RhdGU6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlKTogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3Ige1xuICAgIHJldHVybiAocmVhc29uPykgPT4ge1xuICAgICAgICBpZiAoc3RhdGUucmVzb2x2aW5nKSB7IHJldHVybjsgfVxuICAgICAgICBzdGF0ZS5yZXNvbHZpbmcgPSB0cnVlO1xuXG4gICAgICAgIGlmIChzdGF0ZS5zZXR0bGVkKSB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIGlmIChyZWFzb24gaW5zdGFuY2VvZiBDYW5jZWxFcnJvciAmJiBzdGF0ZS5yZWFzb24gaW5zdGFuY2VvZiBDYW5jZWxFcnJvciAmJiBPYmplY3QuaXMocmVhc29uLmNhdXNlLCBzdGF0ZS5yZWFzb24uY2F1c2UpKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIFN3YWxsb3cgbGF0ZSByZWplY3Rpb25zIHRoYXQgYXJlIENhbmNlbEVycm9ycyB3aG9zZSBjYW5jZWxsYXRpb24gY2F1c2UgaXMgdGhlIHNhbWUgYXMgb3Vycy5cbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gY2F0Y2gge31cblxuICAgICAgICAgICAgdm9pZCBQcm9taXNlLnJlamVjdChuZXcgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3IocHJvbWlzZS5wcm9taXNlLCByZWFzb24pKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHN0YXRlLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAgICAgcHJvbWlzZS5yZWplY3QocmVhc29uKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLyoqXG4gKiBDYW5jZWxzIGFsbCB2YWx1ZXMgaW4gYW4gYXJyYXkgdGhhdCBsb29rIGxpa2UgY2FuY2VsbGFibGUgdGhlbmFibGVzLlxuICogUmV0dXJucyBhIHByb21pc2UgdGhhdCBmdWxmaWxscyBvbmNlIGFsbCBjYW5jZWxsYXRpb24gcHJvY2VkdXJlcyBmb3IgdGhlIGdpdmVuIHZhbHVlcyBoYXZlIHNldHRsZWQuXG4gKi9cbmZ1bmN0aW9uIGNhbmNlbEFsbChwYXJlbnQ6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiwgdmFsdWVzOiBhbnlbXSwgY2F1c2U/OiBhbnkpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCByZXN1bHRzOiBQcm9taXNlPHZvaWQ+W10gPSBbXTtcblxuICAgIGZvciAoY29uc3QgdmFsdWUgb2YgdmFsdWVzKSB7XG4gICAgICAgIGxldCBjYW5jZWw6IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlcjtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGlmICghaXNDYWxsYWJsZSh2YWx1ZS50aGVuKSkgeyBjb250aW51ZTsgfVxuICAgICAgICAgICAgY2FuY2VsID0gdmFsdWUuY2FuY2VsO1xuICAgICAgICAgICAgaWYgKCFpc0NhbGxhYmxlKGNhbmNlbCkpIHsgY29udGludWU7IH1cbiAgICAgICAgfSBjYXRjaCB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgbGV0IHJlc3VsdDogdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+O1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgcmVzdWx0ID0gUmVmbGVjdC5hcHBseShjYW5jZWwsIHZhbHVlLCBbY2F1c2VdKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICBQcm9taXNlLnJlamVjdChuZXcgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3IocGFyZW50LCBlcnIsIFwiVW5oYW5kbGVkIGV4Y2VwdGlvbiBpbiBjYW5jZWwgbWV0aG9kLlwiKSk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghcmVzdWx0KSB7IGNvbnRpbnVlOyB9XG4gICAgICAgIHJlc3VsdHMucHVzaChcbiAgICAgICAgICAgIChyZXN1bHQgaW5zdGFuY2VvZiBQcm9taXNlICA/IHJlc3VsdCA6IFByb21pc2UucmVzb2x2ZShyZXN1bHQpKS5jYXRjaCgocmVhc29uPykgPT4ge1xuICAgICAgICAgICAgICAgIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwYXJlbnQsIHJlYXNvbiwgXCJVbmhhbmRsZWQgcmVqZWN0aW9uIGluIGNhbmNlbCBtZXRob2QuXCIpKTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIFByb21pc2UuYWxsKHJlc3VsdHMpIGFzIGFueTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIGl0cyBhcmd1bWVudC5cbiAqL1xuZnVuY3Rpb24gaWRlbnRpdHk8VD4oeDogVCk6IFQge1xuICAgIHJldHVybiB4O1xufVxuXG4vKipcbiAqIFRocm93cyBpdHMgYXJndW1lbnQuXG4gKi9cbmZ1bmN0aW9uIHRocm93ZXIocmVhc29uPzogYW55KTogbmV2ZXIge1xuICAgIHRocm93IHJlYXNvbjtcbn1cblxuLyoqXG4gKiBBdHRlbXB0cyB2YXJpb3VzIHN0cmF0ZWdpZXMgdG8gY29udmVydCBhbiBlcnJvciB0byBhIHN0cmluZy5cbiAqL1xuZnVuY3Rpb24gZXJyb3JNZXNzYWdlKGVycjogYW55KTogc3RyaW5nIHtcbiAgICB0cnkge1xuICAgICAgICBpZiAoZXJyIGluc3RhbmNlb2YgRXJyb3IgfHwgdHlwZW9mIGVyciAhPT0gJ29iamVjdCcgfHwgZXJyLnRvU3RyaW5nICE9PSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nKSB7XG4gICAgICAgICAgICByZXR1cm4gXCJcIiArIGVycjtcbiAgICAgICAgfVxuICAgIH0gY2F0Y2gge31cblxuICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBKU09OLnN0cmluZ2lmeShlcnIpO1xuICAgIH0gY2F0Y2gge31cblxuICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwoZXJyKTtcbiAgICB9IGNhdGNoIHt9XG5cbiAgICByZXR1cm4gXCI8Y291bGQgbm90IGNvbnZlcnQgZXJyb3IgdG8gc3RyaW5nPlwiO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIGN1cnJlbnQgYmFycmllciBwcm9taXNlIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS4gSWYgbmVjZXNzYXJ5LCBpbml0aWFsaXNlcyB0aGUgYmFycmllci5cbiAqL1xuZnVuY3Rpb24gY3VycmVudEJhcnJpZXI8VD4ocHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgbGV0IHB3cjogUGFydGlhbDxQcm9taXNlV2l0aFJlc29sdmVyczx2b2lkPj4gPSBwcm9taXNlW2JhcnJpZXJTeW1dID8/IHt9O1xuICAgIGlmICghKCdwcm9taXNlJyBpbiBwd3IpKSB7XG4gICAgICAgIE9iamVjdC5hc3NpZ24ocHdyLCBwcm9taXNlV2l0aFJlc29sdmVyczx2b2lkPigpKTtcbiAgICB9XG4gICAgaWYgKHByb21pc2VbYmFycmllclN5bV0gPT0gbnVsbCkge1xuICAgICAgICBwd3IucmVzb2x2ZSEoKTtcbiAgICAgICAgcHJvbWlzZVtiYXJyaWVyU3ltXSA9IHB3cjtcbiAgICB9XG4gICAgcmV0dXJuIHB3ci5wcm9taXNlITtcbn1cblxuLy8gUG9seWZpbGwgUHJvbWlzZS53aXRoUmVzb2x2ZXJzLlxubGV0IHByb21pc2VXaXRoUmVzb2x2ZXJzID0gUHJvbWlzZS53aXRoUmVzb2x2ZXJzO1xuaWYgKHByb21pc2VXaXRoUmVzb2x2ZXJzICYmIHR5cGVvZiBwcm9taXNlV2l0aFJlc29sdmVycyA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIHByb21pc2VXaXRoUmVzb2x2ZXJzID0gcHJvbWlzZVdpdGhSZXNvbHZlcnMuYmluZChQcm9taXNlKTtcbn0gZWxzZSB7XG4gICAgcHJvbWlzZVdpdGhSZXNvbHZlcnMgPSBmdW5jdGlvbiA8VD4oKTogUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4ge1xuICAgICAgICBsZXQgcmVzb2x2ZSE6ICh2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+KSA9PiB2b2lkO1xuICAgICAgICBsZXQgcmVqZWN0ITogKHJlYXNvbj86IGFueSkgPT4gdm9pZDtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ldyBQcm9taXNlPFQ+KChyZXMsIHJlaikgPT4geyByZXNvbHZlID0gcmVzOyByZWplY3QgPSByZWo7IH0pO1xuICAgICAgICByZXR1cm4geyBwcm9taXNlLCByZXNvbHZlLCByZWplY3QgfTtcbiAgICB9XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7bmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXN9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQ2xpcGJvYXJkKTtcblxuY29uc3QgQ2xpcGJvYXJkU2V0VGV4dCA9IDA7XG5jb25zdCBDbGlwYm9hcmRUZXh0ID0gMTtcblxuLyoqXG4gKiBTZXRzIHRoZSB0ZXh0IHRvIHRoZSBDbGlwYm9hcmQuXG4gKlxuICogQHBhcmFtIHRleHQgLSBUaGUgdGV4dCB0byBiZSBzZXQgdG8gdGhlIENsaXBib2FyZC5cbiAqIEByZXR1cm4gQSBQcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgb3BlcmF0aW9uIGlzIHN1Y2Nlc3NmdWwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTZXRUZXh0KHRleHQ6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKENsaXBib2FyZFNldFRleHQsIHt0ZXh0fSk7XG59XG5cbi8qKlxuICogR2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIHRleHQgZnJvbSB0aGUgQ2xpcGJvYXJkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gVGV4dCgpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiBjYWxsKENsaXBib2FyZFRleHQpO1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFNpemUge1xuICAgIC8qKiBUaGUgd2lkdGggb2YgYSByZWN0YW5ndWxhciBhcmVhLiAqL1xuICAgIFdpZHRoOiBudW1iZXI7XG4gICAgLyoqIFRoZSBoZWlnaHQgb2YgYSByZWN0YW5ndWxhciBhcmVhLiAqL1xuICAgIEhlaWdodDogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFJlY3Qge1xuICAgIC8qKiBUaGUgWCBjb29yZGluYXRlIG9mIHRoZSBvcmlnaW4uICovXG4gICAgWDogbnVtYmVyO1xuICAgIC8qKiBUaGUgWSBjb29yZGluYXRlIG9mIHRoZSBvcmlnaW4uICovXG4gICAgWTogbnVtYmVyO1xuICAgIC8qKiBUaGUgd2lkdGggb2YgdGhlIHJlY3RhbmdsZS4gKi9cbiAgICBXaWR0aDogbnVtYmVyO1xuICAgIC8qKiBUaGUgaGVpZ2h0IG9mIHRoZSByZWN0YW5nbGUuICovXG4gICAgSGVpZ2h0OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2NyZWVuIHtcbiAgICAvKiogVW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSBzY3JlZW4uICovXG4gICAgSUQ6IHN0cmluZztcbiAgICAvKiogSHVtYW4tcmVhZGFibGUgbmFtZSBvZiB0aGUgc2NyZWVuLiAqL1xuICAgIE5hbWU6IHN0cmluZztcbiAgICAvKiogVGhlIHNjYWxlIGZhY3RvciBvZiB0aGUgc2NyZWVuIChEUEkvOTYpLiAxID0gc3RhbmRhcmQgRFBJLCAyID0gSGlEUEkgKFJldGluYSksIGV0Yy4gKi9cbiAgICBTY2FsZUZhY3RvcjogbnVtYmVyO1xuICAgIC8qKiBUaGUgWCBjb29yZGluYXRlIG9mIHRoZSBzY3JlZW4uICovXG4gICAgWDogbnVtYmVyO1xuICAgIC8qKiBUaGUgWSBjb29yZGluYXRlIG9mIHRoZSBzY3JlZW4uICovXG4gICAgWTogbnVtYmVyO1xuICAgIC8qKiBDb250YWlucyB0aGUgd2lkdGggYW5kIGhlaWdodCBvZiB0aGUgc2NyZWVuLiAqL1xuICAgIFNpemU6IFNpemU7XG4gICAgLyoqIENvbnRhaW5zIHRoZSBib3VuZHMgb2YgdGhlIHNjcmVlbiBpbiB0ZXJtcyBvZiBYLCBZLCBXaWR0aCwgYW5kIEhlaWdodC4gKi9cbiAgICBCb3VuZHM6IFJlY3Q7XG4gICAgLyoqIENvbnRhaW5zIHRoZSBwaHlzaWNhbCBib3VuZHMgb2YgdGhlIHNjcmVlbiBpbiB0ZXJtcyBvZiBYLCBZLCBXaWR0aCwgYW5kIEhlaWdodCAoYmVmb3JlIHNjYWxpbmcpLiAqL1xuICAgIFBoeXNpY2FsQm91bmRzOiBSZWN0O1xuICAgIC8qKiBDb250YWlucyB0aGUgYXJlYSBvZiB0aGUgc2NyZWVuIHRoYXQgaXMgYWN0dWFsbHkgdXNhYmxlIChleGNsdWRpbmcgdGFza2JhciBhbmQgb3RoZXIgc3lzdGVtIFVJKS4gKi9cbiAgICBXb3JrQXJlYTogUmVjdDtcbiAgICAvKiogQ29udGFpbnMgdGhlIHBoeXNpY2FsIFdvcmtBcmVhIG9mIHRoZSBzY3JlZW4gKGJlZm9yZSBzY2FsaW5nKS4gKi9cbiAgICBQaHlzaWNhbFdvcmtBcmVhOiBSZWN0O1xuICAgIC8qKiBUcnVlIGlmIHRoaXMgaXMgdGhlIHByaW1hcnkgbW9uaXRvciBzZWxlY3RlZCBieSB0aGUgdXNlciBpbiB0aGUgb3BlcmF0aW5nIHN5c3RlbS4gKi9cbiAgICBJc1ByaW1hcnk6IGJvb2xlYW47XG4gICAgLyoqIFRoZSByb3RhdGlvbiBvZiB0aGUgc2NyZWVuLiAqL1xuICAgIFJvdGF0aW9uOiBudW1iZXI7XG59XG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuU2NyZWVucyk7XG5cbmNvbnN0IGdldEFsbCA9IDA7XG5jb25zdCBnZXRQcmltYXJ5ID0gMTtcbmNvbnN0IGdldEN1cnJlbnQgPSAyO1xuXG4vKipcbiAqIEdldHMgYWxsIHNjcmVlbnMuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gYW4gYXJyYXkgb2YgU2NyZWVuIG9iamVjdHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRBbGwoKTogUHJvbWlzZTxTY3JlZW5bXT4ge1xuICAgIHJldHVybiBjYWxsKGdldEFsbCk7XG59XG5cbi8qKlxuICogR2V0cyB0aGUgcHJpbWFyeSBzY3JlZW4uXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gdGhlIHByaW1hcnkgc2NyZWVuLlxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0UHJpbWFyeSgpOiBQcm9taXNlPFNjcmVlbj4ge1xuICAgIHJldHVybiBjYWxsKGdldFByaW1hcnkpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBjdXJyZW50IGFjdGl2ZSBzY3JlZW4uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRDdXJyZW50KCk6IFByb21pc2U8U2NyZWVuPiB7XG4gICAgcmV0dXJuIGNhbGwoZ2V0Q3VycmVudCk7XG59XG4iLCAiLypcbiBfICAgICBfXyAgICAgXyBfX1xufCB8ICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5JT1MpO1xuXG4vLyBNZXRob2QgSURzXG5jb25zdCBIYXB0aWNzSW1wYWN0ID0gMDtcbmNvbnN0IERldmljZUluZm8gPSAxO1xuXG5leHBvcnQgbmFtZXNwYWNlIEhhcHRpY3Mge1xuICAgIGV4cG9ydCB0eXBlIEltcGFjdFN0eWxlID0gXCJsaWdodFwifFwibWVkaXVtXCJ8XCJoZWF2eVwifFwic29mdFwifFwicmlnaWRcIjtcbiAgICBleHBvcnQgZnVuY3Rpb24gSW1wYWN0KHN0eWxlOiBJbXBhY3RTdHlsZSA9IFwibWVkaXVtXCIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIGNhbGwoSGFwdGljc0ltcGFjdCwgeyBzdHlsZSB9KTtcbiAgICB9XG59XG5cbmV4cG9ydCBuYW1lc3BhY2UgRGV2aWNlIHtcbiAgICBleHBvcnQgaW50ZXJmYWNlIEluZm8ge1xuICAgICAgICBtb2RlbDogc3RyaW5nO1xuICAgICAgICBzeXN0ZW1OYW1lOiBzdHJpbmc7XG4gICAgICAgIHN5c3RlbVZlcnNpb246IHN0cmluZztcbiAgICAgICAgaXNTaW11bGF0b3I6IGJvb2xlYW47XG4gICAgfVxuICAgIGV4cG9ydCBmdW5jdGlvbiBJbmZvKCk6IFByb21pc2U8SW5mbz4ge1xuICAgICAgICByZXR1cm4gY2FsbChEZXZpY2VJbmZvKTtcbiAgICB9XG59XG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7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QUEsSUFDdkIsdUJBQXVCO0FBQUEsSUFDdkIsZ0JBQWdCO0FBQUEsSUFDaEIsc0JBQXNCO0FBQUEsSUFDdEIsY0FBYztBQUFBLElBQ2Qsb0JBQW9CO0FBQUEsSUFDcEIsb0JBQW9CO0FBQUEsSUFDcEIsc0JBQXNCO0FBQUEsSUFDdEIsYUFBYTtBQUFBLElBQ2IsY0FBYztBQUFBLElBQ2QsbUJBQW1CO0FBQUEsSUFDbkIsbUJBQW1CO0FBQUEsSUFDbkIseUJBQXlCO0FBQUEsSUFDekIsZUFBZTtBQUFBLElBQ2YsaUJBQWlCO0FBQUEsSUFDakIsdUJBQXVCO0FBQUEsSUFDdkIscUJBQXFCO0FBQUEsSUFDckIscUJBQXFCO0FBQUEsSUFDckIsdUJBQXVCO0FBQUEsSUFDdkIsY0FBYztBQUFBLElBQ2QsZUFBZTtBQUFBLElBQ2Ysb0JBQW9CO0FBQUEsSUFDcEIsb0JBQW9CO0FBQUEsSUFDcEIsMEJBQTBCO0FBQUEsSUFDMUIsZ0JBQWdCO0FBQUEsSUFDaEIsNEJBQTRCO0FBQUEsSUFDNUIsNEJBQTRCO0FBQUEsSUFDNUIseURBQXlEO0FBQUEsSUFDekQsc0NBQXNDO0FBQUEsSUFDdEMsb0JBQW9CO0FBQUEsSUFDcEIscUJBQXFCO0FBQUEsSUFDckIscUJBQXFCO0FBQUEsSUFDckIsc0JBQXNCO0FBQUEsSUFDdEIsZ0NBQWdDO0FBQUEsSUFDaEMsa0NBQWtDO0FBQUEsSUFDbEMsbUNBQW1DO0FBQUEsSUFDbkMsb0NBQW9DO0FBQUEsSUFDcEMsK0JBQStCO0FBQUEsSUFDL0IsNkJBQTZCO0FBQUEsSUFDN0IsdUJBQXVCO0FBQUEsSUFDdkIsaUNBQWlDO0FBQUEsSUFDakMsOEJBQThCO0FBQUEsSUFDOUIsNEJBQTRCO0FBQUEsSUFDNUIsc0NBQXNDO0FBQUEsSUFDdEMsNEJBQTRCO0FBQUEsSUFDNUIsc0JBQXNCO0FBQUEsSUFDdEIsa0NBQWtDO0FBQUEsSUFDbEMsc0JBQXNCO0FBQUEsSUFDdEIsd0JBQXdCO0FBQUEsSUFDeEIsd0JBQXdCO0FBQUEsSUFDeEIsbUJBQW1CO0FBQUEsSUFDbkIsMEJBQTBCO0FBQUEsSUFDMUIsOEJBQThCO0FBQUEsSUFDOUIseUJBQXlCO0FBQUEsSUFDekIsNkJBQTZCO0FBQUEsSUFDN0IsaUJBQWlCO0FBQUEsSUFDakIsZ0JBQWdCO0FBQUEsSUFDaEIsc0JBQXNCO0FBQUEsSUFDdEIsZUFBZTtBQUFBLElBQ2YseUJBQXlCO0FBQUEsSUFDekIsd0JBQXdCO0FBQUEsSUFDeEIsb0JBQW9CO0FBQUEsSUFDcEIscUJBQXFCO0FBQUEsSUFDckIsaUJBQWlCO0FBQUEsSUFDakIsaUJBQWlCO0FBQUEsSUFDakIsc0JBQXNCO0FBQUEsSUFDdEIsbUNBQW1DO0FBQUEsSUFDbkMscUNBQXFDO0FBQUEsSUFDckMsdUJBQXVCO0FBQUEsSUFDdkIsc0JBQXNCO0FBQUEsSUFDdEIsd0JBQXdCO0FBQUEsSUFDeEIsZUFBZTtBQUFBLElBQ2YsMkJBQTJCO0FBQUEsSUFDM0IsMEJBQTBCO0FBQUEsSUFDMUIsNkJBQTZCO0FBQUEsSUFDN0IsWUFBWTtBQUFBLElBQ1osZ0JBQWdCO0FBQUEsSUFDaEIsa0JBQWtCO0FBQUEsSUFDbEIsZ0JBQWdCO0FBQUEsSUFDaEIsa0JBQWtCO0FBQUEsSUFDbEIsbUJBQW1CO0FBQUEsSUFDbkIsWUFBWTtBQUFBLElBQ1oscUJBQXFCO0FBQUEsSUFDckIsc0JBQXNCO0FBQUEsSUFDdEIsc0JBQXNCO0FBQUEsSUFDdEIsOEJBQThCO0FBQUEsSUFDOUIsaUJBQWlCO0FBQUEsSUFDakIseUJBQXlCO0FBQUEsSUFDekIsMkJBQTJCO0FBQUEsSUFDM0IsK0JBQStCO0FBQUEsSUFDL0IsMEJBQTBCO0FBQUEsSUFDMUIsOEJBQThCO0FBQUEsSUFDOUIsaUJBQWlCO0FBQUEsSUFDakIsdUJBQXVCO0FBQUEsSUFDdkIsZ0JBQWdCO0FBQUEsSUFDaEIsMEJBQTBCO0FBQUEsSUFDMUIseUJBQXlCO0FBQUEsSUFDekIsc0JBQXNCO0FBQUEsSUFDdEIsa0JBQWtCO0FBQUEsSUFDbEIsbUJBQW1CO0FBQUEsSUFDbkIsa0JBQWtCO0FBQUEsSUFDbEIsdUJBQXVCO0FBQUEsSUFDdkIsb0NB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UyxPQUF3QjtBQUNwQyxTQUFPQSxNQUFLLGFBQWE7QUFDN0I7OztBQ2xDQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUF3REEsSUFBTUMsUUFBTyxpQkFBaUIsWUFBWSxPQUFPO0FBRWpELElBQU0sU0FBUztBQUNmLElBQU0sYUFBYTtBQUNuQixJQUFNLGFBQWE7QUFPWixTQUFTLFNBQTRCO0FBQ3hDLFNBQU9BLE1BQUssTUFBTTtBQUN0QjtBQU9PLFNBQVMsYUFBOEI7QUFDMUMsU0FBT0EsTUFBSyxVQUFVO0FBQzFCO0FBT08sU0FBUyxhQUE4QjtBQUMxQyxTQUFPQSxNQUFLLFVBQVU7QUFDMUI7OztBQ3ZGQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBWUEsSUFBTUMsU0FBTyxpQkFBaUIsWUFBWSxHQUFHO0FBRzdDLElBQU0sZ0JBQWdCO0FBQ3RCLElBQU0sYUFBYTtBQUVaLElBQVU7QUFBQSxDQUFWLENBQVVDLGFBQVY7QUFFSSxXQUFTLE9BQU8sUUFBcUIsVUFBeUI7QUFDakUsV0FBT0QsT0FBSyxlQUFlLEVBQUUsTUFBTSxDQUFDO0FBQUEsRUFDeEM7QUFGTyxFQUFBQyxTQUFTO0FBQUEsR0FGSDtBQU9WLElBQVU7QUFBQSxDQUFWLENBQVVDLFlBQVY7QUFPSSxXQUFTQyxRQUFzQjtBQUNsQyxXQUFPSCxPQUFLLFVBQVU7QUFBQSxFQUMxQjtBQUZPLEVBQUFFLFFBQVMsT0FBQUM7QUFBQSxHQVBIOzs7QXZCZGpCLE9BQU8sU0FBUyxPQUFPLFVBQVUsQ0FBQztBQXNEbEMsT0FBTyxPQUFPLFNBQWdCO0FBSzlCLE9BQU8sT0FBTyx5QkFBeUIsZUFBTyx1QkFBdUIsS0FBSyxjQUFNO0FBR2hGLE9BQU8sT0FBTyxrQkFBa0I7QUFDaEMsT0FBTyxPQUFPLGtCQUFrQjtBQUNoQyxPQUFPLE9BQU8saUJBQWlCO0FBRXhCLE9BQU8scUJBQXFCOyIsCiAgIm5hbWVzIjogWyJfYSIsICJFcnJvciIsICJjYWxsIiwgIkVycm9yIiwgIl9hIiwgIkFycmF5IiwgIk1hcCIsICJBcnJheSIsICJNYXAiLCAia2V5IiwgImNhbGwiLCAiX2EiLCAiX2EiLCAicmVzaXphYmxlIiwgIl9hIiwgImNhbGwiLCAiX2EiLCAiY2FsbCIsICJfYSIsICJjYWxsIiwgIkhpZGVNZXRob2QiLCAiU2hvd01ldGhvZCIsICJpc0RvY3VtZW50RG90QWxsIiwgIl9hIiwgInJlYXNvbiIsICJ2YWx1ZSIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgIkhhcHRpY3MiLCAiRGV2aWNlIiwgIkluZm8iXQp9Cg== diff --git a/v3/internal/assetserver/bundledassets/runtime.js b/v3/internal/assetserver/bundledassets/runtime.js index fca1b0e81..e4cf264cc 100644 --- a/v3/internal/assetserver/bundledassets/runtime.js +++ b/v3/internal/assetserver/bundledassets/runtime.js @@ -1 +1 @@ -var Ze=Object.defineProperty,hn=Object.defineProperties;var bn=Object.getOwnPropertyDescriptors;var He=Object.getOwnPropertySymbols;var vn=Object.prototype.hasOwnProperty,yn=Object.prototype.propertyIsEnumerable;var Ve=(n,e,i)=>e in n?Ze(n,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):n[e]=i,Ge=(n,e)=>{for(var i in e||(e={}))vn.call(e,i)&&Ve(n,i,e[i]);if(He)for(var i of He(e))yn.call(e,i)&&Ve(n,i,e[i]);return n},Ke=(n,e)=>hn(n,bn(e));var p=(n,e)=>{for(var i in e)Ze(n,i,{get:e[i],enumerable:!0})};var pe={};p(pe,{Application:()=>Se,Browser:()=>ie,Call:()=>Le,CancelError:()=>O,CancellablePromise:()=>N,CancelledRejectionError:()=>b,Clipboard:()=>Ie,Create:()=>re,Dialogs:()=>te,Events:()=>le,Flags:()=>he,IOS:()=>je,Screens:()=>Ue,System:()=>We,WML:()=>we,Window:()=>M,clientId:()=>ee,getTransport:()=>Xe,objectNames:()=>c,setTransport:()=>Ye});var we={};p(we,{Enable:()=>ue,Reload:()=>tn});var ie={};p(ie,{OpenURL:()=>ne});var Cn="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function j(n=21){let e="",i=n|0;for(;i--;)e+=Cn[Math.random()*64|0];return e}var Dn=window.location.origin+"/wails/runtime";var c=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10,IOS:11}),ee=j(),H=null;function Ye(n){H=n}function Xe(){return H}function m(n,e=""){return function(i,o=null){return Pn(n,i,e,o)}}async function Pn(n,e,i,o){var d,w;if(H)return H.call(n,e,i,o);let t=new URL(Dn),a={object:n,method:e};o!=null&&(a.args=o);let s={"x-wails-client-id":ee,"Content-Type":"application/json"};i&&(s["x-wails-window-name"]=i);let l=await fetch(t,{method:"POST",headers:s,body:JSON.stringify(a)});if(!l.ok)throw new Error(await l.text());return((w=(d=l.headers.get("Content-Type"))==null?void 0:d.indexOf("application/json"))!=null?w:-1)!==-1?l.json():l.text()}var Tn=m(c.Browser),Mn=0;function ne(n){return Tn(Mn,{url:n.toString()})}var te={};p(te,{Error:()=>In,Info:()=>kn,OpenFile:()=>zn,Question:()=>oe,SaveFile:()=>Un,Warning:()=>Ln});window._wails=window._wails||{};var Sn=m(c.Dialog),En=0,xn=1,An=2,Rn=3,Fn=4,On=5;function P(n,e={}){return Sn(n,e)}function kn(n){return P(En,n)}function Ln(n){return P(xn,n)}function In(n){return P(An,n)}function oe(n){return P(Rn,n)}function zn(n){var e;return(e=P(Fn,n))!=null?e:[]}function Un(n){return P(On,n)}var le={};p(le,{Emit:()=>se,Off:()=>Qn,OffAll:()=>qn,On:()=>Xn,OnMultiple:()=>ae,Once:()=>$n,Types:()=>Zn,WailsEvent:()=>k});var g=new Map,V=class{constructor(e,i,o){this.eventName=e,this.callback=i,this.maxCallbacks=o||-1}dispatch(e){try{this.callback(e)}catch(i){}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}};function $e(n){let e=g.get(n.eventName);e&&(e=e.filter(i=>i!==n),e.length===0?g.delete(n.eventName):g.set(n.eventName,e))}var re={};p(re,{Any:()=>v,Array:()=>Bn,ByteSlice:()=>Nn,Events:()=>Z,Map:()=>jn,Nullable:()=>Hn,Struct:()=>Vn});function v(n){return n}function Nn(n){return n==null?"":n}function Bn(n){return n===v?e=>e===null?[]:e:e=>{if(e===null)return[];for(let i=0;ii===null?{}:i:i=>{if(i===null)return{};for(let o in i)i[o]=e(i[o]);return i}}function Hn(n){return n===v?v:e=>e===null?null:n(e)}function Vn(n){let e=!0;for(let i in n)if(n[i]!==v){e=!1;break}return e?v:i=>{for(let o in n)o in i&&(i[o]=n[o](i[o]));return i}}var Z={};var Zn=Object.freeze({Windows:Object.freeze({APMPowerSettingChange:"windows:APMPowerSettingChange",APMPowerStatusChange:"windows:APMPowerStatusChange",APMResumeAutomatic:"windows:APMResumeAutomatic",APMResumeSuspend:"windows:APMResumeSuspend",APMSuspend:"windows:APMSuspend",ApplicationStarted:"windows:ApplicationStarted",SystemThemeChanged:"windows:SystemThemeChanged",WebViewNavigationCompleted:"windows:WebViewNavigationCompleted",WindowActive:"windows:WindowActive",WindowBackgroundErase:"windows:WindowBackgroundErase",WindowClickActive:"windows:WindowClickActive",WindowClosing:"windows:WindowClosing",WindowDidMove:"windows:WindowDidMove",WindowDidResize:"windows:WindowDidResize",WindowDPIChanged:"windows:WindowDPIChanged",WindowDragDrop:"windows:WindowDragDrop",WindowDragEnter:"windows:WindowDragEnter",WindowDragLeave:"windows:WindowDragLeave",WindowDragOver:"windows:WindowDragOver",WindowEndMove:"windows:WindowEndMove",WindowEndResize:"windows:WindowEndResize",WindowFullscreen:"windows:WindowFullscreen",WindowHide:"windows:WindowHide",WindowInactive:"windows:WindowInactive",WindowKeyDown:"windows:WindowKeyDown",WindowKeyUp:"windows:WindowKeyUp",WindowKillFocus:"windows:WindowKillFocus",WindowNonClientHit:"windows:WindowNonClientHit",WindowNonClientMouseDown:"windows:WindowNonClientMouseDown",WindowNonClientMouseLeave:"windows:WindowNonClientMouseLeave",WindowNonClientMouseMove:"windows:WindowNonClientMouseMove",WindowNonClientMouseUp:"windows:WindowNonClientMouseUp",WindowPaint:"windows:WindowPaint",WindowRestore:"windows:WindowRestore",WindowSetFocus:"windows:WindowSetFocus",WindowShow:"windows:WindowShow",WindowStartMove:"windows:WindowStartMove",WindowStartResize:"windows:WindowStartResize",WindowUnFullscreen:"windows:WindowUnFullscreen",WindowZOrderChanged:"windows:WindowZOrderChanged",WindowMinimise:"windows:WindowMinimise",WindowUnMinimise:"windows:WindowUnMinimise",WindowMaximise:"windows:WindowMaximise",WindowUnMaximise:"windows:WindowUnMaximise"}),Mac:Object.freeze({ApplicationDidBecomeActive:"mac:ApplicationDidBecomeActive",ApplicationDidChangeBackingProperties:"mac:ApplicationDidChangeBackingProperties",ApplicationDidChangeEffectiveAppearance:"mac:ApplicationDidChangeEffectiveAppearance",ApplicationDidChangeIcon:"mac:ApplicationDidChangeIcon",ApplicationDidChangeOcclusionState:"mac:ApplicationDidChangeOcclusionState",ApplicationDidChangeScreenParameters:"mac:ApplicationDidChangeScreenParameters",ApplicationDidChangeStatusBarFrame:"mac:ApplicationDidChangeStatusBarFrame",ApplicationDidChangeStatusBarOrientation:"mac:ApplicationDidChangeStatusBarOrientation",ApplicationDidChangeTheme:"mac:ApplicationDidChangeTheme",ApplicationDidFinishLaunching:"mac:ApplicationDidFinishLaunching",ApplicationDidHide:"mac:ApplicationDidHide",ApplicationDidResignActive:"mac:ApplicationDidResignActive",ApplicationDidUnhide:"mac:ApplicationDidUnhide",ApplicationDidUpdate:"mac:ApplicationDidUpdate",ApplicationShouldHandleReopen:"mac:ApplicationShouldHandleReopen",ApplicationWillBecomeActive:"mac:ApplicationWillBecomeActive",ApplicationWillFinishLaunching:"mac:ApplicationWillFinishLaunching",ApplicationWillHide:"mac:ApplicationWillHide",ApplicationWillResignActive:"mac:ApplicationWillResignActive",ApplicationWillTerminate:"mac:ApplicationWillTerminate",ApplicationWillUnhide:"mac:ApplicationWillUnhide",ApplicationWillUpdate:"mac:ApplicationWillUpdate",MenuDidAddItem:"mac:MenuDidAddItem",MenuDidBeginTracking:"mac:MenuDidBeginTracking",MenuDidClose:"mac:MenuDidClose",MenuDidDisplayItem:"mac:MenuDidDisplayItem",MenuDidEndTracking:"mac:MenuDidEndTracking",MenuDidHighlightItem:"mac:MenuDidHighlightItem",MenuDidOpen:"mac:MenuDidOpen",MenuDidPopUp:"mac:MenuDidPopUp",MenuDidRemoveItem:"mac:MenuDidRemoveItem",MenuDidSendAction:"mac:MenuDidSendAction",MenuDidSendActionToItem:"mac:MenuDidSendActionToItem",MenuDidUpdate:"mac:MenuDidUpdate",MenuWillAddItem:"mac:MenuWillAddItem",MenuWillBeginTracking:"mac:MenuWillBeginTracking",MenuWillDisplayItem:"mac:MenuWillDisplayItem",MenuWillEndTracking:"mac:MenuWillEndTracking",MenuWillHighlightItem:"mac:MenuWillHighlightItem",MenuWillOpen:"mac:MenuWillOpen",MenuWillPopUp:"mac:MenuWillPopUp",MenuWillRemoveItem:"mac:MenuWillRemoveItem",MenuWillSendAction:"mac:MenuWillSendAction",MenuWillSendActionToItem:"mac:MenuWillSendActionToItem",MenuWillUpdate:"mac:MenuWillUpdate",WebViewDidCommitNavigation:"mac:WebViewDidCommitNavigation",WebViewDidFinishNavigation:"mac:WebViewDidFinishNavigation",WebViewDidReceiveServerRedirectForProvisionalNavigation:"mac:WebViewDidReceiveServerRedirectForProvisionalNavigation",WebViewDidStartProvisionalNavigation:"mac:WebViewDidStartProvisionalNavigation",WindowDidBecomeKey:"mac:WindowDidBecomeKey",WindowDidBecomeMain:"mac:WindowDidBecomeMain",WindowDidBeginSheet:"mac:WindowDidBeginSheet",WindowDidChangeAlpha:"mac:WindowDidChangeAlpha",WindowDidChangeBackingLocation:"mac:WindowDidChangeBackingLocation",WindowDidChangeBackingProperties:"mac:WindowDidChangeBackingProperties",WindowDidChangeCollectionBehavior:"mac:WindowDidChangeCollectionBehavior",WindowDidChangeEffectiveAppearance:"mac:WindowDidChangeEffectiveAppearance",WindowDidChangeOcclusionState:"mac:WindowDidChangeOcclusionState",WindowDidChangeOrderingMode:"mac:WindowDidChangeOrderingMode",WindowDidChangeScreen:"mac:WindowDidChangeScreen",WindowDidChangeScreenParameters:"mac:WindowDidChangeScreenParameters",WindowDidChangeScreenProfile:"mac:WindowDidChangeScreenProfile",WindowDidChangeScreenSpace:"mac:WindowDidChangeScreenSpace",WindowDidChangeScreenSpaceProperties:"mac:WindowDidChangeScreenSpaceProperties",WindowDidChangeSharingType:"mac:WindowDidChangeSharingType",WindowDidChangeSpace:"mac:WindowDidChangeSpace",WindowDidChangeSpaceOrderingMode:"mac:WindowDidChangeSpaceOrderingMode",WindowDidChangeTitle:"mac:WindowDidChangeTitle",WindowDidChangeToolbar:"mac:WindowDidChangeToolbar",WindowDidDeminiaturize:"mac:WindowDidDeminiaturize",WindowDidEndSheet:"mac:WindowDidEndSheet",WindowDidEnterFullScreen:"mac:WindowDidEnterFullScreen",WindowDidEnterVersionBrowser:"mac:WindowDidEnterVersionBrowser",WindowDidExitFullScreen:"mac:WindowDidExitFullScreen",WindowDidExitVersionBrowser:"mac:WindowDidExitVersionBrowser",WindowDidExpose:"mac:WindowDidExpose",WindowDidFocus:"mac:WindowDidFocus",WindowDidMiniaturize:"mac:WindowDidMiniaturize",WindowDidMove:"mac:WindowDidMove",WindowDidOrderOffScreen:"mac:WindowDidOrderOffScreen",WindowDidOrderOnScreen:"mac:WindowDidOrderOnScreen",WindowDidResignKey:"mac:WindowDidResignKey",WindowDidResignMain:"mac:WindowDidResignMain",WindowDidResize:"mac:WindowDidResize",WindowDidUpdate:"mac:WindowDidUpdate",WindowDidUpdateAlpha:"mac:WindowDidUpdateAlpha",WindowDidUpdateCollectionBehavior:"mac:WindowDidUpdateCollectionBehavior",WindowDidUpdateCollectionProperties:"mac:WindowDidUpdateCollectionProperties",WindowDidUpdateShadow:"mac:WindowDidUpdateShadow",WindowDidUpdateTitle:"mac:WindowDidUpdateTitle",WindowDidUpdateToolbar:"mac:WindowDidUpdateToolbar",WindowDidZoom:"mac:WindowDidZoom",WindowFileDraggingEntered:"mac:WindowFileDraggingEntered",WindowFileDraggingExited:"mac:WindowFileDraggingExited",WindowFileDraggingPerformed:"mac:WindowFileDraggingPerformed",WindowHide:"mac:WindowHide",WindowMaximise:"mac:WindowMaximise",WindowUnMaximise:"mac:WindowUnMaximise",WindowMinimise:"mac:WindowMinimise",WindowUnMinimise:"mac:WindowUnMinimise",WindowShouldClose:"mac:WindowShouldClose",WindowShow:"mac:WindowShow",WindowWillBecomeKey:"mac:WindowWillBecomeKey",WindowWillBecomeMain:"mac:WindowWillBecomeMain",WindowWillBeginSheet:"mac:WindowWillBeginSheet",WindowWillChangeOrderingMode:"mac:WindowWillChangeOrderingMode",WindowWillClose:"mac:WindowWillClose",WindowWillDeminiaturize:"mac:WindowWillDeminiaturize",WindowWillEnterFullScreen:"mac:WindowWillEnterFullScreen",WindowWillEnterVersionBrowser:"mac:WindowWillEnterVersionBrowser",WindowWillExitFullScreen:"mac:WindowWillExitFullScreen",WindowWillExitVersionBrowser:"mac:WindowWillExitVersionBrowser",WindowWillFocus:"mac:WindowWillFocus",WindowWillMiniaturize:"mac:WindowWillMiniaturize",WindowWillMove:"mac:WindowWillMove",WindowWillOrderOffScreen:"mac:WindowWillOrderOffScreen",WindowWillOrderOnScreen:"mac:WindowWillOrderOnScreen",WindowWillResignMain:"mac:WindowWillResignMain",WindowWillResize:"mac:WindowWillResize",WindowWillUnfocus:"mac:WindowWillUnfocus",WindowWillUpdate:"mac:WindowWillUpdate",WindowWillUpdateAlpha:"mac:WindowWillUpdateAlpha",WindowWillUpdateCollectionBehavior:"mac:WindowWillUpdateCollectionBehavior",WindowWillUpdateCollectionProperties:"mac:WindowWillUpdateCollectionProperties",WindowWillUpdateShadow:"mac:WindowWillUpdateShadow",WindowWillUpdateTitle:"mac:WindowWillUpdateTitle",WindowWillUpdateToolbar:"mac:WindowWillUpdateToolbar",WindowWillUpdateVisibility:"mac:WindowWillUpdateVisibility",WindowWillUseStandardFrame:"mac:WindowWillUseStandardFrame",WindowZoomIn:"mac:WindowZoomIn",WindowZoomOut:"mac:WindowZoomOut",WindowZoomReset:"mac:WindowZoomReset"}),Linux:Object.freeze({ApplicationStartup:"linux:ApplicationStartup",SystemThemeChanged:"linux:SystemThemeChanged",WindowDeleteEvent:"linux:WindowDeleteEvent",WindowDidMove:"linux:WindowDidMove",WindowDidResize:"linux:WindowDidResize",WindowFocusIn:"linux:WindowFocusIn",WindowFocusOut:"linux:WindowFocusOut",WindowLoadStarted:"linux:WindowLoadStarted",WindowLoadRedirected:"linux:WindowLoadRedirected",WindowLoadCommitted:"linux:WindowLoadCommitted",WindowLoadFinished:"linux:WindowLoadFinished"}),iOS:Object.freeze({ApplicationDidBecomeActive:"ios:ApplicationDidBecomeActive",ApplicationDidEnterBackground:"ios:ApplicationDidEnterBackground",ApplicationDidFinishLaunching:"ios:ApplicationDidFinishLaunching",ApplicationDidReceiveMemoryWarning:"ios:ApplicationDidReceiveMemoryWarning",ApplicationWillEnterForeground:"ios:ApplicationWillEnterForeground",ApplicationWillResignActive:"ios:ApplicationWillResignActive",ApplicationWillTerminate:"ios:ApplicationWillTerminate",WindowDidLoad:"ios:WindowDidLoad",WindowWillAppear:"ios:WindowWillAppear",WindowDidAppear:"ios:WindowDidAppear",WindowWillDisappear:"ios:WindowWillDisappear",WindowDidDisappear:"ios:WindowDidDisappear",WindowSafeAreaInsetsChanged:"ios:WindowSafeAreaInsetsChanged",WindowOrientationChanged:"ios:WindowOrientationChanged",WindowTouchBegan:"ios:WindowTouchBegan",WindowTouchMoved:"ios:WindowTouchMoved",WindowTouchEnded:"ios:WindowTouchEnded",WindowTouchCancelled:"ios:WindowTouchCancelled",WebViewDidStartNavigation:"ios:WebViewDidStartNavigation",WebViewDidFinishNavigation:"ios:WebViewDidFinishNavigation",WebViewDidFailNavigation:"ios:WebViewDidFailNavigation",WebViewDecidePolicyForNavigationAction:"ios:WebViewDecidePolicyForNavigationAction"}),Common:Object.freeze({ApplicationOpenedWithFile:"common:ApplicationOpenedWithFile",ApplicationStarted:"common:ApplicationStarted",ApplicationLaunchedWithUrl:"common:ApplicationLaunchedWithUrl",ThemeChanged:"common:ThemeChanged",WindowClosing:"common:WindowClosing",WindowDidMove:"common:WindowDidMove",WindowDidResize:"common:WindowDidResize",WindowDPIChanged:"common:WindowDPIChanged",WindowFilesDropped:"common:WindowFilesDropped",WindowFocus:"common:WindowFocus",WindowFullscreen:"common:WindowFullscreen",WindowHide:"common:WindowHide",WindowLostFocus:"common:WindowLostFocus",WindowMaximise:"common:WindowMaximise",WindowMinimise:"common:WindowMinimise",WindowToggleFrameless:"common:WindowToggleFrameless",WindowRestore:"common:WindowRestore",WindowRuntimeReady:"common:WindowRuntimeReady",WindowShow:"common:WindowShow",WindowUnFullscreen:"common:WindowUnFullscreen",WindowUnMaximise:"common:WindowUnMaximise",WindowUnMinimise:"common:WindowUnMinimise",WindowZoom:"common:WindowZoom",WindowZoomIn:"common:WindowZoomIn",WindowZoomOut:"common:WindowZoomOut",WindowZoomReset:"common:WindowZoomReset",WindowDropZoneFilesDropped:"common:WindowDropZoneFilesDropped"})});window._wails=window._wails||{};window._wails.dispatchWailsEvent=Yn;var Gn=m(c.Events),Kn=0,k=class{constructor(e,i){this.name=e,this.data=i!=null?i:null}};function Yn(n){let e=g.get(n.name);if(!e)return;let i=new k(n.name,n.name in Z?Z[n.name](n.data):n.data);"sender"in n&&(i.sender=n.sender),e=e.filter(o=>!o.dispatch(i)),e.length===0?g.delete(n.name):g.set(n.name,e)}function ae(n,e,i){let o=g.get(n)||[],t=new V(n,e,i);return o.push(t),g.set(n,o),()=>$e(t)}function Xn(n,e){return ae(n,e,-1)}function $n(n,e){return ae(n,e,1)}function Qn(...n){n.forEach(e=>g.delete(e))}function qn(){g.clear()}function se(n,e){return Gn(Kn,new k(n,e))}function Qe(){return new MouseEvent("mousedown").buttons===0}function qe(){if(!EventTarget||!AbortSignal||!AbortController)return!1;let n=!0,e=new EventTarget,i=new AbortController;return e.addEventListener("test",()=>{n=!1},{signal:i.signal}),i.abort(),e.dispatchEvent(new CustomEvent("test")),n}function G(n){var e;return n.target instanceof HTMLElement?n.target:!(n.target instanceof HTMLElement)&&n.target instanceof Node&&(e=n.target.parentElement)!=null?e:document.body}var _e=!1;document.addEventListener("DOMContentLoaded",()=>{_e=!0});function Je(n){_e||document.readyState==="complete"?n():document.addEventListener("DOMContentLoaded",n)}var _n="data-wails-dropzone",T="wails-dropzone-hover",u=null,Jn=0,ei=1,ni=2,ii=3,oi=4,ti=5,ri=6,ai=7,si=8,li=9,di=10,ci=11,mi=12,ui=13,wi=14,pi=15,fi=16,gi=17,Wi=18,hi=19,bi=20,vi=21,yi=22,Ci=23,Di=24,Pi=25,Ti=26,Mi=27,Si=28,Ei=29,xi=30,Ai=31,Ri=32,Fi=33,Oi=34,ki=35,Li=36,Ii=37,zi=38,Ui=39,Ni=40,Bi=41,ji=42,Hi=43,Vi=44,Zi=45,Gi=46,Ki=47,Yi=48,Xi=49,$i=50,Qi=51;function en(n){return n?n.closest("[".concat(_n,"]")):null}var r=Symbol("caller"),K=class K{constructor(e=""){this[r]=m(c.Window,e);for(let i of Object.getOwnPropertyNames(K.prototype))i!=="constructor"&&typeof this[i]=="function"&&(this[i]=this[i].bind(this))}Get(e){return new K(e)}Position(){return this[r](Jn)}Center(){return this[r](ei)}Close(){return this[r](ni)}DisableSizeConstraints(){return this[r](ii)}EnableSizeConstraints(){return this[r](oi)}Focus(){return this[r](ti)}ForceReload(){return this[r](ri)}Fullscreen(){return this[r](ai)}GetScreen(){return this[r](si)}GetZoom(){return this[r](li)}Height(){return this[r](di)}Hide(){return this[r](ci)}IsFocused(){return this[r](mi)}IsFullscreen(){return this[r](ui)}IsMaximised(){return this[r](wi)}IsMinimised(){return this[r](pi)}Maximise(){return this[r](fi)}Minimise(){return this[r](gi)}Name(){return this[r](Wi)}OpenDevTools(){return this[r](hi)}RelativePosition(){return this[r](bi)}Reload(){return this[r](vi)}Resizable(){return this[r](yi)}Restore(){return this[r](Ci)}SetPosition(e,i){return this[r](Di,{x:e,y:i})}SetAlwaysOnTop(e){return this[r](Pi,{alwaysOnTop:e})}SetBackgroundColour(e,i,o,t){return this[r](Ti,{r:e,g:i,b:o,a:t})}SetFrameless(e){return this[r](Mi,{frameless:e})}SetFullscreenButtonEnabled(e){return this[r](Si,{enabled:e})}SetMaxSize(e,i){return this[r](Ei,{width:e,height:i})}SetMinSize(e,i){return this[r](xi,{width:e,height:i})}SetRelativePosition(e,i){return this[r](Ai,{x:e,y:i})}SetResizable(e){return this[r](Ri,{resizable:e})}SetSize(e,i){return this[r](Fi,{width:e,height:i})}SetTitle(e){return this[r](Oi,{title:e})}SetZoom(e){return this[r](ki,{zoom:e})}Show(){return this[r](Li)}Size(){return this[r](Ii)}ToggleFullscreen(){return this[r](zi)}ToggleMaximise(){return this[r](Ui)}ToggleFrameless(){return this[r](Ni)}UnFullscreen(){return this[r](Bi)}UnMaximise(){return this[r](ji)}UnMinimise(){return this[r](Hi)}Width(){return this[r](Vi)}Zoom(){return this[r](Zi)}ZoomIn(){return this[r](Gi)}ZoomOut(){return this[r](Ki)}ZoomReset(){return this[r](Yi)}HandlePlatformFileDrop(e,i,o){let t=document.elementFromPoint(i,o),a=en(t);if(!a)return;let s={id:a.id,classList:Array.from(a.classList),attributes:{}};for(let d=0;d{if(i.preventDefault(),i.dataTransfer&&i.dataTransfer.types.includes("Files")){e++;let o=document.elementFromPoint(i.clientX,i.clientY),t=en(o);u&&u!==t&&u.classList.remove(T),t?(t.classList.add(T),i.dataTransfer.dropEffect="copy",u=t):(i.dataTransfer.dropEffect="none",u=null)}},!1),n.addEventListener("dragover",i=>{i.preventDefault(),i.dataTransfer&&i.dataTransfer.types.includes("Files")&&(u?(u.classList.contains(T)||u.classList.add(T),i.dataTransfer.dropEffect="copy"):i.dataTransfer.dropEffect="none")},!1),n.addEventListener("dragleave",i=>{i.preventDefault(),i.dataTransfer&&i.dataTransfer.types.includes("Files")&&(e--,(e===0||i.relatedTarget===null||u&&!u.contains(i.relatedTarget))&&(u&&(u.classList.remove(T),u=null),e=0))},!1),n.addEventListener("drop",i=>{i.preventDefault(),e=0,u&&(u.classList.remove(T),u=null)},!1)}typeof window<"u"&&typeof document<"u"&&_i();var M=qi;function Ji(n,e=null){se(n,e)}function eo(n,e){let i=M.Get(n),o=i[e];if(typeof o=="function")try{o.call(i)}catch(t){}}function nn(n){let e=n.currentTarget;function i(t="Yes"){if(t!=="Yes")return;let a=e.getAttribute("wml-event")||e.getAttribute("data-wml-event"),s=e.getAttribute("wml-target-window")||e.getAttribute("data-wml-target-window")||"",l=e.getAttribute("wml-window")||e.getAttribute("data-wml-window"),d=e.getAttribute("wml-openurl")||e.getAttribute("data-wml-openurl");a!==null&&Ji(a),l!==null&&eo(s,l),d!==null&&ne(d)}let o=e.getAttribute("wml-confirm")||e.getAttribute("data-wml-confirm");o?oe({Title:"Confirm",Message:o,Detached:!1,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(i):i()}var L=Symbol("controller"),S=Symbol("triggerMap"),y=Symbol("elementCount"),ce=class{constructor(){this[L]=new AbortController}set(e,i){return{signal:this[L].signal}}reset(){this[L].abort(),this[L]=new AbortController}},me=class{constructor(){this[S]=new WeakMap,this[y]=0}set(e,i){return this[S].has(e)||this[y]++,this[S].set(e,i),{}}reset(){if(!(this[y]<=0)){for(let e of document.body.querySelectorAll("*")){if(this[y]<=0)break;let i=this[S].get(e);i!=null&&this[y]--;for(let o of i||[])e.removeEventListener(o,nn)}this[S]=new WeakMap,this[y]=0}}},on=qe()?new ce:new me;function no(n){let e=/\S+/g,i=n.getAttribute("wml-trigger")||n.getAttribute("data-wml-trigger")||"click",o=[],t;for(;(t=e.exec(i))!==null;)o.push(t[0]);let a=on.set(n,o);for(let s of o)n.addEventListener(s,nn,a)}function ue(){Je(tn)}function tn(){on.reset(),document.body.querySelectorAll("[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]").forEach(no)}window.wails=pe;ue();var We={};p(We,{Capabilities:()=>so,Environment:()=>lo,HandlePlatformFileDrop:()=>fo,IsAMD64:()=>uo,IsARM:()=>wo,IsARM64:()=>po,IsDarkMode:()=>ao,IsDebug:()=>ge,IsLinux:()=>co,IsMac:()=>mo,IsWindows:()=>X,invoke:()=>C});var Y=m(c.System),io=0,oo=1,to=2,ro=100,fe=(function(){var n,e,i,o,t,a;try{if((e=(n=window.chrome)==null?void 0:n.webview)!=null&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if((t=(o=(i=window.webkit)==null?void 0:i.messageHandlers)==null?void 0:o.external)!=null&&t.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external);if((a=window.wails)!=null&&a.invoke)return s=>window.wails.invoke(typeof s=="string"?s:JSON.stringify(s))}catch(s){}return null})();function C(n){fe==null||fe(n)}function ao(){return Y(io)}async function so(){return Y(to)}function lo(){return Y(oo)}function X(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="windows"}function co(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="linux"}function mo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="darwin"}function uo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="amd64"}function wo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="arm"}function po(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="arm64"}function ge(){var n,e;return!!((e=(n=window._wails)==null?void 0:n.environment)!=null&&e.Debug)}function fo(n,e,i){let o=document.elementFromPoint(e,i),t=o?o.id:"",a=o?Array.from(o.classList):[];Y(ro,{filenames:n,x:e,y:i,elementId:t,classList:a}).then(()=>{}).catch(l=>{})}window.addEventListener("contextmenu",bo);var go=m(c.ContextMenu),Wo=0;function ho(n,e,i,o){go(Wo,{id:n,x:e,y:i,data:o})}function bo(n){let e=G(n),i=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(i){n.preventDefault();let o=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");ho(i,n.clientX,n.clientY,o)}else vo(n,e)}function vo(n,e){if(ge())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":n.preventDefault();return}if(e.isContentEditable)return;let i=window.getSelection(),o=i&&i.toString().length>0;if(o)for(let t=0;tI});function I(n){try{return window._wails.flags[n]}catch(e){throw new Error("Unable to retrieve flag '"+n+"': "+e,{cause:e})}}var z=!1,U=!1,De=!1,x=!1,A=!1,D="",rn="auto",E=0,be=Qe();window._wails=window._wails||{};window._wails.setResizable=n=>{De=n,De||(x=A=!1,f())};var Pe=!1;function yo(){var i,o;let n=(o=(i=window._wails)==null?void 0:i.environment)==null?void 0:o.OS;if(n==="ios"||n==="android")return!0;let e=navigator.userAgent||navigator.vendor||window.opera||"";return/android|iphone|ipad|ipod|iemobile|wpdesktop/i.test(e)}function Te(){if(!Pe&&!yo()){window.addEventListener("mousedown",Ce,{capture:!0}),window.addEventListener("mousemove",Ce,{capture:!0}),window.addEventListener("mouseup",Ce,{capture:!0});for(let n of["click","contextmenu","dblclick"])window.addEventListener(n,Do,{capture:!0});Pe=!0}}Te();document.addEventListener("DOMContentLoaded",Te,{once:!0});var Co=0,an=window.setInterval(()=>{if(Pe){window.clearInterval(an);return}Te(),++Co>100&&window.clearInterval(an)},50);function Do(n){(U||A)&&(n.stopImmediatePropagation(),n.stopPropagation(),n.preventDefault())}var ve=0,Po=1,ye=2;function Ce(n){let e,i=n.buttons;switch(n.type){case"mousedown":e=ve,be||(i=E|1<Fo,Quit:()=>ko,Show:()=>Oo});var Me=m(c.Application),xo=0,Ao=1,Ro=2;function Fo(){return Me(xo)}function Oo(){return Me(Ao)}function ko(){return Me(Ro)}var Le={};p(Le,{ByID:()=>et,ByName:()=>Jo,Call:()=>ke,RuntimeError:()=>Oe});var ln=Function.prototype.toString,R=typeof Reflect=="object"&&Reflect!==null&&Reflect.apply,Ee,$;if(typeof R=="function"&&typeof Object.defineProperty=="function")try{Ee=Object.defineProperty({},"length",{get:function(){throw $}}),$={},R(function(){throw 42},null,Ee)}catch(n){n!==$&&(R=null)}else R=null;var Lo=/^\s*class\b/,Ae=function(e){try{var i=ln.call(e);return Lo.test(i)}catch(o){return!1}},xe=function(e){try{return Ae(e)?!1:(ln.call(e),!0)}catch(i){return!1}},Q=Object.prototype.toString,Io="[object Object]",zo="[object Function]",Uo="[object GeneratorFunction]",No="[object HTMLAllCollection]",Bo="[object HTML document.all class]",jo="[object HTMLCollection]",Ho=typeof Symbol=="function"&&!!Symbol.toStringTag,Vo=!(0 in[,]),Re=function(){return!1};typeof document=="object"&&(sn=document.all,Q.call(sn)===Q.call(document.all)&&(Re=function(e){if((Vo||!e)&&(typeof e>"u"||typeof e=="object"))try{var i=Q.call(e);return(i===No||i===Bo||i===jo||i===Io)&&e("")==null}catch(o){}return!1}));var sn;function Zo(n){if(Re(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;try{R(n,null,Ee)}catch(e){if(e!==$)return!1}return!Ae(n)&&xe(n)}function Go(n){if(Re(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;if(Ho)return xe(n);if(Ae(n))return!1;var e=Q.call(n);return e!==zo&&e!==Uo&&!/^\[object HTML/.test(e)?!1:xe(n)}var h=R?Zo:Go;var O=class extends Error{constructor(e,i){super(e,i),this.name="CancelError"}},b=class extends Error{constructor(e,i,o){super((o!=null?o:"Unhandled rejection in cancelled promise.")+" Reason: "+Ko(i),{cause:i}),this.promise=e,this.name="CancelledRejectionError"}},W=Symbol("barrier"),Fe=Symbol("cancelImpl"),un,dn=(un=Symbol.species)!=null?un:Symbol("speciesPolyfill"),N=class n extends Promise{constructor(e,i){let o,t;if(super((d,w)=>{o=d,t=w}),this.constructor[dn]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let a={promise:this,resolve:o,reject:t,get oncancelled(){return i!=null?i:null},set oncancelled(d){i=d!=null?d:void 0}},s={get root(){return s},resolving:!1,settled:!1};Object.defineProperties(this,{[W]:{configurable:!1,enumerable:!1,writable:!0,value:null},[Fe]:{configurable:!1,enumerable:!1,writable:!1,value:wn(a,s)}});let l=fn(a,s);try{e(pn(a,s),l)}catch(d){s.resolving||l(d)}}cancel(e){return new n(i=>{Promise.all([this[Fe](new O("Promise cancelled.",{cause:e})),Yo(this)]).then(()=>i(),()=>i())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>{this.cancel(e.reason)},{capture:!0}),this}then(e,i,o){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(h(e)||(e=cn),h(i)||(i=mn),e===cn&&i==mn)return new n(a=>a(this));let t={};return this[W]=t,new n((a,s)=>{super.then(l=>{var d;this[W]===t&&(this[W]=null),(d=t.resolve)==null||d.call(t);try{a(e(l))}catch(w){s(w)}},l=>{var d;this[W]===t&&(this[W]=null),(d=t.resolve)==null||d.call(t);try{a(i(l))}catch(w){s(w)}})},async a=>{try{return o==null?void 0:o(a)}finally{await this.cancel(a)}})}catch(e,i){return this.then(void 0,e,i)}finally(e,i){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return h(e)?this.then(o=>n.resolve(e()).then(()=>o),o=>n.resolve(e()).then(()=>{throw o}),i):this.then(e,e,i)}static get[(W,Fe,dn)](){return Promise}static all(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,a)=>{Promise.all(i).then(t,a)},t=>q(o,i,t));return o}static allSettled(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,a)=>{Promise.allSettled(i).then(t,a)},t=>q(o,i,t));return o}static any(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,a)=>{Promise.any(i).then(t,a)},t=>q(o,i,t));return o}static race(e){let i=Array.from(e),o=new n((t,a)=>{Promise.race(i).then(t,a)},t=>q(o,i,t));return o}static cancel(e){let i=new n(()=>{});return i.cancel(e),i}static timeout(e,i){let o=new n(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>{o.cancel(i)}):setTimeout(()=>{o.cancel(i)},e),o}static sleep(e,i){return new n(o=>{setTimeout(()=>o(i),e)})}static reject(e){return new n((i,o)=>o(e))}static resolve(e){return e instanceof n?e:new n(i=>i(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new n((i,o)=>{e.resolve=i,e.reject=o},i=>{var o;(o=e.oncancelled)==null||o.call(e,i)}),e}};function wn(n,e){let i;return o=>{if(e.settled||(e.settled=!0,e.reason=o,n.reject(o),Promise.prototype.then.call(n.promise,void 0,t=>{if(t!==o)throw t})),!(!e.reason||!n.oncancelled))return i=new Promise(t=>{try{t(n.oncancelled(e.reason.cause))}catch(a){Promise.reject(new b(n.promise,a,"Unhandled exception in oncancelled callback."))}}).catch(t=>{Promise.reject(new b(n.promise,t,"Unhandled rejection in oncancelled callback."))}),n.oncancelled=null,i}}function pn(n,e){return i=>{if(!e.resolving){if(e.resolving=!0,i===n.promise){if(e.settled)return;e.settled=!0,n.reject(new TypeError("A promise cannot be resolved with itself."));return}if(i!=null&&(typeof i=="object"||typeof i=="function")){let o;try{o=i.then}catch(t){e.settled=!0,n.reject(t);return}if(h(o)){try{let s=i.cancel;if(h(s)){let l=d=>{Reflect.apply(s,i,[d])};e.reason?wn(Ke(Ge({},n),{oncancelled:l}),e)(e.reason):n.oncancelled=l}}catch(s){}let t={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(s){this.root.settled=s},get reason(){return this.root.reason}},a=fn(n,t);try{Reflect.apply(o,i,[pn(n,t),a])}catch(s){a(s)}return}}e.settled||(e.settled=!0,n.resolve(i))}}}function fn(n,e){return i=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(i instanceof O&&e.reason instanceof O&&Object.is(i.cause,e.reason.cause))return}catch(o){}Promise.reject(new b(n.promise,i))}else e.settled=!0,n.reject(i)}}function q(n,e,i){let o=[];for(let t of e){let a;try{if(!h(t.then)||(a=t.cancel,!h(a)))continue}catch(l){continue}let s;try{s=Reflect.apply(a,t,[i])}catch(l){Promise.reject(new b(n,l,"Unhandled exception in cancel method."));continue}s&&o.push((s instanceof Promise?s:Promise.resolve(s)).catch(l=>{Promise.reject(new b(n,l,"Unhandled rejection in cancel method."))}))}return Promise.all(o)}function cn(n){return n}function mn(n){throw n}function Ko(n){try{if(n instanceof Error||typeof n!="object"||n.toString!==Object.prototype.toString)return""+n}catch(e){}try{return JSON.stringify(n)}catch(e){}try{return Object.prototype.toString.call(n)}catch(e){}return""}function Yo(n){var i;let e=(i=n[W])!=null?i:{};return"promise"in e||Object.assign(e,F()),n[W]==null&&(e.resolve(),n[W]=e),e.promise}var F=Promise.withResolvers;F&&typeof F=="function"?F=F.bind(Promise):F=function(){let n,e;return{promise:new Promise((o,t)=>{n=o,e=t}),resolve:n,reject:e}};window._wails=window._wails||{};var Xo=m(c.Call),$o=m(c.CancelCall),B=new Map,Qo=0,qo=0,Oe=class extends Error{constructor(e,i){super(e,i),this.name="RuntimeError"}};function _o(){let n;do n=j();while(B.has(n));return n}function ke(n){let e=_o(),i=N.withResolvers();B.set(e,{resolve:i.resolve,reject:i.reject});let o=Xo(Qo,Object.assign({"call-id":e},n)),t=!0;o.then(s=>{t=!1,B.delete(e),i.resolve(s)},s=>{t=!1,B.delete(e),i.reject(s)});let a=()=>(B.delete(e),$o(qo,{"call-id":e}).catch(s=>{}));return i.oncancelled=()=>t?a():o.then(a),i.promise}function Jo(n,...e){return ke({methodName:n,args:e})}function et(n,...e){return ke({methodID:n,args:e})}var Ie={};p(Ie,{SetText:()=>ot,Text:()=>tt});var gn=m(c.Clipboard),nt=0,it=1;function ot(n){return gn(nt,{text:n})}function tt(){return gn(it)}var Ue={};p(Ue,{GetAll:()=>lt,GetCurrent:()=>ct,GetPrimary:()=>dt});var ze=m(c.Screens),rt=0,at=1,st=2;function lt(){return ze(rt)}function dt(){return ze(at)}function ct(){return ze(st)}var je={};p(je,{Device:()=>Be,Haptics:()=>Ne});var Wn=m(c.IOS),mt=0,ut=1,Ne;(e=>{function n(i="medium"){return Wn(mt,{style:i})}e.Impact=n})(Ne||(Ne={}));var Be;(e=>{function n(){return Wn(ut)}e.Info=n})(Be||(Be={}));window._wails=window._wails||{};window._wails.invoke=C;window._wails.handlePlatformFileDrop=M.HandlePlatformFileDrop.bind(M);C("wails:runtime:ready");export{Se as Application,ie as Browser,Le as Call,O as CancelError,N as CancellablePromise,b as CancelledRejectionError,Ie as Clipboard,re as Create,te as Dialogs,le as Events,he as Flags,je as IOS,Ue as Screens,We as System,we as WML,M as Window,ee as clientId,Xe as getTransport,c as objectNames,Ye as setTransport}; +var Ge=Object.defineProperty,Cn=Object.defineProperties;var Pn=Object.getOwnPropertyDescriptors;var Ze=Object.getOwnPropertySymbols;var Tn=Object.prototype.hasOwnProperty,Mn=Object.prototype.propertyIsEnumerable;var _e=(n,e,i)=>e in n?Ge(n,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):n[e]=i,Ke=(n,e)=>{for(var i in e||(e={}))Tn.call(e,i)&&_e(n,i,e[i]);if(Ze)for(var i of Ze(e))Mn.call(e,i)&&_e(n,i,e[i]);return n},Ye=(n,e)=>Cn(n,Pn(e));var p=(n,e)=>{for(var i in e)Ge(n,i,{get:e[i],enumerable:!0})};var fe={};p(fe,{Application:()=>xe,Browser:()=>ie,Call:()=>Ue,CancelError:()=>L,CancellablePromise:()=>B,CancelledRejectionError:()=>v,Clipboard:()=>ze,Create:()=>re,Dialogs:()=>te,Events:()=>le,Flags:()=>ve,IOS:()=>Ve,Screens:()=>je,System:()=>be,WML:()=>pe,Window:()=>E,clientId:()=>ee,getTransport:()=>Qe,objectNames:()=>m,setTransport:()=>Xe});var pe={};p(pe,{Enable:()=>we,Reload:()=>dn});var ie={};p(ie,{OpenURL:()=>ne});var Sn="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function V(n=21){let e="",i=n|0;for(;i--;)e+=Sn[Math.random()*64|0];return e}var En=window.location.origin+"/wails/runtime";var m=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10,IOS:11}),ee=V(),Z=null;function Xe(n){Z=n}function Qe(){return Z}function u(n,e=""){return function(i,o=null){return xn(n,i,e,o)}}async function xn(n,e,i,o){var d,w;if(Z)return Z.call(n,e,i,o);let t=new URL(En),r={object:n,method:e};o!=null&&(r.args=o);let a={"x-wails-client-id":ee,"Content-Type":"application/json"};i&&(a["x-wails-window-name"]=i);let l=await fetch(t,{method:"POST",headers:a,body:JSON.stringify(r)});if(!l.ok)throw new Error(await l.text());return((w=(d=l.headers.get("Content-Type"))==null?void 0:d.indexOf("application/json"))!=null?w:-1)!==-1?l.json():l.text()}var An=u(m.Browser),Rn=0;function ne(n){return An(Rn,{url:n.toString()})}var te={};p(te,{Error:()=>Bn,Info:()=>Nn,OpenFile:()=>Hn,Question:()=>oe,SaveFile:()=>Vn,Warning:()=>jn});window._wails=window._wails||{};var Fn=u(m.Dialog),On=0,kn=1,Ln=2,In=3,Un=4,zn=5;function S(n,e={}){return Fn(n,e)}function Nn(n){return S(On,n)}function jn(n){return S(kn,n)}function Bn(n){return S(Ln,n)}function oe(n){return S(In,n)}function Hn(n){var e;return(e=S(Un,n))!=null?e:[]}function Vn(n){return S(zn,n)}var le={};p(le,{Emit:()=>se,Off:()=>ni,OffAll:()=>ii,On:()=>Jn,OnMultiple:()=>ae,Once:()=>ei,Types:()=>Xn,WailsEvent:()=>I});var g=new Map,_=class{constructor(e,i,o){this.eventName=e,this.callback=i,this.maxCallbacks=o||-1}dispatch(e){try{this.callback(e)}catch(i){}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}};function qe(n){let e=g.get(n.eventName);e&&(e=e.filter(i=>i!==n),e.length===0?g.delete(n.eventName):g.set(n.eventName,e))}var re={};p(re,{Any:()=>D,Array:()=>_n,ByteSlice:()=>Zn,Events:()=>G,Map:()=>Gn,Nullable:()=>Kn,Struct:()=>Yn});function D(n){return n}function Zn(n){return n==null?"":n}function _n(n){return n===D?e=>e===null?[]:e:e=>{if(e===null)return[];for(let i=0;ii===null?{}:i:i=>{if(i===null)return{};for(let o in i)i[o]=e(i[o]);return i}}function Kn(n){return n===D?D:e=>e===null?null:n(e)}function Yn(n){let e=!0;for(let i in n)if(n[i]!==D){e=!1;break}return e?D:i=>{for(let o in n)o in i&&(i[o]=n[o](i[o]));return i}}var G={};var Xn=Object.freeze({Windows:Object.freeze({APMPowerSettingChange:"windows:APMPowerSettingChange",APMPowerStatusChange:"windows:APMPowerStatusChange",APMResumeAutomatic:"windows:APMResumeAutomatic",APMResumeSuspend:"windows:APMResumeSuspend",APMSuspend:"windows:APMSuspend",ApplicationStarted:"windows:ApplicationStarted",SystemThemeChanged:"windows:SystemThemeChanged",WebViewNavigationCompleted:"windows:WebViewNavigationCompleted",WindowActive:"windows:WindowActive",WindowBackgroundErase:"windows:WindowBackgroundErase",WindowClickActive:"windows:WindowClickActive",WindowClosing:"windows:WindowClosing",WindowDidMove:"windows:WindowDidMove",WindowDidResize:"windows:WindowDidResize",WindowDPIChanged:"windows:WindowDPIChanged",WindowDragDrop:"windows:WindowDragDrop",WindowDragEnter:"windows:WindowDragEnter",WindowDragLeave:"windows:WindowDragLeave",WindowDragOver:"windows:WindowDragOver",WindowEndMove:"windows:WindowEndMove",WindowEndResize:"windows:WindowEndResize",WindowFullscreen:"windows:WindowFullscreen",WindowHide:"windows:WindowHide",WindowInactive:"windows:WindowInactive",WindowKeyDown:"windows:WindowKeyDown",WindowKeyUp:"windows:WindowKeyUp",WindowKillFocus:"windows:WindowKillFocus",WindowNonClientHit:"windows:WindowNonClientHit",WindowNonClientMouseDown:"windows:WindowNonClientMouseDown",WindowNonClientMouseLeave:"windows:WindowNonClientMouseLeave",WindowNonClientMouseMove:"windows:WindowNonClientMouseMove",WindowNonClientMouseUp:"windows:WindowNonClientMouseUp",WindowPaint:"windows:WindowPaint",WindowRestore:"windows:WindowRestore",WindowSetFocus:"windows:WindowSetFocus",WindowShow:"windows:WindowShow",WindowStartMove:"windows:WindowStartMove",WindowStartResize:"windows:WindowStartResize",WindowUnFullscreen:"windows:WindowUnFullscreen",WindowZOrderChanged:"windows:WindowZOrderChanged",WindowMinimise:"windows:WindowMinimise",WindowUnMinimise:"windows:WindowUnMinimise",WindowMaximise:"windows:WindowMaximise",WindowUnMaximise:"windows:WindowUnMaximise"}),Mac:Object.freeze({ApplicationDidBecomeActive:"mac:ApplicationDidBecomeActive",ApplicationDidChangeBackingProperties:"mac:ApplicationDidChangeBackingProperties",ApplicationDidChangeEffectiveAppearance:"mac:ApplicationDidChangeEffectiveAppearance",ApplicationDidChangeIcon:"mac:ApplicationDidChangeIcon",ApplicationDidChangeOcclusionState:"mac:ApplicationDidChangeOcclusionState",ApplicationDidChangeScreenParameters:"mac:ApplicationDidChangeScreenParameters",ApplicationDidChangeStatusBarFrame:"mac:ApplicationDidChangeStatusBarFrame",ApplicationDidChangeStatusBarOrientation:"mac:ApplicationDidChangeStatusBarOrientation",ApplicationDidChangeTheme:"mac:ApplicationDidChangeTheme",ApplicationDidFinishLaunching:"mac:ApplicationDidFinishLaunching",ApplicationDidHide:"mac:ApplicationDidHide",ApplicationDidResignActive:"mac:ApplicationDidResignActive",ApplicationDidUnhide:"mac:ApplicationDidUnhide",ApplicationDidUpdate:"mac:ApplicationDidUpdate",ApplicationShouldHandleReopen:"mac:ApplicationShouldHandleReopen",ApplicationWillBecomeActive:"mac:ApplicationWillBecomeActive",ApplicationWillFinishLaunching:"mac:ApplicationWillFinishLaunching",ApplicationWillHide:"mac:ApplicationWillHide",ApplicationWillResignActive:"mac:ApplicationWillResignActive",ApplicationWillTerminate:"mac:ApplicationWillTerminate",ApplicationWillUnhide:"mac:ApplicationWillUnhide",ApplicationWillUpdate:"mac:ApplicationWillUpdate",MenuDidAddItem:"mac:MenuDidAddItem",MenuDidBeginTracking:"mac:MenuDidBeginTracking",MenuDidClose:"mac:MenuDidClose",MenuDidDisplayItem:"mac:MenuDidDisplayItem",MenuDidEndTracking:"mac:MenuDidEndTracking",MenuDidHighlightItem:"mac:MenuDidHighlightItem",MenuDidOpen:"mac:MenuDidOpen",MenuDidPopUp:"mac:MenuDidPopUp",MenuDidRemoveItem:"mac:MenuDidRemoveItem",MenuDidSendAction:"mac:MenuDidSendAction",MenuDidSendActionToItem:"mac:MenuDidSendActionToItem",MenuDidUpdate:"mac:MenuDidUpdate",MenuWillAddItem:"mac:MenuWillAddItem",MenuWillBeginTracking:"mac:MenuWillBeginTracking",MenuWillDisplayItem:"mac:MenuWillDisplayItem",MenuWillEndTracking:"mac:MenuWillEndTracking",MenuWillHighlightItem:"mac:MenuWillHighlightItem",MenuWillOpen:"mac:MenuWillOpen",MenuWillPopUp:"mac:MenuWillPopUp",MenuWillRemoveItem:"mac:MenuWillRemoveItem",MenuWillSendAction:"mac:MenuWillSendAction",MenuWillSendActionToItem:"mac:MenuWillSendActionToItem",MenuWillUpdate:"mac:MenuWillUpdate",WebViewDidCommitNavigation:"mac:WebViewDidCommitNavigation",WebViewDidFinishNavigation:"mac:WebViewDidFinishNavigation",WebViewDidReceiveServerRedirectForProvisionalNavigation:"mac:WebViewDidReceiveServerRedirectForProvisionalNavigation",WebViewDidStartProvisionalNavigation:"mac:WebViewDidStartProvisionalNavigation",WindowDidBecomeKey:"mac:WindowDidBecomeKey",WindowDidBecomeMain:"mac:WindowDidBecomeMain",WindowDidBeginSheet:"mac:WindowDidBeginSheet",WindowDidChangeAlpha:"mac:WindowDidChangeAlpha",WindowDidChangeBackingLocation:"mac:WindowDidChangeBackingLocation",WindowDidChangeBackingProperties:"mac:WindowDidChangeBackingProperties",WindowDidChangeCollectionBehavior:"mac:WindowDidChangeCollectionBehavior",WindowDidChangeEffectiveAppearance:"mac:WindowDidChangeEffectiveAppearance",WindowDidChangeOcclusionState:"mac:WindowDidChangeOcclusionState",WindowDidChangeOrderingMode:"mac:WindowDidChangeOrderingMode",WindowDidChangeScreen:"mac:WindowDidChangeScreen",WindowDidChangeScreenParameters:"mac:WindowDidChangeScreenParameters",WindowDidChangeScreenProfile:"mac:WindowDidChangeScreenProfile",WindowDidChangeScreenSpace:"mac:WindowDidChangeScreenSpace",WindowDidChangeScreenSpaceProperties:"mac:WindowDidChangeScreenSpaceProperties",WindowDidChangeSharingType:"mac:WindowDidChangeSharingType",WindowDidChangeSpace:"mac:WindowDidChangeSpace",WindowDidChangeSpaceOrderingMode:"mac:WindowDidChangeSpaceOrderingMode",WindowDidChangeTitle:"mac:WindowDidChangeTitle",WindowDidChangeToolbar:"mac:WindowDidChangeToolbar",WindowDidDeminiaturize:"mac:WindowDidDeminiaturize",WindowDidEndSheet:"mac:WindowDidEndSheet",WindowDidEnterFullScreen:"mac:WindowDidEnterFullScreen",WindowDidEnterVersionBrowser:"mac:WindowDidEnterVersionBrowser",WindowDidExitFullScreen:"mac:WindowDidExitFullScreen",WindowDidExitVersionBrowser:"mac:WindowDidExitVersionBrowser",WindowDidExpose:"mac:WindowDidExpose",WindowDidFocus:"mac:WindowDidFocus",WindowDidMiniaturize:"mac:WindowDidMiniaturize",WindowDidMove:"mac:WindowDidMove",WindowDidOrderOffScreen:"mac:WindowDidOrderOffScreen",WindowDidOrderOnScreen:"mac:WindowDidOrderOnScreen",WindowDidResignKey:"mac:WindowDidResignKey",WindowDidResignMain:"mac:WindowDidResignMain",WindowDidResize:"mac:WindowDidResize",WindowDidUpdate:"mac:WindowDidUpdate",WindowDidUpdateAlpha:"mac:WindowDidUpdateAlpha",WindowDidUpdateCollectionBehavior:"mac:WindowDidUpdateCollectionBehavior",WindowDidUpdateCollectionProperties:"mac:WindowDidUpdateCollectionProperties",WindowDidUpdateShadow:"mac:WindowDidUpdateShadow",WindowDidUpdateTitle:"mac:WindowDidUpdateTitle",WindowDidUpdateToolbar:"mac:WindowDidUpdateToolbar",WindowDidZoom:"mac:WindowDidZoom",WindowFileDraggingEntered:"mac:WindowFileDraggingEntered",WindowFileDraggingExited:"mac:WindowFileDraggingExited",WindowFileDraggingPerformed:"mac:WindowFileDraggingPerformed",WindowHide:"mac:WindowHide",WindowMaximise:"mac:WindowMaximise",WindowUnMaximise:"mac:WindowUnMaximise",WindowMinimise:"mac:WindowMinimise",WindowUnMinimise:"mac:WindowUnMinimise",WindowShouldClose:"mac:WindowShouldClose",WindowShow:"mac:WindowShow",WindowWillBecomeKey:"mac:WindowWillBecomeKey",WindowWillBecomeMain:"mac:WindowWillBecomeMain",WindowWillBeginSheet:"mac:WindowWillBeginSheet",WindowWillChangeOrderingMode:"mac:WindowWillChangeOrderingMode",WindowWillClose:"mac:WindowWillClose",WindowWillDeminiaturize:"mac:WindowWillDeminiaturize",WindowWillEnterFullScreen:"mac:WindowWillEnterFullScreen",WindowWillEnterVersionBrowser:"mac:WindowWillEnterVersionBrowser",WindowWillExitFullScreen:"mac:WindowWillExitFullScreen",WindowWillExitVersionBrowser:"mac:WindowWillExitVersionBrowser",WindowWillFocus:"mac:WindowWillFocus",WindowWillMiniaturize:"mac:WindowWillMiniaturize",WindowWillMove:"mac:WindowWillMove",WindowWillOrderOffScreen:"mac:WindowWillOrderOffScreen",WindowWillOrderOnScreen:"mac:WindowWillOrderOnScreen",WindowWillResignMain:"mac:WindowWillResignMain",WindowWillResize:"mac:WindowWillResize",WindowWillUnfocus:"mac:WindowWillUnfocus",WindowWillUpdate:"mac:WindowWillUpdate",WindowWillUpdateAlpha:"mac:WindowWillUpdateAlpha",WindowWillUpdateCollectionBehavior:"mac:WindowWillUpdateCollectionBehavior",WindowWillUpdateCollectionProperties:"mac:WindowWillUpdateCollectionProperties",WindowWillUpdateShadow:"mac:WindowWillUpdateShadow",WindowWillUpdateTitle:"mac:WindowWillUpdateTitle",WindowWillUpdateToolbar:"mac:WindowWillUpdateToolbar",WindowWillUpdateVisibility:"mac:WindowWillUpdateVisibility",WindowWillUseStandardFrame:"mac:WindowWillUseStandardFrame",WindowZoomIn:"mac:WindowZoomIn",WindowZoomOut:"mac:WindowZoomOut",WindowZoomReset:"mac:WindowZoomReset"}),Linux:Object.freeze({ApplicationStartup:"linux:ApplicationStartup",SystemThemeChanged:"linux:SystemThemeChanged",WindowDeleteEvent:"linux:WindowDeleteEvent",WindowDidMove:"linux:WindowDidMove",WindowDidResize:"linux:WindowDidResize",WindowFocusIn:"linux:WindowFocusIn",WindowFocusOut:"linux:WindowFocusOut",WindowLoadStarted:"linux:WindowLoadStarted",WindowLoadRedirected:"linux:WindowLoadRedirected",WindowLoadCommitted:"linux:WindowLoadCommitted",WindowLoadFinished:"linux:WindowLoadFinished"}),iOS:Object.freeze({ApplicationDidBecomeActive:"ios:ApplicationDidBecomeActive",ApplicationDidEnterBackground:"ios:ApplicationDidEnterBackground",ApplicationDidFinishLaunching:"ios:ApplicationDidFinishLaunching",ApplicationDidReceiveMemoryWarning:"ios:ApplicationDidReceiveMemoryWarning",ApplicationWillEnterForeground:"ios:ApplicationWillEnterForeground",ApplicationWillResignActive:"ios:ApplicationWillResignActive",ApplicationWillTerminate:"ios:ApplicationWillTerminate",WindowDidLoad:"ios:WindowDidLoad",WindowWillAppear:"ios:WindowWillAppear",WindowDidAppear:"ios:WindowDidAppear",WindowWillDisappear:"ios:WindowWillDisappear",WindowDidDisappear:"ios:WindowDidDisappear",WindowSafeAreaInsetsChanged:"ios:WindowSafeAreaInsetsChanged",WindowOrientationChanged:"ios:WindowOrientationChanged",WindowTouchBegan:"ios:WindowTouchBegan",WindowTouchMoved:"ios:WindowTouchMoved",WindowTouchEnded:"ios:WindowTouchEnded",WindowTouchCancelled:"ios:WindowTouchCancelled",WebViewDidStartNavigation:"ios:WebViewDidStartNavigation",WebViewDidFinishNavigation:"ios:WebViewDidFinishNavigation",WebViewDidFailNavigation:"ios:WebViewDidFailNavigation",WebViewDecidePolicyForNavigationAction:"ios:WebViewDecidePolicyForNavigationAction"}),Common:Object.freeze({ApplicationOpenedWithFile:"common:ApplicationOpenedWithFile",ApplicationStarted:"common:ApplicationStarted",ApplicationLaunchedWithUrl:"common:ApplicationLaunchedWithUrl",ThemeChanged:"common:ThemeChanged",WindowClosing:"common:WindowClosing",WindowDidMove:"common:WindowDidMove",WindowDidResize:"common:WindowDidResize",WindowDPIChanged:"common:WindowDPIChanged",WindowFilesDropped:"common:WindowFilesDropped",WindowFocus:"common:WindowFocus",WindowFullscreen:"common:WindowFullscreen",WindowHide:"common:WindowHide",WindowLostFocus:"common:WindowLostFocus",WindowMaximise:"common:WindowMaximise",WindowMinimise:"common:WindowMinimise",WindowToggleFrameless:"common:WindowToggleFrameless",WindowRestore:"common:WindowRestore",WindowRuntimeReady:"common:WindowRuntimeReady",WindowShow:"common:WindowShow",WindowUnFullscreen:"common:WindowUnFullscreen",WindowUnMaximise:"common:WindowUnMaximise",WindowUnMinimise:"common:WindowUnMinimise",WindowZoom:"common:WindowZoom",WindowZoomIn:"common:WindowZoomIn",WindowZoomOut:"common:WindowZoomOut",WindowZoomReset:"common:WindowZoomReset"})});window._wails=window._wails||{};window._wails.dispatchWailsEvent=$n;var Qn=u(m.Events),qn=0,I=class{constructor(e,i){this.name=e,this.data=i!=null?i:null}};function $n(n){let e=g.get(n.name);if(!e)return;let i=new I(n.name,n.name in G?G[n.name](n.data):n.data);"sender"in n&&(i.sender=n.sender),e=e.filter(o=>!o.dispatch(i)),e.length===0?g.delete(n.name):g.set(n.name,e)}function ae(n,e,i){let o=g.get(n)||[],t=new _(n,e,i);return o.push(t),g.set(n,o),()=>qe(t)}function Jn(n,e){return ae(n,e,-1)}function ei(n,e){return ae(n,e,1)}function ni(...n){n.forEach(e=>g.delete(e))}function ii(){g.clear()}function se(n,e){return Qn(qn,new I(n,e))}function $e(){return new MouseEvent("mousedown").buttons===0}function Je(){if(!EventTarget||!AbortSignal||!AbortController)return!1;let n=!0,e=new EventTarget,i=new AbortController;return e.addEventListener("test",()=>{n=!1},{signal:i.signal}),i.abort(),e.dispatchEvent(new CustomEvent("test")),n}function K(n){var e;return n.target instanceof HTMLElement?n.target:!(n.target instanceof HTMLElement)&&n.target instanceof Node&&(e=n.target.parentElement)!=null?e:document.body}var en=!1;document.addEventListener("DOMContentLoaded",()=>{en=!0});function nn(n){en||document.readyState==="complete"?n():document.addEventListener("DOMContentLoaded",n)}var oi="data-file-drop-target",h="file-drop-target-active",c=null,ti=0,ri=1,ai=2,si=3,li=4,di=5,ci=6,mi=7,ui=8,wi=9,pi=10,fi=11,gi=12,hi=13,Wi=14,bi=15,vi=16,yi=17,Di=18,Ci=19,Pi=20,Ti=21,Mi=22,Si=23,Ei=24,xi=25,Ai=26,Ri=27,Fi=28,Oi=29,ki=30,Li=31,Ii=32,Ui=33,zi=34,Ni=35,ji=36,Bi=37,Hi=38,Vi=39,Zi=40,_i=41,Gi=42,Ki=43,Yi=44,Xi=45,Qi=46,qi=47,$i=48,Ji=49,eo=50,no=51;function Y(n){return n?n.closest("[".concat(oi,"]")):null}function io(){var n,e,i,o;return((e=(n=window.chrome)==null?void 0:n.webview)==null?void 0:e.postMessageWithAdditionalObjects)==null?!1:((o=(i=window._wails)==null?void 0:i.flags)==null?void 0:o.enableFileDrop)===!0}function oo(n,e,i){var o,t;(t=(o=window.chrome)==null?void 0:o.webview)!=null&&t.postMessageWithAdditionalObjects&&window.chrome.webview.postMessageWithAdditionalObjects("file:drop:".concat(n,":").concat(e),i)}var ce=!1;function on(){ce=!1,c&&(c.classList.remove(h),c=null)}function tn(){var n,e;((e=(n=window._wails)==null?void 0:n.flags)==null?void 0:e.enableFileDrop)!==!1&&(ce=!0)}function rn(){on()}function an(n,e){var t,r;if(!ce||((r=(t=window._wails)==null?void 0:t.flags)==null?void 0:r.enableFileDrop)===!1)return;let i=document.elementFromPoint(n,e),o=Y(i);c&&c!==o&&c.classList.remove(h),o?(o.classList.add(h),c=o):c=null}var s=Symbol("caller"),X=class X{constructor(e=""){this[s]=u(m.Window,e);for(let i of Object.getOwnPropertyNames(X.prototype))i!=="constructor"&&typeof this[i]=="function"&&(this[i]=this[i].bind(this))}Get(e){return new X(e)}Position(){return this[s](ti)}Center(){return this[s](ri)}Close(){return this[s](ai)}DisableSizeConstraints(){return this[s](si)}EnableSizeConstraints(){return this[s](li)}Focus(){return this[s](di)}ForceReload(){return this[s](ci)}Fullscreen(){return this[s](mi)}GetScreen(){return this[s](ui)}GetZoom(){return this[s](wi)}Height(){return this[s](pi)}Hide(){return this[s](fi)}IsFocused(){return this[s](gi)}IsFullscreen(){return this[s](hi)}IsMaximised(){return this[s](Wi)}IsMinimised(){return this[s](bi)}Maximise(){return this[s](vi)}Minimise(){return this[s](yi)}Name(){return this[s](Di)}OpenDevTools(){return this[s](Ci)}RelativePosition(){return this[s](Pi)}Reload(){return this[s](Ti)}Resizable(){return this[s](Mi)}Restore(){return this[s](Si)}SetPosition(e,i){return this[s](Ei,{x:e,y:i})}SetAlwaysOnTop(e){return this[s](xi,{alwaysOnTop:e})}SetBackgroundColour(e,i,o,t){return this[s](Ai,{r:e,g:i,b:o,a:t})}SetFrameless(e){return this[s](Ri,{frameless:e})}SetFullscreenButtonEnabled(e){return this[s](Fi,{enabled:e})}SetMaxSize(e,i){return this[s](Oi,{width:e,height:i})}SetMinSize(e,i){return this[s](ki,{width:e,height:i})}SetRelativePosition(e,i){return this[s](Li,{x:e,y:i})}SetResizable(e){return this[s](Ii,{resizable:e})}SetSize(e,i){return this[s](Ui,{width:e,height:i})}SetTitle(e){return this[s](zi,{title:e})}SetZoom(e){return this[s](Ni,{zoom:e})}Show(){return this[s](ji)}Size(){return this[s](Bi)}ToggleFullscreen(){return this[s](Hi)}ToggleMaximise(){return this[s](Vi)}ToggleFrameless(){return this[s](Zi)}UnFullscreen(){return this[s](_i)}UnMaximise(){return this[s](Gi)}UnMinimise(){return this[s](Ki)}Width(){return this[s](Yi)}Zoom(){return this[s](Xi)}ZoomIn(){return this[s](Qi)}ZoomOut(){return this[s](qi)}ZoomReset(){return this[s]($i)}HandlePlatformFileDrop(e,i,o){var d,w;if(((w=(d=window._wails)==null?void 0:d.flags)==null?void 0:w.enableFileDrop)===!1)return;let t=document.elementFromPoint(i,o),r=Y(t);if(!r)return;let a={id:r.id,classList:Array.from(r.classList),attributes:{}};for(let y=0;y{var r,a,l;if(!((r=i.dataTransfer)!=null&&r.types.includes("Files")))return;if(i.preventDefault(),((l=(a=window._wails)==null?void 0:a.flags)==null?void 0:l.enableFileDrop)===!1){i.dataTransfer.dropEffect="none";return}e++;let o=document.elementFromPoint(i.clientX,i.clientY),t=Y(o);c&&c!==t&&c.classList.remove(h),t?(t.classList.add(h),i.dataTransfer.dropEffect="copy",c=t):(i.dataTransfer.dropEffect="none",c=null)},!1),n.addEventListener("dragover",i=>{var r,a,l;if(!((r=i.dataTransfer)!=null&&r.types.includes("Files")))return;if(i.preventDefault(),((l=(a=window._wails)==null?void 0:a.flags)==null?void 0:l.enableFileDrop)===!1){i.dataTransfer.dropEffect="none";return}let o=document.elementFromPoint(i.clientX,i.clientY),t=Y(o);c&&c!==t&&c.classList.remove(h),t?(t.classList.contains(h)||t.classList.add(h),i.dataTransfer.dropEffect="copy",c=t):(i.dataTransfer.dropEffect="none",c=null)},!1),n.addEventListener("dragleave",i=>{var o,t,r;(o=i.dataTransfer)!=null&&o.types.includes("Files")&&(i.preventDefault(),((r=(t=window._wails)==null?void 0:t.flags)==null?void 0:r.enableFileDrop)!==!1&&i.relatedTarget!==null&&(e--,(e===0||c&&!c.contains(i.relatedTarget))&&(c&&(c.classList.remove(h),c=null),e=0)))},!1),n.addEventListener("drop",i=>{var o,t,r;if((o=i.dataTransfer)!=null&&o.types.includes("Files")&&(i.preventDefault(),((r=(t=window._wails)==null?void 0:t.flags)==null?void 0:r.enableFileDrop)!==!1&&(e=0,c&&(c.classList.remove(h),c=null),io()))){let a=[];if(i.dataTransfer.items){for(let l of i.dataTransfer.items)if(l.kind==="file"){let d=l.getAsFile();d&&a.push(d)}}else if(i.dataTransfer.files)for(let l of i.dataTransfer.files)a.push(l);a.length>0&&oo(i.clientX,i.clientY,a)}},!1)}typeof window<"u"&&typeof document<"u"&&ro();var E=to;function ao(n,e=null){se(n,e)}function so(n,e){let i=E.Get(n),o=i[e];if(typeof o=="function")try{o.call(i)}catch(t){}}function sn(n){let e=n.currentTarget;function i(t="Yes"){if(t!=="Yes")return;let r=e.getAttribute("wml-event")||e.getAttribute("data-wml-event"),a=e.getAttribute("wml-target-window")||e.getAttribute("data-wml-target-window")||"",l=e.getAttribute("wml-window")||e.getAttribute("data-wml-window"),d=e.getAttribute("wml-openurl")||e.getAttribute("data-wml-openurl");r!==null&&ao(r),l!==null&&so(a,l),d!==null&&ne(d)}let o=e.getAttribute("wml-confirm")||e.getAttribute("data-wml-confirm");o?oe({Title:"Confirm",Message:o,Detached:!1,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(i):i()}var U=Symbol("controller"),x=Symbol("triggerMap"),C=Symbol("elementCount"),me=class{constructor(){this[U]=new AbortController}set(e,i){return{signal:this[U].signal}}reset(){this[U].abort(),this[U]=new AbortController}},ue=class{constructor(){this[x]=new WeakMap,this[C]=0}set(e,i){return this[x].has(e)||this[C]++,this[x].set(e,i),{}}reset(){if(!(this[C]<=0)){for(let e of document.body.querySelectorAll("*")){if(this[C]<=0)break;let i=this[x].get(e);i!=null&&this[C]--;for(let o of i||[])e.removeEventListener(o,sn)}this[x]=new WeakMap,this[C]=0}}},ln=Je()?new me:new ue;function lo(n){let e=/\S+/g,i=n.getAttribute("wml-trigger")||n.getAttribute("data-wml-trigger")||"click",o=[],t;for(;(t=e.exec(i))!==null;)o.push(t[0]);let r=ln.set(n,o);for(let a of o)n.addEventListener(a,sn,r)}function we(){nn(dn)}function dn(){ln.reset(),document.body.querySelectorAll("[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]").forEach(lo)}window.wails=fe;we();var be={};p(be,{Capabilities:()=>po,Environment:()=>fo,IsAMD64:()=>Wo,IsARM:()=>bo,IsARM64:()=>vo,IsDarkMode:()=>wo,IsDebug:()=>We,IsLinux:()=>go,IsMac:()=>ho,IsWindows:()=>Q,invoke:()=>P});var he=u(m.System),co=0,mo=1,uo=2,ge=(function(){var n,e,i,o,t,r;try{if((e=(n=window.chrome)==null?void 0:n.webview)!=null&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if((t=(o=(i=window.webkit)==null?void 0:i.messageHandlers)==null?void 0:o.external)!=null&&t.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external);if((r=window.wails)!=null&&r.invoke)return a=>window.wails.invoke(typeof a=="string"?a:JSON.stringify(a))}catch(a){}return null})();function P(n){ge==null||ge(n)}function wo(){return he(co)}async function po(){return he(uo)}function fo(){return he(mo)}function Q(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="windows"}function go(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="linux"}function ho(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.OS)==="darwin"}function Wo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="amd64"}function bo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="arm"}function vo(){var n,e;return((e=(n=window._wails)==null?void 0:n.environment)==null?void 0:e.Arch)==="arm64"}function We(){var n,e;return!!((e=(n=window._wails)==null?void 0:n.environment)!=null&&e.Debug)}window.addEventListener("contextmenu",Po);var yo=u(m.ContextMenu),Do=0;function Co(n,e,i,o){yo(Do,{id:n,x:e,y:i,data:o})}function Po(n){let e=K(n),i=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(i){n.preventDefault();let o=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");Co(i,n.clientX,n.clientY,o)}else To(n,e)}function To(n,e){if(We())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":n.preventDefault();return}if(e.isContentEditable)return;let i=window.getSelection(),o=i&&i.toString().length>0;if(o)for(let t=0;tz});function z(n){try{return window._wails.flags[n]}catch(e){throw new Error("Unable to retrieve flag '"+n+"': "+e,{cause:e})}}var N=!1,j=!1,Te=!1,R=!1,F=!1,T="",cn="auto",A=0,ye=$e();window._wails=window._wails||{};window._wails.setResizable=n=>{Te=n,Te||(R=F=!1,f())};var Me=!1;function Mo(){var i,o;let n=(o=(i=window._wails)==null?void 0:i.environment)==null?void 0:o.OS;if(n==="ios"||n==="android")return!0;let e=navigator.userAgent||navigator.vendor||window.opera||"";return/android|iphone|ipad|ipod|iemobile|wpdesktop/i.test(e)}function Se(){if(!Me&&!Mo()){window.addEventListener("mousedown",Pe,{capture:!0}),window.addEventListener("mousemove",Pe,{capture:!0}),window.addEventListener("mouseup",Pe,{capture:!0});for(let n of["click","contextmenu","dblclick"])window.addEventListener(n,Eo,{capture:!0});Me=!0}}Se();document.addEventListener("DOMContentLoaded",Se,{once:!0});var So=0,mn=window.setInterval(()=>{if(Me){window.clearInterval(mn);return}Se(),++So>100&&window.clearInterval(mn)},50);function Eo(n){(j||F)&&(n.stopImmediatePropagation(),n.stopPropagation(),n.preventDefault())}var De=0,xo=1,Ce=2;function Pe(n){let e,i=n.buttons;switch(n.type){case"mousedown":e=De,ye||(i=A|1<Uo,Quit:()=>No,Show:()=>zo});var Ee=u(m.Application),ko=0,Lo=1,Io=2;function Uo(){return Ee(ko)}function zo(){return Ee(Lo)}function No(){return Ee(Io)}var Ue={};p(Ue,{ByID:()=>rt,ByName:()=>tt,Call:()=>Ie,RuntimeError:()=>Le});var wn=Function.prototype.toString,O=typeof Reflect=="object"&&Reflect!==null&&Reflect.apply,Ae,q;if(typeof O=="function"&&typeof Object.defineProperty=="function")try{Ae=Object.defineProperty({},"length",{get:function(){throw q}}),q={},O(function(){throw 42},null,Ae)}catch(n){n!==q&&(O=null)}else O=null;var jo=/^\s*class\b/,Fe=function(e){try{var i=wn.call(e);return jo.test(i)}catch(o){return!1}},Re=function(e){try{return Fe(e)?!1:(wn.call(e),!0)}catch(i){return!1}},$=Object.prototype.toString,Bo="[object Object]",Ho="[object Function]",Vo="[object GeneratorFunction]",Zo="[object HTMLAllCollection]",_o="[object HTML document.all class]",Go="[object HTMLCollection]",Ko=typeof Symbol=="function"&&!!Symbol.toStringTag,Yo=!(0 in[,]),Oe=function(){return!1};typeof document=="object"&&(un=document.all,$.call(un)===$.call(document.all)&&(Oe=function(e){if((Yo||!e)&&(typeof e>"u"||typeof e=="object"))try{var i=$.call(e);return(i===Zo||i===_o||i===Go||i===Bo)&&e("")==null}catch(o){}return!1}));var un;function Xo(n){if(Oe(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;try{O(n,null,Ae)}catch(e){if(e!==q)return!1}return!Fe(n)&&Re(n)}function Qo(n){if(Oe(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;if(Ko)return Re(n);if(Fe(n))return!1;var e=$.call(n);return e!==Ho&&e!==Vo&&!/^\[object HTML/.test(e)?!1:Re(n)}var b=O?Xo:Qo;var L=class extends Error{constructor(e,i){super(e,i),this.name="CancelError"}},v=class extends Error{constructor(e,i,o){super((o!=null?o:"Unhandled rejection in cancelled promise.")+" Reason: "+qo(i),{cause:i}),this.promise=e,this.name="CancelledRejectionError"}},W=Symbol("barrier"),ke=Symbol("cancelImpl"),hn,pn=(hn=Symbol.species)!=null?hn:Symbol("speciesPolyfill"),B=class n extends Promise{constructor(e,i){let o,t;if(super((d,w)=>{o=d,t=w}),this.constructor[pn]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let r={promise:this,resolve:o,reject:t,get oncancelled(){return i!=null?i:null},set oncancelled(d){i=d!=null?d:void 0}},a={get root(){return a},resolving:!1,settled:!1};Object.defineProperties(this,{[W]:{configurable:!1,enumerable:!1,writable:!0,value:null},[ke]:{configurable:!1,enumerable:!1,writable:!1,value:Wn(r,a)}});let l=vn(r,a);try{e(bn(r,a),l)}catch(d){a.resolving||l(d)}}cancel(e){return new n(i=>{Promise.all([this[ke](new L("Promise cancelled.",{cause:e})),$o(this)]).then(()=>i(),()=>i())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>{this.cancel(e.reason)},{capture:!0}),this}then(e,i,o){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(b(e)||(e=fn),b(i)||(i=gn),e===fn&&i==gn)return new n(r=>r(this));let t={};return this[W]=t,new n((r,a)=>{super.then(l=>{var d;this[W]===t&&(this[W]=null),(d=t.resolve)==null||d.call(t);try{r(e(l))}catch(w){a(w)}},l=>{var d;this[W]===t&&(this[W]=null),(d=t.resolve)==null||d.call(t);try{r(i(l))}catch(w){a(w)}})},async r=>{try{return o==null?void 0:o(r)}finally{await this.cancel(r)}})}catch(e,i){return this.then(void 0,e,i)}finally(e,i){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return b(e)?this.then(o=>n.resolve(e()).then(()=>o),o=>n.resolve(e()).then(()=>{throw o}),i):this.then(e,e,i)}static get[(W,ke,pn)](){return Promise}static all(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.all(i).then(t,r)},t=>J(o,i,t));return o}static allSettled(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.allSettled(i).then(t,r)},t=>J(o,i,t));return o}static any(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.any(i).then(t,r)},t=>J(o,i,t));return o}static race(e){let i=Array.from(e),o=new n((t,r)=>{Promise.race(i).then(t,r)},t=>J(o,i,t));return o}static cancel(e){let i=new n(()=>{});return i.cancel(e),i}static timeout(e,i){let o=new n(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>{o.cancel(i)}):setTimeout(()=>{o.cancel(i)},e),o}static sleep(e,i){return new n(o=>{setTimeout(()=>o(i),e)})}static reject(e){return new n((i,o)=>o(e))}static resolve(e){return e instanceof n?e:new n(i=>i(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new n((i,o)=>{e.resolve=i,e.reject=o},i=>{var o;(o=e.oncancelled)==null||o.call(e,i)}),e}};function Wn(n,e){let i;return o=>{if(e.settled||(e.settled=!0,e.reason=o,n.reject(o),Promise.prototype.then.call(n.promise,void 0,t=>{if(t!==o)throw t})),!(!e.reason||!n.oncancelled))return i=new Promise(t=>{try{t(n.oncancelled(e.reason.cause))}catch(r){Promise.reject(new v(n.promise,r,"Unhandled exception in oncancelled callback."))}}).catch(t=>{Promise.reject(new v(n.promise,t,"Unhandled rejection in oncancelled callback."))}),n.oncancelled=null,i}}function bn(n,e){return i=>{if(!e.resolving){if(e.resolving=!0,i===n.promise){if(e.settled)return;e.settled=!0,n.reject(new TypeError("A promise cannot be resolved with itself."));return}if(i!=null&&(typeof i=="object"||typeof i=="function")){let o;try{o=i.then}catch(t){e.settled=!0,n.reject(t);return}if(b(o)){try{let a=i.cancel;if(b(a)){let l=d=>{Reflect.apply(a,i,[d])};e.reason?Wn(Ye(Ke({},n),{oncancelled:l}),e)(e.reason):n.oncancelled=l}}catch(a){}let t={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(a){this.root.settled=a},get reason(){return this.root.reason}},r=vn(n,t);try{Reflect.apply(o,i,[bn(n,t),r])}catch(a){r(a)}return}}e.settled||(e.settled=!0,n.resolve(i))}}}function vn(n,e){return i=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(i instanceof L&&e.reason instanceof L&&Object.is(i.cause,e.reason.cause))return}catch(o){}Promise.reject(new v(n.promise,i))}else e.settled=!0,n.reject(i)}}function J(n,e,i){let o=[];for(let t of e){let r;try{if(!b(t.then)||(r=t.cancel,!b(r)))continue}catch(l){continue}let a;try{a=Reflect.apply(r,t,[i])}catch(l){Promise.reject(new v(n,l,"Unhandled exception in cancel method."));continue}a&&o.push((a instanceof Promise?a:Promise.resolve(a)).catch(l=>{Promise.reject(new v(n,l,"Unhandled rejection in cancel method."))}))}return Promise.all(o)}function fn(n){return n}function gn(n){throw n}function qo(n){try{if(n instanceof Error||typeof n!="object"||n.toString!==Object.prototype.toString)return""+n}catch(e){}try{return JSON.stringify(n)}catch(e){}try{return Object.prototype.toString.call(n)}catch(e){}return""}function $o(n){var i;let e=(i=n[W])!=null?i:{};return"promise"in e||Object.assign(e,k()),n[W]==null&&(e.resolve(),n[W]=e),e.promise}var k=Promise.withResolvers;k&&typeof k=="function"?k=k.bind(Promise):k=function(){let n,e;return{promise:new Promise((o,t)=>{n=o,e=t}),resolve:n,reject:e}};window._wails=window._wails||{};var Jo=u(m.Call),et=u(m.CancelCall),H=new Map,nt=0,it=0,Le=class extends Error{constructor(e,i){super(e,i),this.name="RuntimeError"}};function ot(){let n;do n=V();while(H.has(n));return n}function Ie(n){let e=ot(),i=B.withResolvers();H.set(e,{resolve:i.resolve,reject:i.reject});let o=Jo(nt,Object.assign({"call-id":e},n)),t=!0;o.then(a=>{t=!1,H.delete(e),i.resolve(a)},a=>{t=!1,H.delete(e),i.reject(a)});let r=()=>(H.delete(e),et(it,{"call-id":e}).catch(a=>{}));return i.oncancelled=()=>t?r():o.then(r),i.promise}function tt(n,...e){return Ie({methodName:n,args:e})}function rt(n,...e){return Ie({methodID:n,args:e})}var ze={};p(ze,{SetText:()=>lt,Text:()=>dt});var yn=u(m.Clipboard),at=0,st=1;function lt(n){return yn(at,{text:n})}function dt(){return yn(st)}var je={};p(je,{GetAll:()=>wt,GetCurrent:()=>ft,GetPrimary:()=>pt});var Ne=u(m.Screens),ct=0,mt=1,ut=2;function wt(){return Ne(ct)}function pt(){return Ne(mt)}function ft(){return Ne(ut)}var Ve={};p(Ve,{Device:()=>He,Haptics:()=>Be});var Dn=u(m.IOS),gt=0,ht=1,Be;(e=>{function n(i="medium"){return Dn(gt,{style:i})}e.Impact=n})(Be||(Be={}));var He;(e=>{function n(){return Dn(ht)}e.Info=n})(He||(He={}));window._wails=window._wails||{};window._wails.invoke=P;window._wails.handlePlatformFileDrop=E.HandlePlatformFileDrop.bind(E);window._wails.handleDragEnter=tn;window._wails.handleDragLeave=rn;window._wails.handleDragOver=an;P("wails:runtime:ready");export{xe as Application,ie as Browser,Ue as Call,L as CancelError,B as CancellablePromise,v as CancelledRejectionError,ze as Clipboard,re as Create,te as Dialogs,le as Events,ve as Flags,Ve as IOS,je as Screens,be as System,pe as WML,E as Window,ee as clientId,Qe as getTransport,m as objectNames,Xe as setTransport}; diff --git a/v3/internal/assetserver/common.go b/v3/internal/assetserver/common.go index 6e1dfca17..a41139dff 100644 --- a/v3/internal/assetserver/common.go +++ b/v3/internal/assetserver/common.go @@ -1,10 +1,8 @@ package assetserver import ( - "bytes" "context" "fmt" - "io" "log/slog" "net/http" "strings" @@ -26,32 +24,23 @@ type assetServerLogger struct{} var assetServerLoggerKey assetServerLogger +// ServeFile writes the provided blob to rw as an HTTP 200 response, ensuring appropriate +// Content-Length and Content-Type headers are set. +// +// If the Content-Type header is not already present, ServeFile determines an appropriate +// MIME type from the filename and blob and sets the Content-Type header. It then writes +// the 200 status and the blob body to the response, returning any error encountered while +// writing the body. func ServeFile(rw http.ResponseWriter, filename string, blob []byte) error { header := rw.Header() header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) if mimeType := header.Get(HeaderContentType); mimeType == "" { mimeType = GetMimetype(filename, blob) header.Set(HeaderContentType, mimeType) - // Debug CSS serving with clear markers - if strings.HasSuffix(filename, ".css") { - fmt.Printf("\n🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨\n") - fmt.Printf("CSS FILE BEING SERVED:\n") - fmt.Printf(" Filename: %s\n", filename) - fmt.Printf(" MimeType: %s\n", mimeType) - fmt.Printf(" Size: %d bytes\n", len(blob)) - if len(blob) > 0 { - preview := string(blob) - if len(preview) > 100 { - preview = preview[:100] + "..." - } - fmt.Printf(" Preview: %s\n", preview) - } - fmt.Printf("🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨\n\n") - } } rw.WriteHeader(http.StatusOK) - _, err := io.Copy(rw, bytes.NewReader(blob)) + _, err := rw.Write(blob) return err } @@ -74,4 +63,4 @@ func logError(ctx context.Context, message string, args ...interface{}) { if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil { logger.Error(message, args...) } -} +} \ No newline at end of file diff --git a/v3/internal/assetserver/content_type_sniffer.go b/v3/internal/assetserver/content_type_sniffer.go index e36db90e8..fd51a6101 100644 --- a/v3/internal/assetserver/content_type_sniffer.go +++ b/v3/internal/assetserver/content_type_sniffer.go @@ -4,20 +4,21 @@ import ( "net/http" ) +// newContentTypeSniffer creates a contentTypeSniffer that wraps the provided http.ResponseWriter. +// The returned sniffer does not allocate a close notification channel; it will be initialized lazily by CloseNotify. func newContentTypeSniffer(rw http.ResponseWriter) *contentTypeSniffer { return &contentTypeSniffer{ - rw: rw, - closeChannel: make(chan bool, 1), + rw: rw, } } type contentTypeSniffer struct { rw http.ResponseWriter prefix []byte + closeChannel chan bool // lazily allocated only if CloseNotify is called status int headerCommitted bool headerWritten bool - closeChannel chan bool } // Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController]. @@ -118,12 +119,19 @@ func (rw *contentTypeSniffer) complete() (n int, err error) { } // CloseNotify implements the http.CloseNotifier interface. +// The channel is lazily allocated to avoid allocation overhead for requests +// that don't use this deprecated interface. func (rw *contentTypeSniffer) CloseNotify() <-chan bool { + if rw.closeChannel == nil { + rw.closeChannel = make(chan bool, 1) + } return rw.closeChannel } func (rw *contentTypeSniffer) closeClient() { - rw.closeChannel <- true + if rw.closeChannel != nil { + rw.closeChannel <- true + } } // Flush implements the http.Flusher interface. @@ -131,4 +139,4 @@ func (rw *contentTypeSniffer) Flush() { if f, ok := rw.rw.(http.Flusher); ok { f.Flush() } -} +} \ No newline at end of file diff --git a/v3/internal/assetserver/mimecache.go b/v3/internal/assetserver/mimecache.go index c43de519d..9034a4a0f 100644 --- a/v3/internal/assetserver/mimecache.go +++ b/v3/internal/assetserver/mimecache.go @@ -4,64 +4,113 @@ import ( "net/http" "path/filepath" "sync" - - "github.com/wailsapp/mimetype" ) var ( - mimeCache = map[string]string{} - mimeMutex sync.Mutex + // mimeCache uses sync.Map for better concurrent read performance + // since reads are far more common than writes + mimeCache sync.Map - // The list of builtin mime-types by extension as defined by - // the golang standard lib package "mime" - // The standard lib also takes into account mime type definitions from - // /etc files like '/etc/apache2/mime.types' but we want to have the - // same behaviour on all platforms and not depend on some external file. + // mimeTypesByExt maps file extensions to MIME types for common web formats. + // This approach is preferred over content-based detection because: + // 1. Extension-based lookup is O(1) vs O(n) content scanning + // 2. Web assets typically have correct extensions + // 3. stdlib's http.DetectContentType handles remaining cases adequately + // 4. Saves ~208KB binary size by not using github.com/wailsapp/mimetype mimeTypesByExt = map[string]string{ - ".avif": "image/avif", - ".css": "text/css; charset=utf-8", - ".gif": "image/gif", + // HTML ".htm": "text/html; charset=utf-8", ".html": "text/html; charset=utf-8", - ".jpeg": "image/jpeg", - ".jpg": "image/jpeg", - ".js": "text/javascript; charset=utf-8", + + // CSS/JS + ".css": "text/css; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".ts": "application/x-typescript; charset=utf-8", + ".tsx": "application/x-typescript; charset=utf-8", + ".jsx": "text/javascript; charset=utf-8", + + // Data formats ".json": "application/json", - ".mjs": "text/javascript; charset=utf-8", - ".pdf": "application/pdf", - ".png": "image/png", - ".svg": "image/svg+xml", - ".wasm": "application/wasm", - ".webp": "image/webp", ".xml": "text/xml; charset=utf-8", + ".yaml": "text/yaml; charset=utf-8", + ".yml": "text/yaml; charset=utf-8", + ".toml": "text/toml; charset=utf-8", + + // Images + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + ".avif": "image/avif", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".bmp": "image/bmp", + ".tiff": "image/tiff", + ".tif": "image/tiff", + + // Fonts + ".woff": "font/woff", + ".woff2": "font/woff2", + ".ttf": "font/ttf", + ".otf": "font/otf", + ".eot": "application/vnd.ms-fontobject", + + // Audio + ".mp3": "audio/mpeg", + ".wav": "audio/wav", + ".ogg": "audio/ogg", + ".m4a": "audio/mp4", + ".aac": "audio/aac", + ".flac": "audio/flac", + ".opus": "audio/opus", + + // Video + ".mp4": "video/mp4", + ".webm": "video/webm", + ".ogv": "video/ogg", + ".mov": "video/quicktime", + ".avi": "video/x-msvideo", + ".mkv": "video/x-matroska", + ".m4v": "video/mp4", + + // Documents + ".pdf": "application/pdf", + ".txt": "text/plain; charset=utf-8", + ".md": "text/markdown; charset=utf-8", + + // Archives + ".zip": "application/zip", + ".gz": "application/gzip", + ".tar": "application/x-tar", + + // WebAssembly + ".wasm": "application/wasm", + + // Source maps + ".map": "application/json", } ) +// "application/octet-stream". func GetMimetype(filename string, data []byte) string { - mimeMutex.Lock() - defer mimeMutex.Unlock() - - result := mimeTypesByExt[filepath.Ext(filename)] - if result != "" { + // Fast path: check extension map first (no lock needed) + if result := mimeTypesByExt[filepath.Ext(filename)]; result != "" { return result } - result = mimeCache[filename] - if result != "" { - return result - } - - detect := mimetype.Detect(data) - if detect == nil { - result = http.DetectContentType(data) - } else { - result = detect.String() + // Check cache (lock-free read) + if cached, ok := mimeCache.Load(filename); ok { + return cached.(string) } + // Slow path: use stdlib content-based detection and cache + result := http.DetectContentType(data) if result == "" { result = "application/octet-stream" } - mimeCache[filename] = result + mimeCache.Store(filename, result) return result -} +} \ No newline at end of file diff --git a/v3/internal/assetserver/mimetype_stdlib_test.go b/v3/internal/assetserver/mimetype_stdlib_test.go new file mode 100644 index 000000000..18a20131d --- /dev/null +++ b/v3/internal/assetserver/mimetype_stdlib_test.go @@ -0,0 +1,277 @@ +package assetserver + +import ( + "net/http" + "path/filepath" + "strings" + "testing" +) + +// TestMimeTypeDetection_WebFormats validates that extension-based detection +// plus stdlib fallback correctly handles all common web asset formats. +// This test ensures we can safely remove the github.com/wailsapp/mimetype dependency. +func TestMimeTypeDetection_WebFormats(t *testing.T) { + // webMimeTests covers all common web formats that Wails applications typically serve + webMimeTests := []struct { + name string + filename string + data []byte + wantPrefix string // Use prefix matching since charset may vary + }{ + // === TEXT FORMATS (extension-based) === + {"HTML file", "index.html", []byte(""), "text/html"}, + {"HTM file", "page.htm", []byte(""), "text/html"}, + {"CSS file", "styles.css", []byte(".class { color: red; }"), "text/css"}, + {"JavaScript file", "app.js", []byte("function test() {}"), "text/javascript"}, + {"ES Module file", "module.mjs", []byte("export default {}"), "text/javascript"}, + {"JSON file", "data.json", []byte(`{"key": "value"}`), "application/json"}, + {"XML file", "data.xml", []byte(""), "text/xml"}, + + // === IMAGE FORMATS (extension-based) === + {"PNG file", "image.png", pngData, "image/png"}, + {"JPEG file", "photo.jpg", jpegData, "image/jpeg"}, + {"JPEG alt ext", "photo.jpeg", jpegData, "image/jpeg"}, + {"GIF file", "anim.gif", gifData, "image/gif"}, + {"WebP file", "image.webp", webpData, "image/webp"}, + {"AVIF file", "image.avif", avifData, "image/avif"}, + {"SVG file", "icon.svg", []byte(""), "image/svg+xml"}, + {"PDF file", "doc.pdf", pdfData, "application/pdf"}, + + // === WASM (extension-based) === + {"WASM file", "app.wasm", wasmData, "application/wasm"}, + + // === FONT FORMATS (need detection or extension map) === + {"WOFF file", "font.woff", woffData, "font/woff"}, + {"WOFF2 file", "font.woff2", woff2Data, "font/woff2"}, + {"TTF file", "font.ttf", ttfData, "font/ttf"}, + {"OTF file", "font.otf", otfData, "font/otf"}, + {"EOT file", "font.eot", eotData, "application/vnd.ms-fontobject"}, + + // === AUDIO/VIDEO (common web formats) === + {"MP3 file", "audio.mp3", mp3Data, "audio/mpeg"}, + {"MP4 file", "video.mp4", mp4Data, "video/mp4"}, + {"WebM file", "video.webm", webmData, "video/webm"}, + {"OGG file", "audio.ogg", oggData, "audio/ogg"}, + + // === ARCHIVES (sometimes served by web apps) === + {"ZIP file", "archive.zip", zipData, "application/zip"}, + {"GZIP file", "data.gz", gzipData, "application/"}, + + // === SOURCE MAPS (common in dev mode) === + {"Source map", "app.js.map", []byte(`{"version":3}`), "application/json"}, + + // === ICO (favicon) === + {"ICO file", "favicon.ico", icoData, "image/"}, + + // === FALLBACK TESTS === + {"Unknown binary", "data.bin", []byte{0x00, 0x01, 0x02, 0x03}, "application/octet-stream"}, + {"Plain text (no ext)", "readme", []byte("Hello World"), "text/plain"}, + } + + for _, tt := range webMimeTests { + t.Run(tt.name, func(t *testing.T) { + got := getMimeTypeStdlib(tt.filename, tt.data) + if !hasPrefix(got, tt.wantPrefix) { + t.Errorf("getMimeTypeStdlib(%q) = %q, want prefix %q", tt.filename, got, tt.wantPrefix) + } + }) + } +} + +// getMimeTypeStdlib is the proposed replacement that uses only stdlib +func getMimeTypeStdlib(filename string, data []byte) string { + // Fast path: check extension map first + if result := extMimeTypes[filepath.Ext(filename)]; result != "" { + return result + } + + // Fallback to stdlib content-based detection + result := http.DetectContentType(data) + if result == "" { + result = "application/octet-stream" + } + return result +} + +// extMimeTypes is an expanded map covering all common web formats +// This replaces the need for the mimetype library for web assets +var extMimeTypes = map[string]string{ + // HTML + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + + // CSS/JS + ".css": "text/css; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".ts": "application/x-typescript; charset=utf-8", + ".tsx": "application/x-typescript; charset=utf-8", + ".jsx": "text/javascript; charset=utf-8", + + // Data formats + ".json": "application/json", + ".xml": "text/xml; charset=utf-8", + ".yaml": "text/yaml; charset=utf-8", + ".yml": "text/yaml; charset=utf-8", + ".toml": "text/toml; charset=utf-8", + + // Images + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + ".avif": "image/avif", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".bmp": "image/bmp", + ".tiff": "image/tiff", + ".tif": "image/tiff", + + // Fonts + ".woff": "font/woff", + ".woff2": "font/woff2", + ".ttf": "font/ttf", + ".otf": "font/otf", + ".eot": "application/vnd.ms-fontobject", + + // Audio + ".mp3": "audio/mpeg", + ".wav": "audio/wav", + ".ogg": "audio/ogg", + ".m4a": "audio/mp4", + ".aac": "audio/aac", + ".flac": "audio/flac", + ".opus": "audio/opus", + + // Video + ".mp4": "video/mp4", + ".webm": "video/webm", + ".ogv": "video/ogg", + ".mov": "video/quicktime", + ".avi": "video/x-msvideo", + ".mkv": "video/x-matroska", + ".m4v": "video/mp4", + + // Documents + ".pdf": "application/pdf", + ".txt": "text/plain; charset=utf-8", + ".md": "text/markdown; charset=utf-8", + + // Archives + ".zip": "application/zip", + ".gz": "application/gzip", + ".tar": "application/x-tar", + + // WebAssembly + ".wasm": "application/wasm", + + // Source maps + ".map": "application/json", +} + +func hasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +// Magic bytes for various formats +var ( + // PNG: 89 50 4E 47 0D 0A 1A 0A + pngData = []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D} + + // JPEG: FF D8 FF + jpegData = []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46} + + // GIF: 47 49 46 38 + gifData = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61} + + // WebP: 52 49 46 46 ... 57 45 42 50 + webpData = []byte{0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50} + + // AVIF: ... ftypavif or ftypavis + avifData = []byte{0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66} + + // PDF: 25 50 44 46 + pdfData = []byte{0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E} + + // WASM: 00 61 73 6D + wasmData = []byte{0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00} + + // WOFF: 77 4F 46 46 + woffData = []byte{0x77, 0x4F, 0x46, 0x46, 0x00, 0x01, 0x00, 0x00} + + // WOFF2: 77 4F 46 32 + woff2Data = []byte{0x77, 0x4F, 0x46, 0x32, 0x00, 0x01, 0x00, 0x00} + + // TTF: 00 01 00 00 + ttfData = []byte{0x00, 0x01, 0x00, 0x00, 0x00} + + // OTF: 4F 54 54 4F (OTTO) + otfData = []byte{0x4F, 0x54, 0x54, 0x4F, 0x00} + + // EOT: varies, but starts with size bytes then magic + eotData = []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00} + + // MP3: FF FB or FF FA or ID3 + mp3Data = []byte{0xFF, 0xFB, 0x90, 0x00} + + // MP4: ... ftyp + mp4Data = []byte{0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D} + + // WebM: 1A 45 DF A3 (EBML header) + webmData = []byte{0x1A, 0x45, 0xDF, 0xA3} + + // OGG: 4F 67 67 53 + oggData = []byte{0x4F, 0x67, 0x67, 0x53, 0x00, 0x02} + + // ZIP: 50 4B 03 04 + zipData = []byte{0x50, 0x4B, 0x03, 0x04} + + // GZIP: 1F 8B + gzipData = []byte{0x1F, 0x8B, 0x08} + + // ICO: 00 00 01 00 + icoData = []byte{0x00, 0x00, 0x01, 0x00, 0x01, 0x00} +) + +// TestMimeTypeExtensionMapCompleteness checks that all extensions in the +// original mimeTypesByExt are covered by the expanded extMimeTypes +func TestMimeTypeExtensionMapCompleteness(t *testing.T) { + for ext, mime := range mimeTypesByExt { + if newMime, ok := extMimeTypes[ext]; !ok { + t.Errorf("extension %q missing from extMimeTypes (was: %q)", ext, mime) + } else if newMime != mime { + // Allow differences as long as they're equivalent (compare base MIME type) + mimeBase := mime + if idx := strings.Index(mime, ";"); idx > 0 { + mimeBase = mime[:idx] + } + if !hasPrefix(newMime, mimeBase) { + t.Logf("extension %q changed: %q -> %q (verify this is correct)", ext, mime, newMime) + } + } + } +} + +// BenchmarkMimeType_StdlibOnly benchmarks the stdlib-only implementation +func BenchmarkMimeType_StdlibOnly(b *testing.B) { + testCases := []struct { + name string + filename string + data []byte + }{ + {"ExtHit_JS", "app.js", []byte("function() {}")}, + {"ExtHit_CSS", "styles.css", []byte(".class { }")}, + {"ExtHit_PNG", "image.png", pngData}, + {"ExtMiss_Binary", "data.bin", []byte{0x00, 0x01, 0x02}}, + {"ContentDetect_PNG", "unknown", pngData}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + for b.Loop() { + _ = getMimeTypeStdlib(tc.filename, tc.data) + } + }) + } +} diff --git a/v3/internal/assetserver/webview/request_darwin.go b/v3/internal/assetserver/webview/request_darwin.go index 275e0d38d..dd2d0232a 100644 --- a/v3/internal/assetserver/webview/request_darwin.go +++ b/v3/internal/assetserver/webview/request_darwin.go @@ -109,12 +109,13 @@ import "C" import ( "bytes" - "encoding/json" "errors" "fmt" "io" "net/http" "unsafe" + + "encoding/json" ) // NewRequest creates as new WebViewRequest based on a pointer to an `id` diff --git a/v3/internal/assetserver/webview/request_ios.go b/v3/internal/assetserver/webview/request_ios.go index 28e1c31ac..81d23b0bc 100644 --- a/v3/internal/assetserver/webview/request_ios.go +++ b/v3/internal/assetserver/webview/request_ios.go @@ -107,12 +107,13 @@ import "C" import ( "bytes" - "encoding/json" "errors" "fmt" "io" "net/http" "unsafe" + + "encoding/json" ) // NewRequest creates as new WebViewRequest based on a pointer to an `id` diff --git a/v3/internal/assetserver/webview/responsewriter_darwin.go b/v3/internal/assetserver/webview/responsewriter_darwin.go index b6e2f5ef4..ff29a08bb 100644 --- a/v3/internal/assetserver/webview/responsewriter_darwin.go +++ b/v3/internal/assetserver/webview/responsewriter_darwin.go @@ -68,9 +68,10 @@ static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCod import "C" import ( - "encoding/json" "net/http" "unsafe" + + "encoding/json" ) var _ ResponseWriter = &responseWriter{} diff --git a/v3/internal/assetserver/webview/responsewriter_ios.go b/v3/internal/assetserver/webview/responsewriter_ios.go index 6dc56bb81..119ef1628 100644 --- a/v3/internal/assetserver/webview/responsewriter_ios.go +++ b/v3/internal/assetserver/webview/responsewriter_ios.go @@ -68,11 +68,12 @@ static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCod import "C" import ( - "encoding/json" "fmt" "net/http" "strings" "unsafe" + + "encoding/json" ) var _ ResponseWriter = &responseWriter{} diff --git a/v3/internal/commands/build_assets/android/Taskfile.yml b/v3/internal/commands/build_assets/android/Taskfile.yml index 5005f9f4e..aca62e4f9 100644 --- a/v3/internal/commands/build_assets/android/Taskfile.yml +++ b/v3/internal/commands/build_assets/android/Taskfile.yml @@ -125,7 +125,7 @@ tasks: - | cd build/android ./gradlew assembleDebug - cp app/build/outputs/apk/debug/app-debug.apk ../../{{.BIN_DIR}}/{{.APP_NAME}}.apk + cp app/build/outputs/apk/debug/app-debug.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}.apk" echo "APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk" assemble:apk:release: @@ -134,7 +134,7 @@ tasks: - | cd build/android ./gradlew assembleRelease - cp app/build/outputs/apk/release/app-release-unsigned.apk ../../{{.BIN_DIR}}/{{.APP_NAME}}-release.apk + cp app/build/outputs/apk/release/app-release-unsigned.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}-release.apk" echo "Release APK created: {{.BIN_DIR}}/{{.APP_NAME}}-release.apk" generate:android:bindings: @@ -202,7 +202,7 @@ tasks: deps: [package] cmds: - adb uninstall {{.APP_ID}} 2>/dev/null || true - - adb install {{.BIN_DIR}}/{{.APP_NAME}}.apk + - adb install "{{.BIN_DIR}}/{{.APP_NAME}}.apk" - adb shell am start -n {{.APP_ID}}/.MainActivity run: @@ -215,7 +215,7 @@ tasks: cmds: - task: assemble:apk - adb uninstall {{.APP_ID}} 2>/dev/null || true - - adb install {{.BIN_DIR}}/{{.APP_NAME}}.apk + - adb install "{{.BIN_DIR}}/{{.APP_NAME}}.apk" - adb shell am start -n {{.APP_ID}}/.MainActivity logs: diff --git a/v3/internal/commands/build_assets/darwin/Taskfile.yml b/v3/internal/commands/build_assets/darwin/Taskfile.yml index 3458c96ef..50600ced9 100644 --- a/v3/internal/commands/build_assets/darwin/Taskfile.yml +++ b/v3/internal/commands/build_assets/darwin/Taskfile.yml @@ -65,10 +65,10 @@ tasks: Docker image '{{.CROSS_IMAGE}}' not found. Build it first: wails3 task setup:docker cmds: - - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME={{.APP_NAME}} {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}} - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin - mkdir -p {{.BIN_DIR}} - - mv bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}} {{.OUTPUT}} + - mv "bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}}" "{{.OUTPUT}}" vars: DOCKER_ARCH: '{{if eq .ARCH "arm64"}}arm64{{else if eq .ARCH "amd64"}}amd64{{else}}arm64{{end}}' DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' @@ -117,7 +117,7 @@ tasks: summary: Creates universal binary using wails3 tool lipo (Linux/Windows) internal: true cmds: - - wails3 tool lipo -o "{{.BIN_DIR}}/{{.APP_NAME}}" -i "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" -i "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - wails3 tool lipo -output "{{.BIN_DIR}}/{{.APP_NAME}}" -input "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" -input "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" - rm -f "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" package: @@ -138,17 +138,18 @@ tasks: create:app:bundle: summary: Creates an `.app` bundle cmds: - - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} - - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources - - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS - - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + - cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - cp build/darwin/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents" - task: '{{if eq OS "darwin"}}codesign:adhoc{{else}}codesign:skip{{end}}' codesign:adhoc: summary: Ad-hoc signs the app bundle (macOS only) internal: true cmds: - - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + - codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app" codesign:skip: summary: Skips codesigning when cross-compiling @@ -158,11 +159,12 @@ tasks: run: cmds: - - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} - - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources - - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS - - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist - - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + - cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS" + - cp "build/darwin/Info.dev.plist" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist" + - codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' sign: @@ -173,7 +175,7 @@ tasks: deps: - task: package cmds: - - wails3 tool sign --input {{.BIN_DIR}}/{{.APP_NAME}}.app --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} preconditions: - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" @@ -189,7 +191,7 @@ tasks: deps: - task: package cmds: - - wails3 tool sign --input {{.BIN_DIR}}/{{.APP_NAME}}.app --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} --notarize --keychain-profile {{.KEYCHAIN_PROFILE}} + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} --notarize --keychain-profile {{.KEYCHAIN_PROFILE}} preconditions: - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" diff --git a/v3/internal/commands/build_assets/docker/Dockerfile.cross b/v3/internal/commands/build_assets/docker/Dockerfile.cross index e1c05a05d..474c05525 100644 --- a/v3/internal/commands/build_assets/docker/Dockerfile.cross +++ b/v3/internal/commands/build_assets/docker/Dockerfile.cross @@ -46,7 +46,7 @@ for arg in "$@"; do *) ARGS="$ARGS $arg" ;; esac done -exec zig cc -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS +exec zig cc -fno-sanitize=all -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS ZIGWRAP RUN chmod +x /usr/local/bin/zcc-darwin-arm64 @@ -66,7 +66,7 @@ for arg in "$@"; do *) ARGS="$ARGS $arg" ;; esac done -exec zig cc -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS +exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS ZIGWRAP RUN chmod +x /usr/local/bin/zcc-darwin-amd64 diff --git a/v3/internal/commands/build_assets/ios/Taskfile.yml b/v3/internal/commands/build_assets/ios/Taskfile.yml index 19f57f04f..8c27f0894 100644 --- a/v3/internal/commands/build_assets/ios/Taskfile.yml +++ b/v3/internal/commands/build_assets/ios/Taskfile.yml @@ -61,7 +61,7 @@ tasks: sh: xcrun --sdk iphonesimulator --show-sdk-path cmds: - xcrun -sdk iphonesimulator clang -target arm64-apple-ios15.0-simulator -isysroot {{.SDK_PATH}} -framework Foundation -framework UIKit -framework WebKit -o {{.BIN_DIR}}/{{.APP_NAME}} build/ios/main.m - - codesign --force --sign - {{.BIN_DIR}}/{{.APP_NAME}} + - codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}" package: summary: Packages a production build of the application into a `.app` bundle @@ -75,10 +75,10 @@ tasks: create:app:bundle: summary: Creates an iOS `.app` bundle cmds: - - rm -rf {{.BIN_DIR}}/{{.APP_NAME}}.app - - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app - - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/ - - cp build/ios/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/ + - rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.app" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app" + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/" + - cp build/ios/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/" - | # Compile asset catalog and embed icons in the app bundle APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.app" @@ -103,7 +103,7 @@ tasks: /usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true fi fi - - codesign --force --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + - codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app" deploy-simulator: summary: Deploy to iOS Simulator @@ -111,7 +111,7 @@ tasks: cmds: - xcrun simctl terminate booted {{.BUNDLE_ID}} 2>/dev/null || true - xcrun simctl uninstall booted {{.BUNDLE_ID}} 2>/dev/null || true - - xcrun simctl install booted {{.BIN_DIR}}/{{.APP_NAME}}.app + - xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.app" - xcrun simctl launch booted {{.BUNDLE_ID}} compile:ios: @@ -133,8 +133,8 @@ tasks: -framework Foundation -framework UIKit -framework WebKit \ -framework Security -framework CoreFoundation \ -lresolv \ - -o {{.BIN_DIR}}/{{.APP_NAME | lower}} \ - "$MAIN_M" {{.BIN_DIR}}/{{.APP_NAME}}.a + -o "{{.BIN_DIR}}/{{.APP_NAME | lower}}" \ + "$MAIN_M" "{{.BIN_DIR}}/{{.APP_NAME}}.a" generate:ios:bindings: internal: true @@ -225,10 +225,10 @@ tasks: - task: ensure-simulator - task: compile:ios cmds: - - rm -rf {{.BIN_DIR}}/{{.APP_NAME}}.dev.app - - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app - - cp {{.BIN_DIR}}/{{.APP_NAME | lower}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/{{.APP_NAME | lower}} - - cp build/ios/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Info.plist + - rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - cp "{{.BIN_DIR}}/{{.APP_NAME | lower}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/{{.APP_NAME | lower}}" + - cp build/ios/Info.dev.plist "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Info.plist" - | # Compile asset catalog and embed icons for dev bundle APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" @@ -253,11 +253,11 @@ tasks: /usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true fi fi - - codesign --force --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app - - xcrun simctl terminate booted com.wails.{{.APP_NAME | lower}}.dev 2>/dev/null || true - - xcrun simctl uninstall booted com.wails.{{.APP_NAME | lower}}.dev 2>/dev/null || true - - xcrun simctl install booted {{.BIN_DIR}}/{{.APP_NAME}}.dev.app - - xcrun simctl launch booted com.wails.{{.APP_NAME | lower}}.dev + - codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - xcrun simctl terminate booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true + - xcrun simctl uninstall booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true + - xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - xcrun simctl launch booted "com.wails.{{.APP_NAME | lower}}.dev" xcode: summary: Open the generated Xcode project for this app diff --git a/v3/internal/commands/build_assets/linux/Taskfile.yml b/v3/internal/commands/build_assets/linux/Taskfile.yml index 69e3e727a..c29558671 100644 --- a/v3/internal/commands/build_assets/linux/Taskfile.yml +++ b/v3/internal/commands/build_assets/linux/Taskfile.yml @@ -69,10 +69,10 @@ tasks: Docker image '{{.CROSS_IMAGE}}' not found. Build it first: wails3 task setup:docker cmds: - - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME={{.APP_NAME}} {{.CROSS_IMAGE}} linux {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}} - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin - mkdir -p {{.BIN_DIR}} - - mv bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}} {{.OUTPUT}} + - mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}" vars: DOCKER_ARCH: '{{.ARCH | default "amd64"}}' DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' @@ -112,9 +112,9 @@ tasks: - task: build - task: generate:dotdesktop cmds: - - cp {{.APP_BINARY}} {{.APP_NAME}} - - cp ../../appicon.png {{.APP_NAME}}.png - - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + - cp "{{.APP_BINARY}}" "{{.APP_NAME}}" + - cp ../../appicon.png "{{.APP_NAME}}.png" + - wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build vars: APP_NAME: '{{.APP_NAME}}' APP_BINARY: '../../../bin/{{.APP_NAME}}' @@ -149,24 +149,24 @@ tasks: generate:deb: summary: Creates a deb package cmds: - - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + - wails3 tool package -name "{{.APP_NAME}}" -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin generate:rpm: summary: Creates a rpm package cmds: - - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + - wails3 tool package -name "{{.APP_NAME}}" -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin generate:aur: summary: Creates a arch linux packager package cmds: - - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + - wails3 tool package -name "{{.APP_NAME}}" -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin generate:dotdesktop: summary: Generates a `.desktop` file dir: build cmds: - mkdir -p {{.ROOT_DIR}}/build/linux/appimage - - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}" vars: APP_NAME: '{{.APP_NAME}}' EXEC: '{{.APP_NAME}}' @@ -187,7 +187,7 @@ tasks: deps: - task: create:deb cmds: - - wails3 tool sign --input {{.BIN_DIR}}/{{.APP_NAME}}*.deb --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}} + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.deb" --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}} preconditions: - sh: '[ -n "{{.PGP_KEY}}" ]' msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" @@ -201,7 +201,7 @@ tasks: deps: - task: create:rpm cmds: - - wails3 tool sign --input {{.BIN_DIR}}/{{.APP_NAME}}*.rpm --pgp-key {{.PGP_KEY}} + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.rpm" --pgp-key {{.PGP_KEY}} preconditions: - sh: '[ -n "{{.PGP_KEY}}" ]' msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" diff --git a/v3/internal/commands/build_assets/windows/Taskfile.yml b/v3/internal/commands/build_assets/windows/Taskfile.yml index 1ca955f67..77b620b7d 100644 --- a/v3/internal/commands/build_assets/windows/Taskfile.yml +++ b/v3/internal/commands/build_assets/windows/Taskfile.yml @@ -41,7 +41,7 @@ tasks: - task: common:generate:icons cmds: - task: generate:syso - - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - go build {{.BUILD_FLAGS}} -o "{{.BIN_DIR}}/{{.APP_NAME}}.exe" - cmd: powershell Remove-item *.syso platforms: [windows] - cmd: rm -f *.syso @@ -68,7 +68,7 @@ tasks: Build it first: wails3 task setup:docker cmds: - task: generate:syso - - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME={{.APP_NAME}} {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}} - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin - rm -f *.syso vars: @@ -163,7 +163,7 @@ tasks: deps: - task: build cmds: - - wails3 tool sign --input {{.BIN_DIR}}/{{.APP_NAME}}.exe {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} preconditions: - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" @@ -177,7 +177,7 @@ tasks: deps: - task: create:nsis:installer cmds: - - wails3 tool sign --input build/windows/nsis/{{.APP_NAME}}-installer.exe {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} + - wails3 tool sign --input "build/windows/nsis/{{.APP_NAME}}-installer.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} preconditions: - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" diff --git a/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl b/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl index d68664afb..430f8c3a9 100644 --- a/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl +++ b/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl @@ -2,8 +2,10 @@ + xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" + IgnorableNamespaces="uap3"> {{end}} + {{range .Protocols}} + + + {{.Description}} + + + {{end}} diff --git a/v3/internal/commands/task_integration_test.go b/v3/internal/commands/task_integration_test.go index fc7ff6607..c13d73162 100644 --- a/v3/internal/commands/task_integration_test.go +++ b/v3/internal/commands/task_integration_test.go @@ -4,6 +4,7 @@ import ( "bytes" "os" "path/filepath" + "runtime" "strings" "testing" @@ -156,8 +157,8 @@ func TestCLIParameterFormats(t *testing.T) { }, }, { - name: "Empty value", - otherArgs: []string{"build", "EMPTY=", "KEY=value"}, + name: "Empty value", + otherArgs: []string{"build", "EMPTY=", "KEY=value"}, expectedVars: map[string]string{ "EMPTY": "", "KEY": "value", @@ -224,7 +225,7 @@ func captureTaskOutput(t *testing.T, options *RunTaskOptions, otherArgs []string // Wait for task to complete <-done - + // Check for errors (might be expected in some tests) if taskErr != nil && !strings.Contains(taskErr.Error(), "expected") { t.Logf("Task error (might be expected): %v", taskErr) @@ -286,4 +287,56 @@ func TestBackwardCompatibility(t *testing.T) { } }) } -} \ No newline at end of file +} + +func TestMkdirWithSpacesInPath(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip("Skipping: macOS app bundle test only applies to darwin") + } + if os.Getenv("CI") == "true" && os.Getenv("SKIP_INTEGRATION_TESTS") == "true" { + t.Skip("Skipping integration test in CI") + } + + tmpDir, err := os.MkdirTemp("", "wails task test with spaces-*") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + taskfileContent := `version: '3' + +vars: + BIN_DIR: "` + tmpDir + `/bin" + APP_NAME: "My App" + +tasks: + create-bundle: + cmds: + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" +` + + taskfilePath := filepath.Join(tmpDir, "Taskfile.yml") + err = os.WriteFile(taskfilePath, []byte(taskfileContent), 0644) + require.NoError(t, err) + + originalWd, err := os.Getwd() + require.NoError(t, err) + defer os.Chdir(originalWd) + + err = os.Chdir(tmpDir) + require.NoError(t, err) + + err = RunTask(&RunTaskOptions{Name: "create-bundle"}, []string{}) + require.NoError(t, err) + + appContentsDir := filepath.Join(tmpDir, "bin", "My App.app", "Contents") + + macOSDir := filepath.Join(appContentsDir, "MacOS") + info, err := os.Stat(macOSDir) + require.NoError(t, err, "MacOS directory should exist") + assert.True(t, info.IsDir(), "MacOS should be a directory") + + resourcesDir := filepath.Join(appContentsDir, "Resources") + info, err = os.Stat(resourcesDir) + require.NoError(t, err, "Resources directory should exist") + assert.True(t, info.IsDir(), "Resources should be a directory") +} diff --git a/v3/internal/fileexplorer/desktopfile.go b/v3/internal/fileexplorer/desktopfile.go new file mode 100644 index 000000000..4cdd0af04 --- /dev/null +++ b/v3/internal/fileexplorer/desktopfile.go @@ -0,0 +1,96 @@ +package fileexplorer + +import ( + "bufio" + "io" + "os" + "strings" +) + +// DesktopEntry represents a parsed .desktop file's [Desktop Entry] section. +// This is a minimal parser that only extracts the fields we need, +// replacing the full gopkg.in/ini.v1 dependency (~34KB + 68 transitive deps). +type DesktopEntry struct { + Exec string +} + +// ParseDesktopFile parses a .desktop file and returns the Desktop Entry section. +// It follows the Desktop Entry Specification: +// ParseDesktopFile parses the `[Desktop Entry]` section of the desktop file at path and returns a DesktopEntry. +// It returns an error if the file cannot be opened or if parsing the file fails. +func ParseDesktopFile(path string) (*DesktopEntry, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + return ParseDesktopReader(f) +} + +// ParseDesktopReader parses the [Desktop Entry] section of a .desktop file from r and extracts the Exec value. +// It ignores empty lines and lines starting with '#', treats section names as case-sensitive, and stops parsing after leaving the [Desktop Entry] section. +// The returned *DesktopEntry has Exec set to the exact value of the Exec key if present (whitespace preserved). +// An error is returned if reading from r fails. +func ParseDesktopReader(r io.Reader) (*DesktopEntry, error) { + scanner := bufio.NewScanner(r) + entry := &DesktopEntry{} + + inDesktopEntry := false + + for scanner.Scan() { + line := scanner.Text() + + // Skip empty lines + if len(line) == 0 { + continue + } + + // Skip comments (# at start of line) + if line[0] == '#' { + continue + } + + // Handle section headers + if line[0] == '[' { + // Check if this is the [Desktop Entry] section + // The spec says section names are case-sensitive + trimmed := strings.TrimSpace(line) + if trimmed == "[Desktop Entry]" { + inDesktopEntry = true + } else if inDesktopEntry { + // We've left the [Desktop Entry] section + // (e.g., entering [Desktop Action new-window]) + // We already have what we need, so we can stop + break + } + continue + } + + // Only process key=value pairs in [Desktop Entry] section + if !inDesktopEntry { + continue + } + + // Parse key=value (spec says no spaces around =, but be lenient) + eqIdx := strings.Index(line, "=") + if eqIdx == -1 { + continue + } + + key := strings.TrimSpace(line[:eqIdx]) + value := line[eqIdx+1:] // Don't trim value - preserve intentional whitespace + + // We only need the Exec key + // Per spec, keys are case-sensitive and Exec is always "Exec" + if key == "Exec" { + entry.Exec = value + // Continue parsing in case there are multiple Exec lines (shouldn't happen but be safe) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return entry, nil +} \ No newline at end of file diff --git a/v3/internal/fileexplorer/desktopfile_test.go b/v3/internal/fileexplorer/desktopfile_test.go new file mode 100644 index 000000000..feac60fcb --- /dev/null +++ b/v3/internal/fileexplorer/desktopfile_test.go @@ -0,0 +1,532 @@ +package fileexplorer + +import ( + "strings" + "testing" +) + +func TestParseDesktopReader(t *testing.T) { + tests := []struct { + name string + input string + wantExec string + wantErr bool + }{ + { + name: "simple desktop file", + input: `[Desktop Entry] +Name=Files +Exec=nautilus %U +Icon=org.gnome.Nautilus +`, + wantExec: "nautilus %U", + }, + { + name: "exec with full path", + input: `[Desktop Entry] +Name=1Password +Exec=/opt/1Password/1password %U +`, + wantExec: "/opt/1Password/1password %U", + }, + { + name: "exec without arguments", + input: `[Desktop Entry] +Name=Btop +Exec=btop +Terminal=true +`, + wantExec: "btop", + }, + { + name: "exec with spaces in path", + input: `[Desktop Entry] +Name=My App +Exec="/path/with spaces/myapp" %f +`, + wantExec: `"/path/with spaces/myapp" %f`, + }, + { + name: "comments are ignored", + input: `# This is a comment +[Desktop Entry] +# Another comment +Name=Files +Exec=nautilus +# Comment after +`, + wantExec: "nautilus", + }, + { + name: "empty lines are ignored", + input: ` + +[Desktop Entry] + +Name=Files + +Exec=nautilus + +`, + wantExec: "nautilus", + }, + { + name: "key before section is ignored", + input: `Exec=ignored +[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "other sections after Desktop Entry are ignored", + input: `[Desktop Entry] +Exec=nautilus --new-window %U +Icon=nautilus + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window +`, + wantExec: "nautilus --new-window %U", + }, + { + name: "section before Desktop Entry is ignored", + input: `[Some Other Section] +Exec=ignored + +[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "case sensitive section name", + input: `[desktop entry] +Exec=ignored + +[Desktop Entry] +Exec=correct +`, + wantExec: "correct", + }, + { + name: "case sensitive key name", + input: `[Desktop Entry] +exec=ignored +EXEC=also ignored +Exec=correct +`, + wantExec: "correct", + }, + { + name: "value with equals sign", + input: `[Desktop Entry] +Exec=env VAR=value myapp +`, + wantExec: "env VAR=value myapp", + }, + { + name: "value with multiple equals signs", + input: `[Desktop Entry] +Exec=env A=1 B=2 C=3 myapp +`, + wantExec: "env A=1 B=2 C=3 myapp", + }, + { + name: "localized keys are separate", + input: `[Desktop Entry] +Name[en]=Files +Name=Default Files +Exec[en]=ignored +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "whitespace in section header", + input: `[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "no exec key", + input: `[Desktop Entry] +Name=Files +Icon=nautilus +`, + wantExec: "", + }, + { + name: "empty file", + input: ``, + wantExec: "", + }, + { + name: "only comments", + input: `# Comment 1 +# Comment 2 +`, + wantExec: "", + }, + { + name: "no Desktop Entry section", + input: `[Other Section] +Exec=ignored +`, + wantExec: "", + }, + { + name: "real nautilus desktop file structure", + input: `[Desktop Entry] +Name[en_CA]=Files +Name[en_GB]=Files +Name=Files +Comment=Access and organize files +Keywords=folder;manager;explore;disk;filesystem;nautilus; +Exec=nautilus --new-window %U +Icon=org.gnome.Nautilus +Terminal=false +Type=Application +DBusActivatable=true +StartupNotify=true +Categories=GNOME;GTK;Utility;Core;FileManager; +MimeType=inode/directory;application/x-7z-compressed; +X-GNOME-UsesNotifications=true +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window +`, + wantExec: "nautilus --new-window %U", + }, + { + name: "thunar style", + input: `[Desktop Entry] +Version=1.0 +Name=Thunar File Manager +Exec=thunar %F +Icon=Thunar +Type=Application +Categories=System;FileTools;FileManager; +`, + wantExec: "thunar %F", + }, + { + name: "dolphin style", + input: `[Desktop Entry] +Type=Application +Exec=dolphin %u +Icon=system-file-manager +Name=Dolphin +GenericName=File Manager +`, + wantExec: "dolphin %u", + }, + { + name: "pcmanfm style", + input: `[Desktop Entry] +Type=Application +Name=PCManFM +GenericName=File Manager +Exec=pcmanfm %U +Icon=system-file-manager +`, + wantExec: "pcmanfm %U", + }, + { + name: "exec with environment variable", + input: `[Desktop Entry] +Exec=env GDK_BACKEND=x11 nautilus %U +`, + wantExec: "env GDK_BACKEND=x11 nautilus %U", + }, + { + name: "trailing whitespace in value preserved", + input: `[Desktop Entry] +Exec=nautilus +`, + wantExec: "nautilus ", + }, + { + name: "leading whitespace in key", + input: `[Desktop Entry] + Exec=nautilus +`, + wantExec: "nautilus", + }, + { + name: "space around equals", + input: `[Desktop Entry] +Exec = nautilus +`, + wantExec: " nautilus", // We trim the key, value starts after = + }, + { + name: "line without equals is ignored", + input: `[Desktop Entry] +InvalidLine +Exec=nautilus +AnotherInvalidLine +`, + wantExec: "nautilus", + }, + { + name: "UTF-8 in exec path", + input: `[Desktop Entry] +Exec=/usr/bin/文件管理器 %U +`, + wantExec: "/usr/bin/文件管理器 %U", + }, + { + name: "special characters in exec", + input: `[Desktop Entry] +Exec=sh -c "echo 'hello world' && nautilus %U" +`, + wantExec: `sh -c "echo 'hello world' && nautilus %U"`, + }, + { + name: "multiple Desktop Entry sections (invalid file, last value wins)", + input: `[Desktop Entry] +Exec=first + +[Desktop Entry] +Exec=second +`, + wantExec: "second", // Invalid file, but we handle it gracefully + }, + { + name: "very long exec line", + input: `[Desktop Entry] +Exec=` + strings.Repeat("a", 1000) + ` +`, + wantExec: strings.Repeat("a", 1000), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + entry, err := ParseDesktopReader(strings.NewReader(tt.input)) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDesktopReader() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + return + } + if entry.Exec != tt.wantExec { + t.Errorf("ParseDesktopReader() Exec = %q, want %q", entry.Exec, tt.wantExec) + } + }) + } +} + +func TestParseDesktopReader_LineScanning(t *testing.T) { + // Test that we handle lines at the scanner's limit (64KB default) + // bufio.Scanner returns an error for lines > 64KB, which is acceptable + // since real .desktop files never have lines that long + + t.Run("line at buffer limit returns error", func(t *testing.T) { + // Create a line that exceeds the buffer size (64KB) + longValue := strings.Repeat("x", 65536) + input := "[Desktop Entry]\nExec=" + longValue + "\n" + + _, err := ParseDesktopReader(strings.NewReader(input)) + if err == nil { + t.Error("Expected error for line exceeding buffer size, got nil") + } + }) + + t.Run("line under buffer limit works", func(t *testing.T) { + // Create a line that's under the limit (should work fine) + longValue := strings.Repeat("x", 60000) + input := "[Desktop Entry]\nExec=" + longValue + "\n" + + entry, err := ParseDesktopReader(strings.NewReader(input)) + if err != nil { + t.Errorf("Unexpected error for long but valid line: %v", err) + return + } + if entry.Exec != longValue { + t.Errorf("Long line not parsed correctly, got length %d, want %d", len(entry.Exec), len(longValue)) + } + }) +} + +func TestParseDesktopReader_RealWorldFiles(t *testing.T) { + // These are actual .desktop file contents from real systems + realWorldTests := []struct { + name string + content string + wantExec string + }{ + { + name: "GNOME Nautilus 43.x", + content: `[Desktop Entry] +Name=Files +Comment=Access and organize files +Keywords=folder;manager;explore;disk;filesystem;nautilus; +Exec=nautilus --new-window %U +Icon=org.gnome.Nautilus +Terminal=false +Type=Application +DBusActivatable=true +StartupNotify=true +Categories=GNOME;GTK;Utility;Core;FileManager; +MimeType=inode/directory;application/x-7z-compressed; +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window`, + wantExec: "nautilus --new-window %U", + }, + { + name: "KDE Dolphin", + content: `[Desktop Entry] +Type=Application +Exec=dolphin %u +Icon=system-file-manager +Terminal=false +InitialPreference=9 +Name=Dolphin +GenericName=File Manager +MimeType=inode/directory; +Categories=Qt;KDE;System;FileTools;FileManager; +Actions=new-window; + +[Desktop Action new-window] +Name=Open a New Window +Exec=dolphin %u`, + wantExec: "dolphin %u", + }, + { + name: "Thunar", + content: `[Desktop Entry] +Version=1.0 +Name=Thunar File Manager +GenericName=File Manager +Comment=Browse the filesystem with the file manager +Exec=thunar %F +Icon=Thunar +Terminal=false +StartupNotify=true +Type=Application +Categories=System;FileTools;FileManager; +`, + wantExec: "thunar %F", + }, + { + name: "PCManFM", + content: `[Desktop Entry] +Type=Application +Name=PCManFM +GenericName=File Manager +Comment=Browse the file system +Exec=pcmanfm %U +Icon=system-file-manager +Terminal=false +StartupNotify=true +Categories=Utility;FileManager;`, + wantExec: "pcmanfm %U", + }, + { + name: "Caja (MATE)", + content: `[Desktop Entry] +Name=Files +Comment=Access and organize files +Exec=caja %U +Icon=system-file-manager +Terminal=false +Type=Application +Categories=MATE;System;FileManager; +StartupNotify=true`, + wantExec: "caja %U", + }, + { + name: "Nemo (Cinnamon)", + content: `[Desktop Entry] +Name=Files +Comment=Access and organize files +Exec=nemo %U +Icon=folder +Terminal=false +Type=Application +StartupNotify=true +Categories=GNOME;GTK;Utility;Core; +MimeType=inode/directory;`, + wantExec: "nemo %U", + }, + } + + for _, tt := range realWorldTests { + t.Run(tt.name, func(t *testing.T) { + entry, err := ParseDesktopReader(strings.NewReader(tt.content)) + if err != nil { + t.Fatalf("ParseDesktopReader() error = %v", err) + } + if entry.Exec != tt.wantExec { + t.Errorf("ParseDesktopReader() Exec = %q, want %q", entry.Exec, tt.wantExec) + } + }) + } +} + +// BenchmarkParseDesktopReader measures parsing performance +func BenchmarkParseDesktopReader(b *testing.B) { + // Real Nautilus .desktop file content + content := `[Desktop Entry] +Name=Files +Comment=Access and organize files +Keywords=folder;manager;explore;disk;filesystem;nautilus; +Exec=nautilus --new-window %U +Icon=org.gnome.Nautilus +Terminal=false +Type=Application +DBusActivatable=true +StartupNotify=true +Categories=GNOME;GTK;Utility;Core;FileManager; +MimeType=inode/directory;application/x-7z-compressed; +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=nautilus --new-window +` + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ParseDesktopReader(strings.NewReader(content)) + if err != nil { + b.Fatal(err) + } + } +} + +// BenchmarkParseDesktopReader_Large tests parsing a file with many localized entries +func BenchmarkParseDesktopReader_Large(b *testing.B) { + // Simulate a desktop file with many localized Name entries (like Nautilus) + var sb strings.Builder + sb.WriteString("[Desktop Entry]\n") + for i := 0; i < 100; i++ { + sb.WriteString("Name[lang") + sb.WriteString(strings.Repeat("x", 5)) + sb.WriteString("]=Localized Name\n") + } + sb.WriteString("Exec=nautilus %U\n") + sb.WriteString("[Desktop Action new-window]\n") + sb.WriteString("Name=New Window\n") + sb.WriteString("Exec=nautilus\n") + + content := sb.String() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ParseDesktopReader(strings.NewReader(content)) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/v3/internal/fileexplorer/fileexplorer_linux.go b/v3/internal/fileexplorer/fileexplorer_linux.go index cd0201dfc..bf7e070a7 100644 --- a/v3/internal/fileexplorer/fileexplorer_linux.go +++ b/v3/internal/fileexplorer/fileexplorer_linux.go @@ -11,10 +11,9 @@ import ( "path/filepath" "strings" "syscall" - - ini "gopkg.in/ini.v1" ) +// when possible; the fallback method does not support selecting a file. func explorerBinArgs(path string, selectFile bool) (string, []string, error) { // Map of field codes to their replacements var fieldCodes = map[string]string{ @@ -38,22 +37,22 @@ func explorerBinArgs(path string, selectFile bool) (string, []string, error) { return fallbackExplorerBinArgs(path, selectFile) } - desktopFile, err := findDesktopFile(strings.TrimSpace((buf.String()))) + desktopFilePath, err := findDesktopFile(strings.TrimSpace((buf.String()))) if err != nil { return fallbackExplorerBinArgs(path, selectFile) } - cfg, err := ini.Load(desktopFile) + entry, err := ParseDesktopFile(desktopFilePath) if err != nil { // Opting to fallback rather than fail return fallbackExplorerBinArgs(path, selectFile) } - exec := cfg.Section("Desktop Entry").Key("Exec").String() + execCmd := entry.Exec for fieldCode, replacement := range fieldCodes { - exec = strings.ReplaceAll(exec, fieldCode, replacement) + execCmd = strings.ReplaceAll(execCmd, fieldCode, replacement) } - args := strings.Fields(exec) + args := strings.Fields(execCmd) if !strings.Contains(strings.Join(args, " "), path) { args = append(args, path) } @@ -111,4 +110,4 @@ func findDesktopFile(xdgFileName string) (string, error) { } err := fmt.Errorf("desktop file not found: %s", xdgFileName) return "", err -} +} \ No newline at end of file diff --git a/v3/internal/generator/collect/known_events.go b/v3/internal/generator/collect/known_events.go index 111c36b7f..857071585 100644 --- a/v3/internal/generator/collect/known_events.go +++ b/v3/internal/generator/collect/known_events.go @@ -6,218 +6,217 @@ func IsKnownEvent(name string) bool { } var knownEvents = map[string]struct{}{ - "common:ApplicationOpenedWithFile": {}, - "common:ApplicationStarted": {}, - "common:ApplicationLaunchedWithUrl": {}, - "common:ThemeChanged": {}, - "common:WindowClosing": {}, - "common:WindowDidMove": {}, - "common:WindowDidResize": {}, - "common:WindowDPIChanged": {}, - "common:WindowFilesDropped": {}, - "common:WindowFocus": {}, - "common:WindowFullscreen": {}, - "common:WindowHide": {}, - "common:WindowLostFocus": {}, - "common:WindowMaximise": {}, - "common:WindowMinimise": {}, - "common:WindowToggleFrameless": {}, - "common:WindowRestore": {}, - "common:WindowRuntimeReady": {}, - "common:WindowShow": {}, - "common:WindowUnFullscreen": {}, - "common:WindowUnMaximise": {}, - "common:WindowUnMinimise": {}, - "common:WindowZoom": {}, - "common:WindowZoomIn": {}, - "common:WindowZoomOut": {}, - "common:WindowZoomReset": {}, - "common:WindowDropZoneFilesDropped": {}, - "linux:ApplicationStartup": {}, - "linux:SystemThemeChanged": {}, - "linux:WindowDeleteEvent": {}, - "linux:WindowDidMove": {}, - "linux:WindowDidResize": {}, - "linux:WindowFocusIn": {}, - "linux:WindowFocusOut": {}, - "linux:WindowLoadStarted": {}, - "linux:WindowLoadRedirected": {}, - "linux:WindowLoadCommitted": {}, - "linux:WindowLoadFinished": {}, - "mac:ApplicationDidBecomeActive": {}, - "mac:ApplicationDidChangeBackingProperties": {}, - "mac:ApplicationDidChangeEffectiveAppearance": {}, - "mac:ApplicationDidChangeIcon": {}, - "mac:ApplicationDidChangeOcclusionState": {}, - "mac:ApplicationDidChangeScreenParameters": {}, - "mac:ApplicationDidChangeStatusBarFrame": {}, - "mac:ApplicationDidChangeStatusBarOrientation": {}, - "mac:ApplicationDidChangeTheme": {}, - "mac:ApplicationDidFinishLaunching": {}, - "mac:ApplicationDidHide": {}, - "mac:ApplicationDidResignActive": {}, - "mac:ApplicationDidUnhide": {}, - "mac:ApplicationDidUpdate": {}, - "mac:ApplicationShouldHandleReopen": {}, - "mac:ApplicationWillBecomeActive": {}, - "mac:ApplicationWillFinishLaunching": {}, - "mac:ApplicationWillHide": {}, - "mac:ApplicationWillResignActive": {}, - "mac:ApplicationWillTerminate": {}, - "mac:ApplicationWillUnhide": {}, - "mac:ApplicationWillUpdate": {}, - "mac:MenuDidAddItem": {}, - "mac:MenuDidBeginTracking": {}, - "mac:MenuDidClose": {}, - "mac:MenuDidDisplayItem": {}, - "mac:MenuDidEndTracking": {}, - "mac:MenuDidHighlightItem": {}, - "mac:MenuDidOpen": {}, - "mac:MenuDidPopUp": {}, - "mac:MenuDidRemoveItem": {}, - "mac:MenuDidSendAction": {}, - "mac:MenuDidSendActionToItem": {}, - "mac:MenuDidUpdate": {}, - "mac:MenuWillAddItem": {}, - "mac:MenuWillBeginTracking": {}, - "mac:MenuWillDisplayItem": {}, - "mac:MenuWillEndTracking": {}, - "mac:MenuWillHighlightItem": {}, - "mac:MenuWillOpen": {}, - "mac:MenuWillPopUp": {}, - "mac:MenuWillRemoveItem": {}, - "mac:MenuWillSendAction": {}, - "mac:MenuWillSendActionToItem": {}, - "mac:MenuWillUpdate": {}, - "mac:WebViewDidCommitNavigation": {}, - "mac:WebViewDidFinishNavigation": {}, + "common:ApplicationOpenedWithFile": {}, + "common:ApplicationStarted": {}, + "common:ApplicationLaunchedWithUrl": {}, + "common:ThemeChanged": {}, + "common:WindowClosing": {}, + "common:WindowDidMove": {}, + "common:WindowDidResize": {}, + "common:WindowDPIChanged": {}, + "common:WindowFilesDropped": {}, + "common:WindowFocus": {}, + "common:WindowFullscreen": {}, + "common:WindowHide": {}, + "common:WindowLostFocus": {}, + "common:WindowMaximise": {}, + "common:WindowMinimise": {}, + "common:WindowToggleFrameless": {}, + "common:WindowRestore": {}, + "common:WindowRuntimeReady": {}, + "common:WindowShow": {}, + "common:WindowUnFullscreen": {}, + "common:WindowUnMaximise": {}, + "common:WindowUnMinimise": {}, + "common:WindowZoom": {}, + "common:WindowZoomIn": {}, + "common:WindowZoomOut": {}, + "common:WindowZoomReset": {}, + "linux:ApplicationStartup": {}, + "linux:SystemThemeChanged": {}, + "linux:WindowDeleteEvent": {}, + "linux:WindowDidMove": {}, + "linux:WindowDidResize": {}, + "linux:WindowFocusIn": {}, + "linux:WindowFocusOut": {}, + "linux:WindowLoadStarted": {}, + "linux:WindowLoadRedirected": {}, + "linux:WindowLoadCommitted": {}, + "linux:WindowLoadFinished": {}, + "mac:ApplicationDidBecomeActive": {}, + "mac:ApplicationDidChangeBackingProperties": {}, + "mac:ApplicationDidChangeEffectiveAppearance": {}, + "mac:ApplicationDidChangeIcon": {}, + "mac:ApplicationDidChangeOcclusionState": {}, + "mac:ApplicationDidChangeScreenParameters": {}, + "mac:ApplicationDidChangeStatusBarFrame": {}, + "mac:ApplicationDidChangeStatusBarOrientation": {}, + "mac:ApplicationDidChangeTheme": {}, + "mac:ApplicationDidFinishLaunching": {}, + "mac:ApplicationDidHide": {}, + "mac:ApplicationDidResignActive": {}, + "mac:ApplicationDidUnhide": {}, + "mac:ApplicationDidUpdate": {}, + "mac:ApplicationShouldHandleReopen": {}, + "mac:ApplicationWillBecomeActive": {}, + "mac:ApplicationWillFinishLaunching": {}, + "mac:ApplicationWillHide": {}, + "mac:ApplicationWillResignActive": {}, + "mac:ApplicationWillTerminate": {}, + "mac:ApplicationWillUnhide": {}, + "mac:ApplicationWillUpdate": {}, + "mac:MenuDidAddItem": {}, + "mac:MenuDidBeginTracking": {}, + "mac:MenuDidClose": {}, + "mac:MenuDidDisplayItem": {}, + "mac:MenuDidEndTracking": {}, + "mac:MenuDidHighlightItem": {}, + "mac:MenuDidOpen": {}, + "mac:MenuDidPopUp": {}, + "mac:MenuDidRemoveItem": {}, + "mac:MenuDidSendAction": {}, + "mac:MenuDidSendActionToItem": {}, + "mac:MenuDidUpdate": {}, + "mac:MenuWillAddItem": {}, + "mac:MenuWillBeginTracking": {}, + "mac:MenuWillDisplayItem": {}, + "mac:MenuWillEndTracking": {}, + "mac:MenuWillHighlightItem": {}, + "mac:MenuWillOpen": {}, + "mac:MenuWillPopUp": {}, + "mac:MenuWillRemoveItem": {}, + "mac:MenuWillSendAction": {}, + "mac:MenuWillSendActionToItem": {}, + "mac:MenuWillUpdate": {}, + "mac:WebViewDidCommitNavigation": {}, + "mac:WebViewDidFinishNavigation": {}, "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation": {}, - "mac:WebViewDidStartProvisionalNavigation": {}, - "mac:WindowDidBecomeKey": {}, - "mac:WindowDidBecomeMain": {}, - "mac:WindowDidBeginSheet": {}, - "mac:WindowDidChangeAlpha": {}, - "mac:WindowDidChangeBackingLocation": {}, - "mac:WindowDidChangeBackingProperties": {}, - "mac:WindowDidChangeCollectionBehavior": {}, - "mac:WindowDidChangeEffectiveAppearance": {}, - "mac:WindowDidChangeOcclusionState": {}, - "mac:WindowDidChangeOrderingMode": {}, - "mac:WindowDidChangeScreen": {}, - "mac:WindowDidChangeScreenParameters": {}, - "mac:WindowDidChangeScreenProfile": {}, - "mac:WindowDidChangeScreenSpace": {}, - "mac:WindowDidChangeScreenSpaceProperties": {}, - "mac:WindowDidChangeSharingType": {}, - "mac:WindowDidChangeSpace": {}, - "mac:WindowDidChangeSpaceOrderingMode": {}, - "mac:WindowDidChangeTitle": {}, - "mac:WindowDidChangeToolbar": {}, - "mac:WindowDidDeminiaturize": {}, - "mac:WindowDidEndSheet": {}, - "mac:WindowDidEnterFullScreen": {}, - "mac:WindowDidEnterVersionBrowser": {}, - "mac:WindowDidExitFullScreen": {}, - "mac:WindowDidExitVersionBrowser": {}, - "mac:WindowDidExpose": {}, - "mac:WindowDidFocus": {}, - "mac:WindowDidMiniaturize": {}, - "mac:WindowDidMove": {}, - "mac:WindowDidOrderOffScreen": {}, - "mac:WindowDidOrderOnScreen": {}, - "mac:WindowDidResignKey": {}, - "mac:WindowDidResignMain": {}, - "mac:WindowDidResize": {}, - "mac:WindowDidUpdate": {}, - "mac:WindowDidUpdateAlpha": {}, - "mac:WindowDidUpdateCollectionBehavior": {}, - "mac:WindowDidUpdateCollectionProperties": {}, - "mac:WindowDidUpdateShadow": {}, - "mac:WindowDidUpdateTitle": {}, - "mac:WindowDidUpdateToolbar": {}, - "mac:WindowDidZoom": {}, - "mac:WindowFileDraggingEntered": {}, - "mac:WindowFileDraggingExited": {}, - "mac:WindowFileDraggingPerformed": {}, - "mac:WindowHide": {}, - "mac:WindowMaximise": {}, - "mac:WindowUnMaximise": {}, - "mac:WindowMinimise": {}, - "mac:WindowUnMinimise": {}, - "mac:WindowShouldClose": {}, - "mac:WindowShow": {}, - "mac:WindowWillBecomeKey": {}, - "mac:WindowWillBecomeMain": {}, - "mac:WindowWillBeginSheet": {}, - "mac:WindowWillChangeOrderingMode": {}, - "mac:WindowWillClose": {}, - "mac:WindowWillDeminiaturize": {}, - "mac:WindowWillEnterFullScreen": {}, - "mac:WindowWillEnterVersionBrowser": {}, - "mac:WindowWillExitFullScreen": {}, - "mac:WindowWillExitVersionBrowser": {}, - "mac:WindowWillFocus": {}, - "mac:WindowWillMiniaturize": {}, - "mac:WindowWillMove": {}, - "mac:WindowWillOrderOffScreen": {}, - "mac:WindowWillOrderOnScreen": {}, - "mac:WindowWillResignMain": {}, - "mac:WindowWillResize": {}, - "mac:WindowWillUnfocus": {}, - "mac:WindowWillUpdate": {}, - "mac:WindowWillUpdateAlpha": {}, - "mac:WindowWillUpdateCollectionBehavior": {}, - "mac:WindowWillUpdateCollectionProperties": {}, - "mac:WindowWillUpdateShadow": {}, - "mac:WindowWillUpdateTitle": {}, - "mac:WindowWillUpdateToolbar": {}, - "mac:WindowWillUpdateVisibility": {}, - "mac:WindowWillUseStandardFrame": {}, - "mac:WindowZoomIn": {}, - "mac:WindowZoomOut": {}, - "mac:WindowZoomReset": {}, - "windows:APMPowerSettingChange": {}, - "windows:APMPowerStatusChange": {}, - "windows:APMResumeAutomatic": {}, - "windows:APMResumeSuspend": {}, - "windows:APMSuspend": {}, - "windows:ApplicationStarted": {}, - "windows:SystemThemeChanged": {}, - "windows:WebViewNavigationCompleted": {}, - "windows:WindowActive": {}, - "windows:WindowBackgroundErase": {}, - "windows:WindowClickActive": {}, - "windows:WindowClosing": {}, - "windows:WindowDidMove": {}, - "windows:WindowDidResize": {}, - "windows:WindowDPIChanged": {}, - "windows:WindowDragDrop": {}, - "windows:WindowDragEnter": {}, - "windows:WindowDragLeave": {}, - "windows:WindowDragOver": {}, - "windows:WindowEndMove": {}, - "windows:WindowEndResize": {}, - "windows:WindowFullscreen": {}, - "windows:WindowHide": {}, - "windows:WindowInactive": {}, - "windows:WindowKeyDown": {}, - "windows:WindowKeyUp": {}, - "windows:WindowKillFocus": {}, - "windows:WindowNonClientHit": {}, - "windows:WindowNonClientMouseDown": {}, - "windows:WindowNonClientMouseLeave": {}, - "windows:WindowNonClientMouseMove": {}, - "windows:WindowNonClientMouseUp": {}, - "windows:WindowPaint": {}, - "windows:WindowRestore": {}, - "windows:WindowSetFocus": {}, - "windows:WindowShow": {}, - "windows:WindowStartMove": {}, - "windows:WindowStartResize": {}, - "windows:WindowUnFullscreen": {}, - "windows:WindowZOrderChanged": {}, - "windows:WindowMinimise": {}, - "windows:WindowUnMinimise": {}, - "windows:WindowMaximise": {}, - "windows:WindowUnMaximise": {}, + "mac:WebViewDidStartProvisionalNavigation": {}, + "mac:WindowDidBecomeKey": {}, + "mac:WindowDidBecomeMain": {}, + "mac:WindowDidBeginSheet": {}, + "mac:WindowDidChangeAlpha": {}, + "mac:WindowDidChangeBackingLocation": {}, + "mac:WindowDidChangeBackingProperties": {}, + "mac:WindowDidChangeCollectionBehavior": {}, + "mac:WindowDidChangeEffectiveAppearance": {}, + "mac:WindowDidChangeOcclusionState": {}, + "mac:WindowDidChangeOrderingMode": {}, + "mac:WindowDidChangeScreen": {}, + "mac:WindowDidChangeScreenParameters": {}, + "mac:WindowDidChangeScreenProfile": {}, + "mac:WindowDidChangeScreenSpace": {}, + "mac:WindowDidChangeScreenSpaceProperties": {}, + "mac:WindowDidChangeSharingType": {}, + "mac:WindowDidChangeSpace": {}, + "mac:WindowDidChangeSpaceOrderingMode": {}, + "mac:WindowDidChangeTitle": {}, + "mac:WindowDidChangeToolbar": {}, + "mac:WindowDidDeminiaturize": {}, + "mac:WindowDidEndSheet": {}, + "mac:WindowDidEnterFullScreen": {}, + "mac:WindowDidEnterVersionBrowser": {}, + "mac:WindowDidExitFullScreen": {}, + "mac:WindowDidExitVersionBrowser": {}, + "mac:WindowDidExpose": {}, + "mac:WindowDidFocus": {}, + "mac:WindowDidMiniaturize": {}, + "mac:WindowDidMove": {}, + "mac:WindowDidOrderOffScreen": {}, + "mac:WindowDidOrderOnScreen": {}, + "mac:WindowDidResignKey": {}, + "mac:WindowDidResignMain": {}, + "mac:WindowDidResize": {}, + "mac:WindowDidUpdate": {}, + "mac:WindowDidUpdateAlpha": {}, + "mac:WindowDidUpdateCollectionBehavior": {}, + "mac:WindowDidUpdateCollectionProperties": {}, + "mac:WindowDidUpdateShadow": {}, + "mac:WindowDidUpdateTitle": {}, + "mac:WindowDidUpdateToolbar": {}, + "mac:WindowDidZoom": {}, + "mac:WindowFileDraggingEntered": {}, + "mac:WindowFileDraggingExited": {}, + "mac:WindowFileDraggingPerformed": {}, + "mac:WindowHide": {}, + "mac:WindowMaximise": {}, + "mac:WindowUnMaximise": {}, + "mac:WindowMinimise": {}, + "mac:WindowUnMinimise": {}, + "mac:WindowShouldClose": {}, + "mac:WindowShow": {}, + "mac:WindowWillBecomeKey": {}, + "mac:WindowWillBecomeMain": {}, + "mac:WindowWillBeginSheet": {}, + "mac:WindowWillChangeOrderingMode": {}, + "mac:WindowWillClose": {}, + "mac:WindowWillDeminiaturize": {}, + "mac:WindowWillEnterFullScreen": {}, + "mac:WindowWillEnterVersionBrowser": {}, + "mac:WindowWillExitFullScreen": {}, + "mac:WindowWillExitVersionBrowser": {}, + "mac:WindowWillFocus": {}, + "mac:WindowWillMiniaturize": {}, + "mac:WindowWillMove": {}, + "mac:WindowWillOrderOffScreen": {}, + "mac:WindowWillOrderOnScreen": {}, + "mac:WindowWillResignMain": {}, + "mac:WindowWillResize": {}, + "mac:WindowWillUnfocus": {}, + "mac:WindowWillUpdate": {}, + "mac:WindowWillUpdateAlpha": {}, + "mac:WindowWillUpdateCollectionBehavior": {}, + "mac:WindowWillUpdateCollectionProperties": {}, + "mac:WindowWillUpdateShadow": {}, + "mac:WindowWillUpdateTitle": {}, + "mac:WindowWillUpdateToolbar": {}, + "mac:WindowWillUpdateVisibility": {}, + "mac:WindowWillUseStandardFrame": {}, + "mac:WindowZoomIn": {}, + "mac:WindowZoomOut": {}, + "mac:WindowZoomReset": {}, + "windows:APMPowerSettingChange": {}, + "windows:APMPowerStatusChange": {}, + "windows:APMResumeAutomatic": {}, + "windows:APMResumeSuspend": {}, + "windows:APMSuspend": {}, + "windows:ApplicationStarted": {}, + "windows:SystemThemeChanged": {}, + "windows:WebViewNavigationCompleted": {}, + "windows:WindowActive": {}, + "windows:WindowBackgroundErase": {}, + "windows:WindowClickActive": {}, + "windows:WindowClosing": {}, + "windows:WindowDidMove": {}, + "windows:WindowDidResize": {}, + "windows:WindowDPIChanged": {}, + "windows:WindowDragDrop": {}, + "windows:WindowDragEnter": {}, + "windows:WindowDragLeave": {}, + "windows:WindowDragOver": {}, + "windows:WindowEndMove": {}, + "windows:WindowEndResize": {}, + "windows:WindowFullscreen": {}, + "windows:WindowHide": {}, + "windows:WindowInactive": {}, + "windows:WindowKeyDown": {}, + "windows:WindowKeyUp": {}, + "windows:WindowKillFocus": {}, + "windows:WindowNonClientHit": {}, + "windows:WindowNonClientMouseDown": {}, + "windows:WindowNonClientMouseLeave": {}, + "windows:WindowNonClientMouseMove": {}, + "windows:WindowNonClientMouseUp": {}, + "windows:WindowPaint": {}, + "windows:WindowRestore": {}, + "windows:WindowSetFocus": {}, + "windows:WindowShow": {}, + "windows:WindowStartMove": {}, + "windows:WindowStartResize": {}, + "windows:WindowUnFullscreen": {}, + "windows:WindowZOrderChanged": {}, + "windows:WindowMinimise": {}, + "windows:WindowUnMinimise": {}, + "windows:WindowMaximise": {}, + "windows:WindowUnMaximise": {}, } diff --git a/v3/internal/libpath/cache_linux.go b/v3/internal/libpath/cache_linux.go new file mode 100644 index 000000000..5591c6833 --- /dev/null +++ b/v3/internal/libpath/cache_linux.go @@ -0,0 +1,79 @@ +//go:build linux + +package libpath + +import "sync" + +// pathCache holds cached dynamic library paths to avoid repeated +// expensive filesystem and subprocess operations. +type pathCache struct { + mu sync.RWMutex + flatpak []string + snap []string + nix []string + initOnce sync.Once + inited bool +} + +var cache pathCache + +// init populates the cache with dynamic paths from package managers. +// This is called lazily on first access. +func (c *pathCache) init() { + c.initOnce.Do(func() { + // Discover paths without holding the lock + flatpak := discoverFlatpakLibPaths() + snap := discoverSnapLibPaths() + nix := discoverNixLibPaths() + + // Hold lock only while updating the cache + c.mu.Lock() + c.flatpak = flatpak + c.snap = snap + c.nix = nix + c.inited = true + c.mu.Unlock() + }) +} + +// getFlatpak returns cached Flatpak library paths. +func (c *pathCache) getFlatpak() []string { + c.init() + c.mu.RLock() + defer c.mu.RUnlock() + return c.flatpak +} + +// getSnap returns cached Snap library paths. +func (c *pathCache) getSnap() []string { + c.init() + c.mu.RLock() + defer c.mu.RUnlock() + return c.snap +} + +// getNix returns cached Nix library paths. +func (c *pathCache) getNix() []string { + c.init() + c.mu.RLock() + defer c.mu.RUnlock() + return c.nix +} + +// invalidate clears the cache and forces re-discovery on next access. +func (c *pathCache) invalidate() { + c.mu.Lock() + defer c.mu.Unlock() + c.flatpak = nil + c.snap = nil + c.nix = nil + c.initOnce = sync.Once{} // Reset so init() runs again + c.inited = false +} + +// InvalidateCache clears the cached dynamic library paths. +// Call this if packages are installed or removed during runtime +// and you need to re-discover library paths. +func InvalidateCache() { + cache.invalidate() +} diff --git a/v3/internal/libpath/flatpak_linux.go b/v3/internal/libpath/flatpak_linux.go new file mode 100644 index 000000000..ce83c08c6 --- /dev/null +++ b/v3/internal/libpath/flatpak_linux.go @@ -0,0 +1,53 @@ +//go:build linux + +package libpath + +import ( + "os" + "os/exec" + "path/filepath" + "strings" +) + +// getFlatpakLibPaths returns cached library paths from installed Flatpak runtimes. +func getFlatpakLibPaths() []string { + return cache.getFlatpak() +} + +// discoverFlatpakLibPaths scans for Flatpak runtime library directories. +// Uses `flatpak --installations` and scans for runtime lib directories. +func discoverFlatpakLibPaths() []string { + var paths []string + + // Get system and user installation directories + installDirs := []string{ + "/var/lib/flatpak", // System default + os.ExpandEnv("$HOME/.local/share/flatpak"), // User default + } + + // Try to get actual installation path from flatpak + if out, err := exec.Command("flatpak", "--installations").Output(); err == nil { + for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { + if line != "" { + installDirs = append(installDirs, line) + } + } + } + + // Scan for runtime lib directories + for _, installDir := range installDirs { + runtimeDir := filepath.Join(installDir, "runtime") + if _, err := os.Stat(runtimeDir); err != nil { + continue + } + + // Look for lib directories in runtimes + // Structure: runtime/////files/lib + matches, err := filepath.Glob(filepath.Join(runtimeDir, "*", "*", "*", "*", "files", "lib")) + if err == nil { + paths = append(paths, matches...) + } + } + + return paths +} diff --git a/v3/internal/libpath/libpath.go b/v3/internal/libpath/libpath.go new file mode 100644 index 000000000..4c32ce518 --- /dev/null +++ b/v3/internal/libpath/libpath.go @@ -0,0 +1,104 @@ +// Package libpath provides utilities for finding native library paths on Linux. +// +// # Overview +// +// This package helps locate shared libraries (.so files) on Linux systems, +// supporting multiple distributions and package managers. It's particularly +// useful for applications that need to link against libraries like GTK, +// WebKit2GTK, or other system libraries at runtime. +// +// # Search Strategy +// +// The package uses a multi-tier search strategy, trying each method in order +// until a library is found: +// +// 1. pkg-config: Queries the pkg-config database for library paths +// 2. ldconfig: Searches the dynamic linker cache +// 3. Filesystem: Scans common library directories +// +// # Supported Distributions +// +// The package includes default search paths for: +// +// - Debian/Ubuntu (multiarch paths like /usr/lib/x86_64-linux-gnu) +// - Fedora/RHEL/CentOS (/usr/lib64, /usr/lib64/gtk-*) +// - Arch Linux (/usr/lib/webkit2gtk-*, /usr/lib/gtk-*) +// - openSUSE (/usr/lib64/gcc/x86_64-suse-linux) +// - NixOS and Nix package manager +// +// # Package Manager Support +// +// Dynamic paths are discovered from: +// +// - Flatpak: Scans runtime directories via `flatpak --installations` +// - Snap: Globs /snap/*/current/usr/lib* directories +// - Nix: Checks ~/.nix-profile/lib and /run/current-system/sw/lib +// +// # Caching +// +// Dynamic path discovery (Flatpak, Snap, Nix) is cached for performance. +// The cache is populated on first access and persists for the process lifetime. +// Use [InvalidateCache] to force re-discovery if packages are installed/removed +// during runtime. +// +// # Security +// +// The current directory (".") is never included in search paths by default, +// as this is a security risk. Use [FindLibraryPathWithOptions] with +// IncludeCurrentDir if you explicitly need this behavior (not recommended +// for production). +// +// # Performance +// +// Typical lookup times (cached): +// +// - Found via pkg-config: ~2ms (spawns external process) +// - Found via ldconfig: ~1.3ms (spawns external process) +// - Found via filesystem: ~0.1ms (uses cached paths) +// - Not found (worst case): ~20ms (searches all paths) +// +// # Example Usage +// +// // Find a library by its pkg-config name +// path, err := libpath.FindLibraryPath("webkit2gtk-4.1") +// if err != nil { +// log.Fatal("WebKit2GTK not found:", err) +// } +// fmt.Println("Found at:", path) +// +// // Find a specific .so file +// soPath, err := libpath.FindLibraryFile("libgtk-3.so") +// if err != nil { +// log.Fatal("GTK3 library file not found:", err) +// } +// fmt.Println("Library file:", soPath) +// +// // Get all library search paths +// for _, p := range libpath.GetAllLibPaths() { +// fmt.Println(p) +// } +// +// # Multi-Library Search +// +// When you don't know which version of a library is installed, use the +// multi-library search functions: +// +// // Find any available WebKit2GTK version (first found wins) +// match, err := libpath.FindFirstLibrary("webkit2gtk-4.1", "webkit2gtk-4.0", "webkit2gtk-6.0") +// if err != nil { +// log.Fatal("No WebKit2GTK found") +// } +// fmt.Printf("Found %s at %s\n", match.Name, match.Path) +// +// // Prefer newer versions (ordered search) +// match, err := libpath.FindFirstLibraryOrdered("gtk4", "gtk+-3.0") +// +// // Discover all available versions +// matches := libpath.FindAllLibraries("gtk+-3.0", "gtk4", "webkit2gtk-4.0", "webkit2gtk-4.1") +// for _, m := range matches { +// fmt.Printf("Available: %s at %s\n", m.Name, m.Path) +// } +// +// On non-Linux platforms, stub implementations are provided that always +// return [LibraryNotFoundError]. +package libpath diff --git a/v3/internal/libpath/libpath_linux.go b/v3/internal/libpath/libpath_linux.go new file mode 100644 index 000000000..9b908d405 --- /dev/null +++ b/v3/internal/libpath/libpath_linux.go @@ -0,0 +1,551 @@ +//go:build linux + +package libpath + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" +) + +// Common library search paths on Linux systems +var defaultLibPaths = []string{ + // Standard paths + "/usr/lib", + "/usr/lib64", + "/lib", + "/lib64", + + // Debian/Ubuntu multiarch + "/usr/lib/x86_64-linux-gnu", + "/usr/lib/aarch64-linux-gnu", + "/usr/lib/i386-linux-gnu", + "/usr/lib/arm-linux-gnueabihf", + "/lib/x86_64-linux-gnu", + "/lib/aarch64-linux-gnu", + + // Fedora/RHEL/CentOS + "/usr/lib64/gtk-3.0", + "/usr/lib64/gtk-4.0", + "/usr/lib/gcc/x86_64-redhat-linux", + "/usr/lib/gcc/aarch64-redhat-linux", + + // Arch Linux + "/usr/lib/webkit2gtk-4.0", + "/usr/lib/webkit2gtk-4.1", + "/usr/lib/gtk-3.0", + "/usr/lib/gtk-4.0", + + // openSUSE + "/usr/lib64/gcc/x86_64-suse-linux", + + // Local installations + "/usr/local/lib", + "/usr/local/lib64", +} + +// searchResult holds the result from a parallel search goroutine. +type searchResult struct { + path string + source string // for debugging: "pkg-config", "ldconfig", "filesystem" +} + +// FindLibraryPath attempts to find the path to a library using multiple methods +// in parallel. It searches via pkg-config, ldconfig, and filesystem simultaneously, +// returning as soon as any method finds the library. +// +// The libName should be the pkg-config name (e.g., "gtk+-3.0", "webkit2gtk-4.1"). +// Returns the library directory path and any error encountered. +func FindLibraryPath(libName string) (string, error) { + return findLibraryPathCtx(context.Background(), libName) +} + +// FindLibraryPathSequential is the original sequential implementation. +// Use this if you need deterministic search order (pkg-config → ldconfig → filesystem). +func FindLibraryPathSequential(libName string) (string, error) { + // Try pkg-config first (most reliable when available) + if path, err := findWithPkgConfig(libName); err == nil { + return path, nil + } + + // Try ldconfig cache + if path, err := findWithLdconfig(libName); err == nil { + return path, nil + } + + // Fall back to searching common paths + return findInCommonPaths(libName) +} + +// FindLibraryFile finds the full path to a specific library file (e.g., "libgtk-3.so"). +func FindLibraryFile(fileName string) (string, error) { + // Try ldconfig first + if path, err := findFileWithLdconfig(fileName); err == nil { + return path, nil + } + + // Search all paths including dynamic ones + for _, dir := range GetAllLibPaths() { + // Check exact match + fullPath := filepath.Join(dir, fileName) + if _, err := os.Stat(fullPath); err == nil { + return fullPath, nil + } + + // Check with .so suffix variations + matches, err := filepath.Glob(filepath.Join(dir, fileName+"*")) + if err == nil && len(matches) > 0 { + return matches[0], nil + } + } + + return "", &LibraryNotFoundError{Name: fileName} +} + +// findWithPkgConfig uses pkg-config to find library paths. +func findWithPkgConfig(libName string) (string, error) { + return findWithPkgConfigCtx(context.Background(), libName) +} + +// findWithPkgConfigCtx uses pkg-config to find library paths with context support. +func findWithPkgConfigCtx(ctx context.Context, libName string) (string, error) { + // Check if already cancelled + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + cmd := exec.CommandContext(ctx, "pkg-config", "--libs-only-L", libName) + output, err := cmd.Output() + if err != nil { + return "", err + } + + // Parse -L flags from output + parts := strings.Fields(string(output)) + for _, part := range parts { + if strings.HasPrefix(part, "-L") { + path := strings.TrimPrefix(part, "-L") + if _, err := os.Stat(path); err == nil { + return path, nil + } + } + } + + // Check context before second command + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // If no -L flag, try --variable=libdir + cmd = exec.CommandContext(ctx, "pkg-config", "--variable=libdir", libName) + output, err = cmd.Output() + if err != nil { + return "", err + } + + path := strings.TrimSpace(string(output)) + if path != "" { + if _, err := os.Stat(path); err == nil { + return path, nil + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// findWithLdconfig searches the ldconfig cache for library paths. +func findWithLdconfig(libName string) (string, error) { + return findWithLdconfigCtx(context.Background(), libName) +} + +// findWithLdconfigCtx searches the ldconfig cache for library paths with context support. +func findWithLdconfigCtx(ctx context.Context, libName string) (string, error) { + // Check if already cancelled + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // Convert pkg-config name to library name pattern + // e.g., "gtk+-3.0" -> "libgtk-3", "webkit2gtk-4.1" -> "libwebkit2gtk-4.1" + searchName := pkgConfigToLibName(libName) + + cmd := exec.CommandContext(ctx, "ldconfig", "-p") + output, err := cmd.Output() + if err != nil { + return "", err + } + + for _, line := range strings.Split(string(output), "\n") { + if strings.Contains(line, searchName) { + // Line format: " libname.so.X (libc6,x86-64) => /path/to/lib" + parts := strings.Split(line, "=>") + if len(parts) == 2 { + libPath := strings.TrimSpace(parts[1]) + return filepath.Dir(libPath), nil + } + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// findFileWithLdconfig finds a specific library file using ldconfig. +func findFileWithLdconfig(fileName string) (string, error) { + cmd := exec.Command("ldconfig", "-p") + output, err := cmd.Output() + if err != nil { + return "", err + } + + baseName := strings.TrimSuffix(fileName, ".so") + for _, line := range strings.Split(string(output), "\n") { + if strings.Contains(line, baseName) { + parts := strings.Split(line, "=>") + if len(parts) == 2 { + return strings.TrimSpace(parts[1]), nil + } + } + } + + return "", &LibraryNotFoundError{Name: fileName} +} + +// findInCommonPaths searches common library directories including +// dynamically discovered Flatpak, Snap, and Nix paths. +func findInCommonPaths(libName string) (string, error) { + return findInCommonPathsCtx(context.Background(), libName) +} + +// findInCommonPathsCtx searches common library directories with context support. +func findInCommonPathsCtx(ctx context.Context, libName string) (string, error) { + searchName := pkgConfigToLibName(libName) + + // Search all paths including dynamic ones + allPaths := GetAllLibPaths() + + for _, dir := range allPaths { + // Check if cancelled periodically + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + if _, err := os.Stat(dir); err != nil { + continue + } + + // Look for the library file + pattern := filepath.Join(dir, searchName+"*.so*") + matches, err := filepath.Glob(pattern) + if err == nil && len(matches) > 0 { + return dir, nil + } + + // Also check pkgconfig subdirectory for .pc files + pcPath := filepath.Join(dir, "pkgconfig", libName+".pc") + if _, err := os.Stat(pcPath); err == nil { + return dir, nil + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// pkgConfigToLibName converts a pkg-config package name to a library name pattern. +func pkgConfigToLibName(pkgName string) string { + // Common transformations + name := pkgName + + // Remove version suffix like "-3.0", "-4.1" + // but keep it for webkit2gtk-4.1 style names + if strings.HasPrefix(name, "gtk+-") { + // gtk+-3.0 -> libgtk-3 + name = "libgtk-" + strings.TrimPrefix(name, "gtk+-") + name = strings.Split(name, ".")[0] + } else if strings.HasPrefix(name, "webkit2gtk-") { + // webkit2gtk-4.1 -> libwebkit2gtk-4.1 + name = "lib" + name + } else if !strings.HasPrefix(name, "lib") { + name = "lib" + name + } + + return name +} + +// GetAllLibPaths returns all library paths from LD_LIBRARY_PATH, default paths, +// and dynamically discovered paths from Flatpak, Snap, and Nix. +// It does NOT include the current directory for security reasons. +func GetAllLibPaths() []string { + var paths []string + + // Add LD_LIBRARY_PATH entries first (highest priority) + if ldPath := os.Getenv("LD_LIBRARY_PATH"); ldPath != "" { + for _, p := range strings.Split(ldPath, ":") { + if p != "" { + paths = append(paths, p) + } + } + } + + // Add default system paths + paths = append(paths, defaultLibPaths...) + + // Add dynamically discovered paths from package managers + paths = append(paths, getFlatpakLibPaths()...) + paths = append(paths, getSnapLibPaths()...) + paths = append(paths, getNixLibPaths()...) + + return paths +} + +// FindOptions controls library search behavior. +type FindOptions struct { + // IncludeCurrentDir includes "." in the search path. + // WARNING: This is a security risk and should only be used for development. + IncludeCurrentDir bool + + // ExtraPaths are additional paths to search before the defaults. + ExtraPaths []string +} + +// FindLibraryPathWithOptions attempts to find the path to a library with custom options. +func FindLibraryPathWithOptions(libName string, opts FindOptions) (string, error) { + // Try pkg-config first (most reliable when available) + if path, err := findWithPkgConfig(libName); err == nil { + return path, nil + } + + // Try ldconfig cache + if path, err := findWithLdconfig(libName); err == nil { + return path, nil + } + + // Build search paths - include all dynamic paths too + allPaths := GetAllLibPaths() + searchPaths := make([]string, 0, len(opts.ExtraPaths)+len(allPaths)+1) + + if opts.IncludeCurrentDir { + if cwd, err := os.Getwd(); err == nil { + searchPaths = append(searchPaths, cwd) + } + } + + searchPaths = append(searchPaths, opts.ExtraPaths...) + searchPaths = append(searchPaths, allPaths...) + + // Search the paths + searchName := pkgConfigToLibName(libName) + for _, dir := range searchPaths { + if _, err := os.Stat(dir); err != nil { + continue + } + + pattern := filepath.Join(dir, searchName+"*.so*") + matches, err := filepath.Glob(pattern) + if err == nil && len(matches) > 0 { + return dir, nil + } + + pcPath := filepath.Join(dir, "pkgconfig", libName+".pc") + if _, err := os.Stat(pcPath); err == nil { + return dir, nil + } + } + + return "", &LibraryNotFoundError{Name: libName} +} + +// LibraryNotFoundError is returned when a library cannot be found. +type LibraryNotFoundError struct { + Name string +} + +func (e *LibraryNotFoundError) Error() string { + return "library not found: " + e.Name +} + +// LibraryMatch holds information about a found library. +type LibraryMatch struct { + // Name is the pkg-config name that was searched for. + Name string + // Path is the directory containing the library. + Path string +} + +// FindFirstLibrary searches for multiple libraries in parallel and returns +// the first one found. This is useful when you don't know the exact version +// of a library installed (e.g., gtk+-3.0 vs gtk+-4.0). +// +// The search order among candidates is non-deterministic - whichever is found +// first wins. If you need a specific preference order, list preferred libraries +// first and use FindFirstLibraryOrdered instead. +// +// Example: +// +// match, err := FindFirstLibrary("webkit2gtk-4.1", "webkit2gtk-4.0", "webkit2gtk-6.0") +// if err != nil { +// log.Fatal("No WebKit2GTK found") +// } +// fmt.Printf("Found %s at %s\n", match.Name, match.Path) +func FindFirstLibrary(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + results := make(chan *LibraryMatch, len(libNames)) + var wg sync.WaitGroup + + for _, name := range libNames { + wg.Add(1) + go func(libName string) { + defer wg.Done() + if path, err := findLibraryPathCtx(ctx, libName); err == nil { + select { + case results <- &LibraryMatch{Name: libName, Path: path}: + case <-ctx.Done(): + } + } + }(name) + } + + // Close results when all goroutines complete + go func() { + wg.Wait() + close(results) + }() + + if result := <-results; result != nil { + return result, nil + } + + return nil, &LibraryNotFoundError{Name: strings.Join(libNames, ", ")} +} + +// FindFirstLibraryOrdered searches for libraries in order of preference, +// returning the first one found. Unlike FindFirstLibrary, this respects +// the order of candidates - earlier entries are preferred. +// +// This is useful when you want to prefer newer library versions: +// +// match, err := FindFirstLibraryOrdered("gtk+-4.0", "gtk+-3.0") +// // Will return gtk+-4.0 if available, otherwise gtk+-3.0 +func FindFirstLibraryOrdered(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + + for _, name := range libNames { + if path, err := FindLibraryPath(name); err == nil { + return &LibraryMatch{Name: name, Path: path}, nil + } + } + + return nil, &LibraryNotFoundError{Name: strings.Join(libNames, ", ")} +} + +// FindAllLibraries searches for multiple libraries in parallel and returns +// all that are found. This is useful for discovering which library versions +// are available on the system. +// +// Example: +// +// matches := FindAllLibraries("gtk+-3.0", "gtk+-4.0", "webkit2gtk-4.0", "webkit2gtk-4.1") +// for _, m := range matches { +// fmt.Printf("Found %s at %s\n", m.Name, m.Path) +// } +func FindAllLibraries(libNames ...string) []LibraryMatch { + if len(libNames) == 0 { + return nil + } + + results := make(chan *LibraryMatch, len(libNames)) + var wg sync.WaitGroup + + for _, name := range libNames { + wg.Add(1) + go func(libName string) { + defer wg.Done() + if path, err := FindLibraryPath(libName); err == nil { + results <- &LibraryMatch{Name: libName, Path: path} + } + }(name) + } + + // Close results when all goroutines complete + go func() { + wg.Wait() + close(results) + }() + + var matches []LibraryMatch + for result := range results { + matches = append(matches, *result) + } + + return matches +} + +// findLibraryPathCtx is FindLibraryPath with context support. +func findLibraryPathCtx(ctx context.Context, libName string) (string, error) { + // Create a child context for this search + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + results := make(chan searchResult, 3) + var wg sync.WaitGroup + wg.Add(3) + + go func() { + defer wg.Done() + if path, err := findWithPkgConfigCtx(ctx, libName); err == nil { + select { + case results <- searchResult{path: path, source: "pkg-config"}: + case <-ctx.Done(): + } + } + }() + + go func() { + defer wg.Done() + if path, err := findWithLdconfigCtx(ctx, libName); err == nil { + select { + case results <- searchResult{path: path, source: "ldconfig"}: + case <-ctx.Done(): + } + } + }() + + go func() { + defer wg.Done() + if path, err := findInCommonPathsCtx(ctx, libName); err == nil { + select { + case results <- searchResult{path: path, source: "filesystem"}: + case <-ctx.Done(): + } + } + }() + + go func() { + wg.Wait() + close(results) + }() + + if result, ok := <-results; ok { + return result.path, nil + } + + return "", &LibraryNotFoundError{Name: libName} +} diff --git a/v3/internal/libpath/libpath_linux_test.go b/v3/internal/libpath/libpath_linux_test.go new file mode 100644 index 000000000..0f2e91bef --- /dev/null +++ b/v3/internal/libpath/libpath_linux_test.go @@ -0,0 +1,769 @@ +//go:build linux + +package libpath + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func TestPkgConfigToLibName(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"gtk+-3.0", "libgtk-3"}, + {"gtk+-4.0", "libgtk-4"}, + {"webkit2gtk-4.1", "libwebkit2gtk-4.1"}, + {"webkit2gtk-4.0", "libwebkit2gtk-4.0"}, + {"glib-2.0", "libglib-2.0"}, + {"libsoup-3.0", "libsoup-3.0"}, + {"cairo", "libcairo"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := pkgConfigToLibName(tt.input) + if result != tt.expected { + t.Errorf("pkgConfigToLibName(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} + +func TestGetAllLibPaths(t *testing.T) { + paths := GetAllLibPaths() + + if len(paths) == 0 { + t.Error("GetAllLibPaths() returned empty slice") + } + + // Check that default paths are included + hasUsrLib := false + for _, p := range paths { + if p == "/usr/lib" || p == "/usr/lib64" { + hasUsrLib = true + break + } + } + if !hasUsrLib { + t.Error("GetAllLibPaths() should include /usr/lib or /usr/lib64") + } +} + +func TestGetAllLibPaths_WithLDPath(t *testing.T) { + // Save and restore LD_LIBRARY_PATH + original := os.Getenv("LD_LIBRARY_PATH") + defer os.Setenv("LD_LIBRARY_PATH", original) + + testPath := "/test/custom/lib:/another/path" + os.Setenv("LD_LIBRARY_PATH", testPath) + + paths := GetAllLibPaths() + + // First paths should be from LD_LIBRARY_PATH + if len(paths) < 2 { + t.Fatal("Expected at least 2 paths") + } + if paths[0] != "/test/custom/lib" { + t.Errorf("First path should be /test/custom/lib, got %s", paths[0]) + } + if paths[1] != "/another/path" { + t.Errorf("Second path should be /another/path, got %s", paths[1]) + } +} + +func TestLibraryNotFoundError(t *testing.T) { + err := &LibraryNotFoundError{Name: "testlib"} + expected := "library not found: testlib" + if err.Error() != expected { + t.Errorf("Error() = %q, want %q", err.Error(), expected) + } +} + +func TestFindLibraryPath_NotFound(t *testing.T) { + _, err := FindLibraryPath("nonexistent-library-xyz-123") + if err == nil { + t.Error("Expected error for nonexistent library") + } + + var notFoundErr *LibraryNotFoundError + if _, ok := err.(*LibraryNotFoundError); !ok { + t.Errorf("Expected LibraryNotFoundError, got %T", err) + } else { + notFoundErr = err.(*LibraryNotFoundError) + if notFoundErr.Name != "nonexistent-library-xyz-123" { + t.Errorf("Error name = %q, want %q", notFoundErr.Name, "nonexistent-library-xyz-123") + } + } +} + +func TestFindLibraryFile_NotFound(t *testing.T) { + _, err := FindLibraryFile("libnonexistent-xyz-123.so") + if err == nil { + t.Error("Expected error for nonexistent library file") + } +} + +// Integration tests - these depend on system state +// They're skipped if the required tools/libraries aren't available + +func TestFindLibraryPath_WithPkgConfig(t *testing.T) { + // Skip if pkg-config is not available + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Try to find a common library that's likely installed + commonLibs := []string{"glib-2.0", "zlib"} + + for _, lib := range commonLibs { + // Check if pkg-config knows about this library + cmd := exec.Command("pkg-config", "--exists", lib) + if cmd.Run() != nil { + continue + } + + t.Run(lib, func(t *testing.T) { + path, err := FindLibraryPath(lib) + if err != nil { + t.Errorf("FindLibraryPath(%q) failed: %v", lib, err) + return + } + + // Verify the path exists + if _, err := os.Stat(path); err != nil { + t.Errorf("Returned path %q does not exist", path) + } + }) + return // Only need to test one + } + + t.Skip("No common libraries found via pkg-config") +} + +func TestFindLibraryFile_Integration(t *testing.T) { + // Try to find libc which should exist on any Linux system + libcNames := []string{"libc.so.6", "libc.so"} + + for _, name := range libcNames { + path, err := FindLibraryFile(name) + if err == nil { + // Verify the path exists + if _, err := os.Stat(path); err != nil { + t.Errorf("Returned path %q does not exist", path) + } + return + } + } + + t.Skip("Could not find libc.so - unusual system configuration") +} + +func TestFindInCommonPaths(t *testing.T) { + // Create a temporary directory structure for testing + tmpDir := t.TempDir() + + // Create a fake library directory with a fake .so file + libDir := filepath.Join(tmpDir, "lib") + if err := os.MkdirAll(libDir, 0755); err != nil { + t.Fatal(err) + } + + // Create a fake library file + fakeLib := filepath.Join(libDir, "libfaketest.so.1") + if err := os.WriteFile(fakeLib, []byte{}, 0644); err != nil { + t.Fatal(err) + } + + // Temporarily add our test dir to defaultLibPaths + originalPaths := defaultLibPaths + defaultLibPaths = append([]string{libDir}, defaultLibPaths...) + defer func() { defaultLibPaths = originalPaths }() + + // Now test finding it + path, err := findInCommonPaths("faketest") + if err != nil { + t.Errorf("findInCommonPaths(\"faketest\") failed: %v", err) + return + } + + if path != libDir { + t.Errorf("findInCommonPaths(\"faketest\") = %q, want %q", path, libDir) + } +} + +func TestFindWithLdconfig(t *testing.T) { + // Skip if ldconfig is not available + if _, err := exec.LookPath("ldconfig"); err != nil { + t.Skip("ldconfig not available") + } + + // Check if we can run ldconfig -p + cmd := exec.Command("ldconfig", "-p") + output, err := cmd.Output() + if err != nil { + t.Skip("ldconfig -p failed") + } + + // Find any library from the output to test with + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "=>") && strings.Contains(line, "libc.so") { + // We found libc, try to find it + path, err := findWithLdconfig("glib-2.0") // Common library + if err == nil { + if _, statErr := os.Stat(path); statErr != nil { + t.Errorf("Returned path %q does not exist", path) + } + return + } + // If glib not found, that's okay - just means it's not installed + break + } + } +} + +func TestFindLibraryPathWithOptions_IncludeCurrentDir(t *testing.T) { + // Create a temporary directory and change to it + tmpDir := t.TempDir() + + // Create a fake library file in the temp dir + fakeLib := filepath.Join(tmpDir, "libcwdtest.so.1") + if err := os.WriteFile(fakeLib, []byte{}, 0644); err != nil { + t.Fatal(err) + } + + // Save current directory + origDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(origDir) + + // Change to temp directory + if err := os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + + // Without IncludeCurrentDir, should not find it + _, err = FindLibraryPathWithOptions("cwdtest", FindOptions{IncludeCurrentDir: false}) + if err == nil { + t.Error("Expected error without IncludeCurrentDir") + } + + // With IncludeCurrentDir, should find it + path, err := FindLibraryPathWithOptions("cwdtest", FindOptions{IncludeCurrentDir: true}) + if err != nil { + t.Errorf("FindLibraryPathWithOptions with IncludeCurrentDir failed: %v", err) + return + } + + if path != tmpDir { + t.Errorf("Expected path %q, got %q", tmpDir, path) + } +} + +func TestFindLibraryPathWithOptions_ExtraPaths(t *testing.T) { + // Create a temporary directory with a fake library + tmpDir := t.TempDir() + + fakeLib := filepath.Join(tmpDir, "libextratest.so.1") + if err := os.WriteFile(fakeLib, []byte{}, 0644); err != nil { + t.Fatal(err) + } + + // Should find it with ExtraPaths + path, err := FindLibraryPathWithOptions("extratest", FindOptions{ + ExtraPaths: []string{tmpDir}, + }) + if err != nil { + t.Errorf("FindLibraryPathWithOptions with ExtraPaths failed: %v", err) + return + } + + if path != tmpDir { + t.Errorf("Expected path %q, got %q", tmpDir, path) + } +} + +func TestDefaultLibPaths_ContainsDistros(t *testing.T) { + // Verify that paths for various distros are included + expectedPaths := map[string][]string{ + "Debian/Ubuntu": {"/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu"}, + "Fedora/RHEL": {"/usr/lib64/gtk-3.0", "/usr/lib64/gtk-4.0"}, + "Arch": {"/usr/lib/webkit2gtk-4.0", "/usr/lib/webkit2gtk-4.1"}, + "openSUSE": {"/usr/lib64/gcc/x86_64-suse-linux"}, + "Local": {"/usr/local/lib", "/usr/local/lib64"}, + } + + for distro, paths := range expectedPaths { + for _, path := range paths { + found := false + for _, defaultPath := range defaultLibPaths { + if defaultPath == path { + found = true + break + } + } + if !found { + t.Errorf("Missing %s path: %s", distro, path) + } + } + } +} + +func TestGetFlatpakLibPaths(t *testing.T) { + // This test just ensures the function doesn't panic + // Actual paths depend on system state + paths := getFlatpakLibPaths() + t.Logf("Found %d Flatpak lib paths", len(paths)) + for _, p := range paths { + t.Logf(" %s", p) + } +} + +func TestGetSnapLibPaths(t *testing.T) { + // This test just ensures the function doesn't panic + // Actual paths depend on system state + paths := getSnapLibPaths() + t.Logf("Found %d Snap lib paths", len(paths)) + for _, p := range paths { + t.Logf(" %s", p) + } +} + +func TestGetNixLibPaths(t *testing.T) { + // This test just ensures the function doesn't panic + paths := getNixLibPaths() + t.Logf("Found %d Nix lib paths", len(paths)) + for _, p := range paths { + t.Logf(" %s", p) + } +} + +func TestGetAllLibPaths_IncludesDynamicPaths(t *testing.T) { + paths := GetAllLibPaths() + + // Should have at least the default paths + if len(paths) < len(defaultLibPaths) { + t.Errorf("GetAllLibPaths returned fewer paths (%d) than defaultLibPaths (%d)", + len(paths), len(defaultLibPaths)) + } + + // Log all paths for debugging + t.Logf("Total paths: %d", len(paths)) +} + +func TestGetAllLibPaths_DoesNotIncludeCurrentDir(t *testing.T) { + paths := GetAllLibPaths() + + for _, p := range paths { + if p == "." { + t.Error("GetAllLibPaths should not include '.' for security reasons") + } + } +} + +func TestInvalidateCache(t *testing.T) { + // First call populates cache + paths1 := GetAllLibPaths() + + // Invalidate and call again + InvalidateCache() + paths2 := GetAllLibPaths() + + // Should get same results (assuming no system changes) + if len(paths1) != len(paths2) { + t.Logf("Path counts differ after invalidation: %d vs %d", len(paths1), len(paths2)) + // This is not necessarily an error, just informational + } + + // Verify cache is working by checking getFlatpakLibPaths is fast + // (would be slow if cache wasn't working) + for i := 0; i < 100; i++ { + _ = getFlatpakLibPaths() + } +} + +func TestFindLibraryPath_ParallelConsistency(t *testing.T) { + // Skip if pkg-config is not available + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Check if glib-2.0 is available + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + t.Skip("glib-2.0 not installed") + } + + // Run parallel and sequential versions multiple times + // to ensure they return consistent results + for i := 0; i < 10; i++ { + parallelPath, parallelErr := FindLibraryPath("glib-2.0") + seqPath, seqErr := FindLibraryPathSequential("glib-2.0") + + if parallelErr != nil && seqErr == nil { + t.Errorf("Parallel failed but sequential succeeded: %v", parallelErr) + } + if parallelErr == nil && seqErr != nil { + t.Errorf("Sequential failed but parallel succeeded: %v", seqErr) + } + + // Both should find the library (path might differ if found by different methods) + if parallelErr != nil { + t.Errorf("Iteration %d: parallel search failed: %v", i, parallelErr) + } + if seqErr != nil { + t.Errorf("Iteration %d: sequential search failed: %v", i, seqErr) + } + + // Log paths for debugging + t.Logf("Iteration %d: parallel=%s, sequential=%s", i, parallelPath, seqPath) + } +} + +func TestFindLibraryPath_ParallelNotFound(t *testing.T) { + // Both parallel and sequential should return the same error for non-existent libs + _, parallelErr := FindLibraryPath("nonexistent-library-xyz-123") + _, seqErr := FindLibraryPathSequential("nonexistent-library-xyz-123") + + if parallelErr == nil { + t.Error("Parallel search should fail for nonexistent library") + } + if seqErr == nil { + t.Error("Sequential search should fail for nonexistent library") + } + + // Both should return LibraryNotFoundError + if _, ok := parallelErr.(*LibraryNotFoundError); !ok { + t.Errorf("Parallel: expected LibraryNotFoundError, got %T", parallelErr) + } + if _, ok := seqErr.(*LibraryNotFoundError); !ok { + t.Errorf("Sequential: expected LibraryNotFoundError, got %T", seqErr) + } +} + +// Benchmarks + +// BenchmarkFindLibraryPath benchmarks finding a library via the full search chain. +func BenchmarkFindLibraryPath(b *testing.B) { + // Test with glib-2.0 which is commonly installed + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPath("glib-2.0") + } +} + +// BenchmarkFindLibraryPath_NotFound benchmarks the worst case (library not found). +func BenchmarkFindLibraryPath_NotFound(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPath("nonexistent-library-xyz-123") + } +} + +// BenchmarkFindLibraryFile benchmarks finding a specific library file. +func BenchmarkFindLibraryFile(b *testing.B) { + // libc.so.6 should exist on any Linux system + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryFile("libc.so.6") + } +} + +// BenchmarkGetAllLibPaths benchmarks collecting all library paths. +func BenchmarkGetAllLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = GetAllLibPaths() + } +} + +// BenchmarkFindWithPkgConfig benchmarks pkg-config lookup directly. +func BenchmarkFindWithPkgConfig(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = findWithPkgConfig("glib-2.0") + } +} + +// BenchmarkFindWithLdconfig benchmarks ldconfig lookup directly. +func BenchmarkFindWithLdconfig(b *testing.B) { + if _, err := exec.LookPath("ldconfig"); err != nil { + b.Skip("ldconfig not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = findWithLdconfig("glib-2.0") + } +} + +// BenchmarkFindInCommonPaths benchmarks filesystem scanning. +func BenchmarkFindInCommonPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = findInCommonPaths("glib-2.0") + } +} + +// BenchmarkGetFlatpakLibPaths benchmarks Flatpak path discovery. +func BenchmarkGetFlatpakLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = getFlatpakLibPaths() + } +} + +// BenchmarkGetSnapLibPaths benchmarks Snap path discovery. +func BenchmarkGetSnapLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = getSnapLibPaths() + } +} + +// BenchmarkGetNixLibPaths benchmarks Nix path discovery. +func BenchmarkGetNixLibPaths(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = getNixLibPaths() + } +} + +// BenchmarkPkgConfigToLibName benchmarks the name conversion function. +func BenchmarkPkgConfigToLibName(b *testing.B) { + names := []string{"gtk+-3.0", "webkit2gtk-4.1", "glib-2.0", "cairo", "libsoup-3.0"} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, name := range names { + _ = pkgConfigToLibName(name) + } + } +} + +// BenchmarkFindLibraryPathSequential benchmarks the sequential search. +func BenchmarkFindLibraryPathSequential(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPathSequential("glib-2.0") + } +} + +// BenchmarkFindLibraryPathSequential_NotFound benchmarks the sequential worst case. +func BenchmarkFindLibraryPathSequential_NotFound(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPathSequential("nonexistent-library-xyz-123") + } +} + +// BenchmarkFindLibraryPathParallel explicitly tests parallel performance. +func BenchmarkFindLibraryPathParallel(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + cmd := exec.Command("pkg-config", "--exists", "glib-2.0") + if cmd.Run() != nil { + b.Skip("glib-2.0 not installed") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindLibraryPath("glib-2.0") + } +} + +// Tests for multi-library search functions + +func TestFindFirstLibrary(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Test with a mix of existing and non-existing libraries + match, err := FindFirstLibrary("nonexistent-xyz", "glib-2.0", "also-nonexistent") + if err != nil { + t.Skipf("glib-2.0 not installed: %v", err) + } + + if match.Name != "glib-2.0" { + t.Errorf("Expected glib-2.0, got %s", match.Name) + } + if match.Path == "" { + t.Error("Expected non-empty path") + } +} + +func TestFindFirstLibrary_AllNotFound(t *testing.T) { + _, err := FindFirstLibrary("nonexistent-1", "nonexistent-2", "nonexistent-3") + if err == nil { + t.Error("Expected error for all non-existent libraries") + } +} + +func TestFindFirstLibrary_Empty(t *testing.T) { + _, err := FindFirstLibrary() + if err == nil { + t.Error("Expected error for empty library list") + } +} + +func TestFindFirstLibraryOrdered(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // glib-2.0 should be found, and since it's first, it should be returned + match, err := FindFirstLibraryOrdered("glib-2.0", "nonexistent-xyz") + if err != nil { + t.Skipf("glib-2.0 not installed: %v", err) + } + + if match.Name != "glib-2.0" { + t.Errorf("Expected glib-2.0, got %s", match.Name) + } +} + +func TestFindFirstLibraryOrdered_PreferFirst(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + // Check what GTK versions are available + gtk4Available := exec.Command("pkg-config", "--exists", "gtk4").Run() == nil + gtk3Available := exec.Command("pkg-config", "--exists", "gtk+-3.0").Run() == nil + + if !gtk4Available && !gtk3Available { + t.Skip("Neither GTK3 nor GTK4 installed") + } + + // If both available, test that order is respected + if gtk4Available && gtk3Available { + match, err := FindFirstLibraryOrdered("gtk4", "gtk+-3.0") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if match.Name != "gtk4" { + t.Errorf("Expected gtk4 (first in order), got %s", match.Name) + } + + // Reverse order + match, err = FindFirstLibraryOrdered("gtk+-3.0", "gtk4") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if match.Name != "gtk+-3.0" { + t.Errorf("Expected gtk+-3.0 (first in order), got %s", match.Name) + } + } +} + +func TestFindAllLibraries(t *testing.T) { + if _, err := exec.LookPath("pkg-config"); err != nil { + t.Skip("pkg-config not available") + } + + matches := FindAllLibraries("glib-2.0", "nonexistent-xyz", "zlib") + + // Should find at least glib-2.0 on most systems + if len(matches) == 0 { + t.Skip("No common libraries found") + } + + t.Logf("Found %d libraries:", len(matches)) + for _, m := range matches { + t.Logf(" %s at %s", m.Name, m.Path) + } + + // Verify no duplicates and no nonexistent library + seen := make(map[string]bool) + for _, m := range matches { + if m.Name == "nonexistent-xyz" { + t.Error("Should not have found nonexistent library") + } + if seen[m.Name] { + t.Errorf("Duplicate match for %s", m.Name) + } + seen[m.Name] = true + } +} + +func TestFindAllLibraries_Empty(t *testing.T) { + matches := FindAllLibraries() + if len(matches) != 0 { + t.Error("Expected empty result for empty input") + } +} + +func TestFindAllLibraries_AllNotFound(t *testing.T) { + matches := FindAllLibraries("nonexistent-1", "nonexistent-2") + if len(matches) != 0 { + t.Errorf("Expected empty result, got %d matches", len(matches)) + } +} + +// Benchmarks for multi-library search + +func BenchmarkFindFirstLibrary(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindFirstLibrary("nonexistent-1", "glib-2.0", "nonexistent-2") + } +} + +func BenchmarkFindFirstLibraryOrdered(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindFirstLibraryOrdered("nonexistent-1", "glib-2.0", "nonexistent-2") + } +} + +func BenchmarkFindAllLibraries(b *testing.B) { + if _, err := exec.LookPath("pkg-config"); err != nil { + b.Skip("pkg-config not available") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = FindAllLibraries("glib-2.0", "zlib", "nonexistent-xyz") + } +} diff --git a/v3/internal/libpath/libpath_other.go b/v3/internal/libpath/libpath_other.go new file mode 100644 index 000000000..d1c7bc77d --- /dev/null +++ b/v3/internal/libpath/libpath_other.go @@ -0,0 +1,73 @@ +//go:build !linux + +package libpath + +// FindLibraryPath is a stub for non-Linux platforms. +func FindLibraryPath(libName string) (string, error) { + return "", &LibraryNotFoundError{Name: libName} +} + +// FindLibraryFile is a stub for non-Linux platforms. +func FindLibraryFile(fileName string) (string, error) { + return "", &LibraryNotFoundError{Name: fileName} +} + +// GetAllLibPaths returns an empty slice on non-Linux platforms. +func GetAllLibPaths() []string { + return nil +} + +// InvalidateCache is a no-op on non-Linux platforms. +func InvalidateCache() {} + +// FindOptions controls library search behavior. +type FindOptions struct { + IncludeCurrentDir bool + ExtraPaths []string +} + +// FindLibraryPathWithOptions is a stub for non-Linux platforms. +func FindLibraryPathWithOptions(libName string, opts FindOptions) (string, error) { + return "", &LibraryNotFoundError{Name: libName} +} + +// LibraryNotFoundError is returned when a library cannot be found. +type LibraryNotFoundError struct { + Name string +} + +func (e *LibraryNotFoundError) Error() string { + return "library not found: " + e.Name +} + +// LibraryMatch holds information about a found library. +type LibraryMatch struct { + Name string + Path string +} + +// FindFirstLibrary is a stub for non-Linux platforms. +func FindFirstLibrary(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + return nil, &LibraryNotFoundError{Name: libNames[0]} +} + +// FindFirstLibraryOrdered is a stub for non-Linux platforms. +func FindFirstLibraryOrdered(libNames ...string) (*LibraryMatch, error) { + if len(libNames) == 0 { + return nil, &LibraryNotFoundError{Name: "no libraries specified"} + } + return nil, &LibraryNotFoundError{Name: libNames[0]} +} + +// FindAllLibraries is a stub for non-Linux platforms. +func FindAllLibraries(libNames ...string) []LibraryMatch { + return nil +} + +// FindLibraryPathSequential is a stub for non-Linux platforms. +func FindLibraryPathSequential(libName string) (string, error) { + return "", &LibraryNotFoundError{Name: libName} +} diff --git a/v3/internal/libpath/nix_linux.go b/v3/internal/libpath/nix_linux.go new file mode 100644 index 000000000..74d8487a9 --- /dev/null +++ b/v3/internal/libpath/nix_linux.go @@ -0,0 +1,28 @@ +//go:build linux + +package libpath + +import "os" + +// getNixLibPaths returns cached library paths for Nix/NixOS installations. +func getNixLibPaths() []string { + return cache.getNix() +} + +// discoverNixLibPaths scans for Nix library paths. +func discoverNixLibPaths() []string { + var paths []string + + nixProfileLib := os.ExpandEnv("$HOME/.nix-profile/lib") + if _, err := os.Stat(nixProfileLib); err == nil { + paths = append(paths, nixProfileLib) + } + + // System Nix store - packages expose libs through profiles + nixStoreLib := "/run/current-system/sw/lib" + if _, err := os.Stat(nixStoreLib); err == nil { + paths = append(paths, nixStoreLib) + } + + return paths +} diff --git a/v3/internal/libpath/snap_linux.go b/v3/internal/libpath/snap_linux.go new file mode 100644 index 000000000..99def76ac --- /dev/null +++ b/v3/internal/libpath/snap_linux.go @@ -0,0 +1,42 @@ +//go:build linux + +package libpath + +import ( + "os" + "path/filepath" +) + +// getSnapLibPaths returns cached library paths from installed Snap packages. +func getSnapLibPaths() []string { + return cache.getSnap() +} + +// discoverSnapLibPaths scans for Snap package library directories. +// Scans /snap/*/current/usr/lib* directories. +func discoverSnapLibPaths() []string { + var paths []string + + snapDir := "/snap" + if _, err := os.Stat(snapDir); err != nil { + return paths + } + + // Find all snap packages with lib directories + patterns := []string{ + filepath.Join(snapDir, "*", "current", "usr", "lib"), + filepath.Join(snapDir, "*", "current", "usr", "lib64"), + filepath.Join(snapDir, "*", "current", "usr", "lib", "*-linux-gnu"), + filepath.Join(snapDir, "*", "current", "lib"), + filepath.Join(snapDir, "*", "current", "lib", "*-linux-gnu"), + } + + for _, pattern := range patterns { + matches, err := filepath.Glob(pattern) + if err == nil { + paths = append(paths, matches...) + } + } + + return paths +} diff --git a/v3/internal/runtime/Taskfile.yaml b/v3/internal/runtime/Taskfile.yaml index 2bc433213..4a2a2ccf3 100644 --- a/v3/internal/runtime/Taskfile.yaml +++ b/v3/internal/runtime/Taskfile.yaml @@ -35,14 +35,38 @@ tasks: cmds: - npx esbuild@latest desktop/@wailsio/runtime/src/index.ts --inject:desktop/compiled/main.js --format=esm --target=safari11 --bundle --ignore-annotations --tree-shaking=true --minify --outfile=../assetserver/bundledassets/runtime.js --define:DEBUG=false --drop:console - build:all: + build:docs: + internal: true + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs + + build:docs:md: + internal: true + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs:md + + build:runtime: internal: true deps: - build:debug - build:production cmds: - - cmd: echo "Build Complete." + - cmd: echo "Runtime build complete." + + build:all: + internal: true + cmds: + - task: generate:events + - task: build:docs + - task: build:runtime + - echo "Build Complete." build: deps: @@ -50,8 +74,39 @@ tasks: cmds: - task: build:all + docs: + summary: Generate TypeDoc documentation for the runtime + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs + - echo "Documentation generated at desktop/@wailsio/runtime/docs/" + + docs:md: + summary: Generate markdown documentation for the runtime + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run build:docs:md + - echo "Markdown documentation generated" + generate:events: dir: ../../tasks/events cmds: - go run generate.go - go fmt ../../pkg/events/events.go + + clean: + summary: Clean built artifacts and documentation + dir: desktop/@wailsio/runtime + cmds: + - npm run clean + - echo "Cleaned runtime artifacts" + + generate: + summary: Generate events only (use runtime:build to rebuild everything) + cmds: + - task: generate:events + - echo "Events generated. Run 'wails3 task runtime:build' to rebuild runtime with updated documentation" diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js index c6854a4ae..286659795 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js @@ -1 +1 @@ -window.hierarchyData = "eJylj8EKgzAQRP9lztGi2KC59tpD70Uk1RWDa4QkPYn/XrRUerCl0NMuO8zOmwluHIOHusosEUWalwKOWqY6mNF6qAkyS5Zh9UBQOGlbE7O+MV3cOBhPZ9MTBHpjG6j0KAXujqFgbCDX6pr8Yd8Ud2FgCNSsvYdC8E20fIk25yJ2hhtHdiGUWTkLSJl95dlYkjR/sawRuyAfIZ6HWaBI87e830tX6241V/G/pef5ASffjm8=" \ No newline at end of file +window.hierarchyData = "eJylj8EKwjAQRP9lzrFiSkKbq1cP3kVKbFca3CaQxJP03yWK4kFF8LTLDrPz5oIYQk4wO10r0Uq5F4h0ZOqzCz7BXKBrVYa3E8FgbX1PzPbAtI1hcok27kQQODk/wEilBc6RYeB8pni0PaXle1M15okh0LNNCQY5DYvyZfF0FnF0PETyhVA1+1lAq+Yrz5NlJZsHyy3iLchHiPthFmilfMn7vXR3273lrvq39DxfASprjnI=" \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js index 42de49abe..bbb7f2347 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js @@ -1 +1 @@ -window.navigationData = "eJylmW1v5CYQx7+LXydtk+bSNqoqJdk8Sdluur5qX5xOJ9ZmExoMFuBN9qp+9woDNrbx4N28SrTzn98MmGEw/vJvovC7Si6S3wlTWDBE/0iOkhKpl+QiKXheUSx//OZs3354UQVNjpJXwvLk4vQoyV4IzQVmycWXBrUiLOdvLSajSMouxki6tJPTX/87aiA3QnCxKBXhTLaomrBBWZfma3sZfjo/MvGTi0TJ/JjIY/xu/BIv2oPCAq0pjkVyusOiPHFJdJKxKE43iOKzBC+IxLcV3RBKcb7EsqIqSg56HTgaw3okr9Fp86QfirXE/+BM7TnYrtOH4q+IelliyekWi+iqDPkcFj0l36NTrDXQerl8Q0ThvMWoXdklWEUP8tNvv5x8Op2U5jViGaZU14cdu/0FCyAs4BVMBQx4846zSvH94jmn/cOZpbVnOOd0SDizjPYMZ5xi4Z6QUARRgG0Vhy8Qm1GKlQrsWMN4AfnhwZcY5ZzRHRDQST4SJOMCKjIjODxAupMKFzdbzNSfqMBApJ4y/Pi/+jtEWVKSoW6DckcAz9glnYXPAPck95LbVCyru3OHozVd2PmZl89fFVExhNYAiPTFP4eEEVozQPjzciX4m/SLzs2JNUyaj0WJ2d/Lx1AyDmMlYC7XiNJhIvrXSVksK6ZIgevz0vB8VmN8CXQ+0+LB8cwswprj2WM7z9XuYRaamJqjjcATvtp1y2DgPVz7Hf/uhPa8h/PafxyUlGuO/IpvnokzTXowKVaf9X+hTBqQFQGjiTOCAH9IM4Iof5bDAVnDpOFcVUqNHHMdxkigE8stofiWUOWXXgDUyiDYHEuJnrFxgl4rHDbkAAXQ1atTmR4h6AGFSNEW7xci6AGF6O0M7QpyxMC+0FmBD2zDIX9tB9zdnEAIpwFbB5bdPjbEOA3UPuz0QRinATArJBhhzxDFSsDKrFt5oDDN75PqcoUIlbV+uPtbTCsBd/9KKl70M/JWoKX5OmjdtVH1nrtG2Wu/qQzSc8JYd2k9ZkihKFaLpiNDZ7ABcvz45e3c2rUlbZEg+gzf0Gp7F/Kz739ThA9L1l2bobLbbADnxWYD+16Ge2jrfhnoox4hWKfOGyrQBcuCxdn4ZlBdLti8ooqU4QJvGE4EFuctRaGmWf88qTTvsNLiUCYGYgVgFg+LdJjDwyKdlMEMb4k/nb6/sU3CdDuAtyV4nGEX6G4HY12khzg+AWfjHpWKZIGnojHWOG1IRYkylaodHRS7j/JksXI30rEhdoGBMfqjTDOBMQuM0hqmvRjgLHyd5iBaAJ5Nah2IMH9ByNg9V4OIXHLdYTWyFzmCUQCbwh1W15UQnRYZxFgVjHoSpEBiF0FZFbiYzet84CnXv096yDdsSwRnBWZqtEgtricFD75pDGYUEOMalWhNKFHEb4LeXBmQLwPm3UsegHkqgHWPWE7xE0Vqw0VRn6UFLwFs2AGIQNiWvwY7kCUaAXTmlpfz2fkZgLAKmLGcw4TlPOYfy0ErQMYMidc5D98cNRgngkl4XQWbaYvRCpDxSFj1DjJqBciYowwkzFEG+psPZVBRNBpw+1jNH4d7x2r+OHHj6H4ba3PQBGMFRrHElKN8zN1YI7de+lJ79NaqMcL3Vf2L8TGUr5lA1DfT+kKfcAZmOBDul+3oZ7awFN5t+x7x71oRHyicvVH8LBCTJRfhc0ZfBAEzSnRf8lZU+7rkbMCrEl/rp6BfyoIvXJ4ZgPS/cbf+oU/bHddnrAJz0RaFbweqSkYwEsJ8/R+gZOex" \ No newline at end of file +window.navigationData = "eJylmVFv2zYQx7+LnpNtydJsC4oBSeykAeI5s1r4oSgKWqZjLhQpkJQTd9h3HyiSEiVRR9l5SuD73++OFI9HUV//TRR+U8lV8pEwhQVD9M/kJCmQ2iZXSc7XJcXy5+/O9v2nrcppcpK8ELZOrs5PkmxL6Fpgllx9rVFLwtb8tcFkFEnZxhhJm3Z2/vt/JzVkKgQX80IRzmSDqggblLVpvraT4YfLExM/uUqUXJ8SeYrfjF/iRXtQWKAVxbFITndclCcuiU4yFsXpelF8luA5kfiupBtCKV4vsCypipKDXkeOxrAeyUt02jzpu2It8D84UwcOtu30rvhLorYLLDndYRFdlSGf46Kn5Ed0irUGWi/Xr4govG4wal+0CVbRgfzyx29nH85HpXmLWIYp1fVhx25/wQIIC3gFUwEDTt9wVip+WDzndHg4s7QODOecjglnltGB4YxTLNwTEoogCrCt4vgFYjNKsVKBHasfLyA/PvgCozVndA8EdJL3BMm4gIrMCI4PkO6lwvl0h5n6C+UYiNRRhh//N3+HKApKMtRuUO4I4BnbpIvwGeATWXvJbUqWVd25xdGaNuzywsvn75KoGEJrAES69c8hYYTW9BD+vNwI/ir9onNzYg2j5mNeYPZl8RhKxmGsBMzlFlHaT0T/OiqLRckUyXF1XuqfzyqML4HOZ1rcO56ZRVhxPHts57nZP0xCE1NxtBF4wjf7dhn0vPtrv+XfntCOd39eu4+DkmLFkV/x9TNxplEPJsXqs/4vlEkNsiJgNHFGEOAPaUIQ5c+yPyBrGDWcm1KpgWOuwxgJdGK5IxTfEar80guAGhkEm2Ep0TM2TtBrhcOGHKAAunp1KuMjBD2gECna4cNCBD2gEJ2doVlBjhjYF1or8IFtOOSv7YC7mxMI4TRg68Cy3cf6GKeB2oedPgjjNABmiQQj7BmiWAlYmVUrDxSm+X1UXS4RobLS93d/i2kk4O5fSsXzbkbeCrQ0Xwetuyaq3nNXKHvpNpVeek4Y6y6NxwQpFMVq0Xhk6AzWQw4fv7ydW7s2pB0SRJ/ha1plb0N+9f2nefiwZN21GSq7zQZwnm82sO91uIc27teBPuoRgnXqvKECnbMsWJy1bwbV5ZzNSqpIES7wmuFEYHHeURRqmtXPo0rzHistDmViIFYAZvEwT/s5PMzTURlM8I740+n7G9soTLsDeFuCx+l3gfZ2MNRFOojTM3A2PqFCkSzwVDTGGscNKS9QplK1p71i91GeLFbuRjo0xDYwMEZ/lGkmMGaBUVrDuBcDnIWv0xxEC8CzSaUDEeYvCBm656oRkUuue6wG9iJHMApgU7jH6rYUotUigxirglFPguRI7CMoqwIXs3mdDzzl6vdRD3nKdkRwlmOmBovU4jpS8OCbxmBGATFuUYFWhBJF/CbozZUB+TJg3r3kAZinAliE7fhLsD9YjBFAJ2J5PZtcXgAIq4AZixlMWMxi/rEctAJkTJB4mfHwvU6NcSKYhFdlsNU1GK0AGY+ElW8go1KAjBnKQMIMZaC/+YwFLdlaAxb3cvbYr+zl7HFkWbe/XDU5aIKxAqNYYMrResjdWCN3UvrKefBOqTbCt0nda+shlK8ZQdT3xvq6nXAGZtgTHpbt4EewsBTeC7se8a9OER8onL3v+ywQkwUX4VNAVwQBM0p01/BWVPMy42zAiwxf6aegX5mCr0OeGYB0v0A3/qEPzy3XZ6wCc9EUhW8HqkpGMBLCfPsftSXExw==" \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js index 5b4416f34..db0533722 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js @@ -1 +1 @@ -window.searchData = "eJy1XW2P2ziS/i/ur71ZF/UeHA7IJDO7ASaXbDqzubvGYKG21d2ayJJPkruTDea/H0hKdpEsSSXL+ZSgTVaVqKeKxXpI8fuqrp6b1cvb76svebldvfSvV2W6y1YvV6/2+yLfpG1elavr1aEuVi9Xu2p7KLLmr+i3F4/trlhdrzZF2jRZs3q5Wv153UsLT+L+nm+zo5z7Q7mRnU1Jsgkh7nq1T+usbC2bSC03j9XzhBbZZKGWfxzydkKLbHKOlpOSn+SbyWpn7Lu/c8f9/T4rf/v4K2FuL6hrMW5tbw1l6eu0KBwz5R+5NhoCTgYqEQNykGmqNyn3p2//Jf8zIFn/er7st2+GJb99M0uuWCcRBMIYkPd7JfGoo/22z9CYdD/PUgMiPqr4eCjbfJf9XNfVCWOdFK0Ft5ilJoDTk2yqsmnrw6Zla7kyuwxrNLrSwCzy/V2V1lsXnf0vXIjeZO2n7Cvl9SdRXZuJsTraROqZVHKehpOCN3laVA+NMyLd37nj8ba8rwg7eyny53EreztI6Z/TuszLhxEFXYsFOkzkuxoYuB+V/49D1hiTp6uib7JAiwzev+QFFeR6LX2TBVpu0qdsQkvfZK4WEYTOw+hWdgDMyzar79NN5j6a0WGuBbAWeCoqXz9WVZO9yetMhqA8O9OCq01abpSorSFq1Cxa3qSxssdiM+87IRc2sM7S9kKjqUT9sNGUieHf8+1WdznXzuaxen5UUn7AeH7Mmqp4yppXRZ42Z5tYd1LSo5QLmviqKKrn5t2haPN9kd1kRbYxouA8U1MlbddJa5C0C5os1x0/f22zsjnf0Md8m2VIxmWdSA+jBudSSzdpqcdRo/QHGf1J+mojm39IN1/Sh6x51SwPAq2SKh1r30lNmx8WDzSS37ePWS37qPx7EYwrKeoeibqgsb/kRZvV59p3f+x9SQjkLcoYZr7oru8FzXmXNU36cK5Bu2PvC5r006Ftq9JI+edZdacEtFrABQ3rPfXbmXZtUf9LmpW16eYx255r1an7MqNwztrnveycleywKGeV0kpc6JilVTp/3318WOhHvVAiPWLgWYn0ecZOZn4cM3mJ32wD5yTSE2bOTqRnGTsjkR6xc24iPcvEGYn0iIlzE+lZJrKz0hED52WlcxE5OysdR+V5WeksoxdkpSO2L81KZz3C3Kx0xO6zstJZxjKy0vGJiZWVzoPAVFY69qJZWekscxhZ6YhB3Kx0lkm8rHTEqhlZ6SzDWFnpiF38rHSeWYysdMwqblY6ZRTOSjtYsZNSqv2inHTSzQY1Mr2MfMLznWzYHK6PzTFIu9h5b6Rzr+mwOMcgDoaHLWJDeMIkjGA9RGP26BaLUPprepcVTB1XRdd4/BE7uwcUvm1ep+WGrzNvNn37ZWrfZPfpoRiN6Kbe7bHDTMX4JcpwpXOAMcWnVote5pu82RepSflPa7va6m6sFTB6ngEjPqRtm9WjuLUN2B+7nKEc8ZlPWdm6vK7+M3u3SNlX7gmqrxN1ajP+ujqDBvSMyV8idzNq+WaJzff3I6Lv7xdJfkXugDkJfzW1C2ZM/s87cr9SJ13+OlM29vHXh6atdhb6EOw7LbjZXG3W7pjPaV40qpXh63qDTKfBbHMxhW/SNp1SKNtcTKHc6HKXbr5MKe3bzVSMdwSdhDk7dRx1M9VwdwTRelg7gpyuQxHaqJROKB6dFNgatxg0Exq7tgs1Nlm5zdjDe2w9X6t3eq+fjErEU1rn6R2agNTPM2GDCglFSuxZUn/lTm1/y1rZnoiDWkz3+7iJ2g7KwptNnWWla2P39xlW0lNBL0c3GLeyt2VIw4c636VoGU1q6Rot0/T6UNc4oJCaukZzNRksTP5vMunrtcjf58o3POpzvm0fWRqunrumo3pU+8HSb5Y/PJK5uqvtsW87Sx0eu4/ZZlSX/H3R2P03S/rV18mHUJYOKPkfnpLh2g9HySQKTop4KBhTNo2CkzYmCix1hgepJqOYU/8uQgLaMj2u4CofLiaYzYdUDS0DKWWj0zxP3c0mLbJfUiOrmdDayC736WhWw1M+6mBY5bSLjSsadTKsaNrNJoZzKp7jcdRtl6j7qTqUW3L1Qim861svUfnh8VuTb9Jinup91+siJnyu6i+v6izlKn+u6i+pbn+JJ5+rvn/2C5nxtrHToKmQ1OyPHZYo/li15qmqCb31qf1MtcifvjVttnOTU/Vnbm6al0/VF6q80onRv09MENoQ+kxB8yatv7yryINinY5Tm/P1vE736V1e5C2mfB1NuNX5un4un/K6KncDSbBWhRotGb3PebmVp/lGBq9rskTLr3l5+DqmQzVYouFduhmT/y7dLJH+6t2b0B+Trxos0vDx3aj8j+8WSp+wXzZY5IXZ3YFaJ59cUDY4X8Pf03JbZB+KtL2v6p2iNOtqP6yQbj9Tv3HO5MY4voTjr9aoG8zUYGYUdVpu8RGmUSVXd6fmY9q6PjPTa1fdWHbNUTSYXLuqxnNrjrJ/ZvXQxiJC39Ox9TyVGB8oHk8AxWq5BDGv6g29tBzWdJXqPqNPaj/MIAOMnZ6pf9t1uoQB72/maq9GEuCZqs94zVdVk+telzChj27nGLLv+i4zB4UR9Cr6TPHt+5vRNBFH932bb9xCqJTQ/TbuJlI9PTPt9umGSqOwaN1oUkPffoSE0ZJu2m+FzTG5+lSrc5RiB3zKEXmJh03/dO6o0Sd1kVzZ4i8wbX1nIR0zh4Br6TlDyxR5NKGMxR5ZHYe8dFdt6b0bpOK+9SKVjfLboQmX1Ku7jE68s5SPTMAj+qcmYqYJeXOT7w5FOueN502D+sxVjzgtvVwiSC39AzMifn73q+PWn99RH9wYWEJKnYQLSxn6x3GvkvpJyR+zokq3A5L1j2dKbrL2U52Wzb6qqYCNf+YOw8O4yAeuSIP60F+QcOUihNltxoWv/ROhvsGfNZmSeNW1pgfaMZSEa3X3R7ZRGx4oIhb9OvoMSOCmyGWisCWk9T+NisLbC/TGt6HvjRx/G5XH/7KIKY/5TZGTgWOPUGTbj9kf+iDy2OM47S74aLTsGY/pPsRA/N3X1S5v3B0To3acOp1rg7HDSLeWuPugBf+afyGnQ7ol32PbR5oCG5F71fUZfVDb9MGAMbQ3dMyAiR2i0yaMDvXnvH3sTkvRBzkmuozHhzGkzdTBBd3gsw2Y1R3yOtusU/8Lm/XHAG/PtKrrflGjqnLTe/bZlpkyFpvnhm/UayiqoSb84IFn+wl5V2NzPWHmsMabrG3xaDMUN8cui/WX3/iKy2HejK+xTjfsl3bVNV6q04rKU1p187+IC2iWKV91cLdjDqk+tV+quSmybM/W27de/HbNmDb5fufGsGG9ZoifVjw3pA9pfiZn1yn9sleNep1hxcx000U5P9P8US52Mb3oMAJP80hVY4Zz41xz0rPnJZnDz9sikmH6YdsReoGv9T4v06LgTxWn9udoPqr9D5V5lGnxn0715V/9T/+amN7NirCuoQ+dPEBCrYbjRRRk58DGfLMK1Q8f0jdYjppSY7ynv2Vu7KWVXD1kw3H3pKjrMqTvQ9XkxkaYCaX7U/uFml9nZUvsjx/Qu+lbL9VaVETKOaS0a7xQ55u8kX4i99O9ViE7zfE5nQkjtrq33GG3MXovtEpXLc80Kit/iE2/VJsD24b7rvFinfUms6qwk5rrTVb3XZbqPxRFY278nVKPeyzU/resvZml/CFrL6j7f6tqN0Pzv3XzhXqtTd0Taie2dfO14o+vT+nUbRdqfNsofyJWpgNq8+b+2GG57tm4zptLIltuUvuay8xkxvPvUJfl+vNytn7UZaH+/um5ynen9ks1dw/B1nxqv1CzkRFOaB3lZtka5Wfq3mRPn6qqYE9c1V4e9ntquz4LLfiYFWmbP2VzM7m663e5jG7eJHqp+fOjTEMMdnZS8anDct1tVc/R3DVfqPcma+e+7iZrL/emb+QByOf0W/O+/FS5NaphE1LVqyrbarhWNceKn9LNl4daHsR4XRXVgb2caLL27th103ddbs8vdbrLiqxhx4Ima+9RnwtYcJxC9VdBdJrP9kppz1GC/spNdpSw3Lp36VfjWNG0Nbv06+jholna83Ku9ry8mPZzA3WTtZeP1cqemYFTGXKx2HmTtTNfxsXehPlhrGnF41/DmqN5znqnudR6x7iQaUqpbrtU45wXe5G3+ql6eCiy+WuOVvW75LpDWzI3+9Z2XC4H78Zj7nTUDcflZqTfyvnv5FBe8n38Vs59F4fycu/ht3LuauhQXm49ZJ6Vn1A8flqerXNOiLtMfJMa37LRJXXmF8CV1PqeIIZH1I7xwrP0fswaPk8gNdddh6U1rPFTY1NVLdW7PykhP3e7rS+yDijT/aumyRv2kDRluk/7HksZlDonvtY0RJ90jc/RaV9PZxNwPYc9TIkN9zmLtpo26I/MILRZBuk+P8SgfsvjLIuOnX6IST9/zTaHmWPU9znHILTP0FmHoF1iSH/f7BwCFe9L+zpHz8hXMpCTHJ9gQCX5WYFBlcNk97hKzjeQMC89+Rmk6bF8HvoGDqWHP7GPfQ/pcfBLOKROPmuilf5+vcrLbfZ19fL7qj+18nIlXngvktX16j7Piq28InfVV22rXXecf1ttDuq/v3fN/qnih2ysW/91vbq+XV8H8MJLot9/v77tO6sf1B96Gae/qI6wur6Faz98IeLE6AhORzA6itX1raA6CqejMDp6q+tbjzLVczp6Rkd/dX3rUx19p6NvdAxW17cB1TFwOgZGx3B1fRte++JFInyjY+h0DI2O0VDHyOkYGR3j1fVtRJkaOx1jo2Oyur6NqY6J0zExASDxkFA9wcUOWOBR6CFxBwR+TACBhAXAtRe+iDxzlMDFEJggAgkNEKRmF0dgAgkkPIDEILhYAhNMICECJA7BxROYgAIJEyCxCC6mwAQVSKgACStwcQUmsEDCBUhogYstMMEFEjJAwgtcfIEJMCExAyTChIswYSJMSMwIEmHCRZiwQpSKUUB2JqKUiTAhMSNIhAkXYcJEmJCYER4ZHl2ECRNhQmJG+GRnF2HCRJiQmBEB2dlFmDARJiRmREh2dhEmTIQJiRkRkZ1dhAkTYUJiRpAIEy7ChIkwT2JGJJRmz0WYZyLMk5jx1mRnF2GeiTBPYsYj50/PRZhnTYRqJiTnUI+YC02EeRIzHokwz0WYZyLMk5jxfCqSeC7CPBNhnsSMF5CdXYR5JsI8iRmPRJjnIswzEeZJzHgkwjwXYZ6JME9ixotJs12EeSbCfIkZj4xhvosw30SYLzHjkzHMdxHmmwjzxWAk8V2E+SbCfG8wkvguwnwr3fIHI4lPZFwmwvxgMJL4LsJ8E2F+OBhJfBdhvokwPxoMBr6LMN9EmB8PBgPfRZhvIsxPBoOB7yLMNxEWrAeDQeAiLDARFsBgMAhchAUmwgIxGAwCF2GBibDAGwwGgYuwwERY4A8Gg8BFWGAl9cFgMAiIvN5EWBAOBoPARVhgIiyQmPHJtCJwERaYCAvi4dF2ERaYCAuS4dF2ERaYCAslZnwyoQldhIUmwkIYHLDQRVhoIiyUmPHJfDt0ERaaCAslZnwy3w5dhIUmwkK1ZiTz7dBFWGgiLJSY8UOys4uw0Fo6Ssz4ZL4dEqtHE2GhQhiZDYUuwkITYaHEjE/OVaGLsNBEWCgxE5BzVegiLDQRFknMBKRjRC7CIhNhkcRMQMIzchEWmQiLJGYCEmGRi7DIRFgkMROQCItchEUmwiKJmYBEWOQiLDIRFqnKBImwyEVYZCIskpgJSIRFLsIiq0AhMROQCIuIGoWJsEhiJiARFrkIi0yERRIzIYmwyEVYZCIslpgJSYTFLsJiE2ExDBYcYhdhsYmwWGImFNf++oUPYHZ2ERabCIslZkISnrGLsNhEWCwxE5LwjF2ExSbCYomZkIRn7CIsNhEWq/oXCc/YRVhsIixWNTC6luUiLLbKYBIzIQnPmKiEmQiLFcJIeMYuwmITYYnETETCM3ERlpgISyRmIhKeiYuwxERYIjETkQhLXIQlJsISiZnIIzu7CEtMhCUSM5FPdnYRlpgISyRmIhJhiYuwxERYIjETkSWtxEVYYiIskZiJIrKzi7DERFgSDw+Yi7DEKrYmwwNG1FvtgquCGF1NW1M1V6voulYoI1c4+je7v1V3XQ+HMv2b3d8qva4lemK66rsmiq9rq/q69gdfuv7N7m8VYNfB4HvXv9n9rRrsOhx0NP2b3d8qw64lkmLSy/Vvdn+rEruWYIrp2vWaqMWurWLsWtX66fL1mijHri38qSJ+TFewqZq/U/SXeIrpIjZZ9rfwp0r5MTmtAFX5t0v/qpof06VsqvhvV/9VQT+m/Y+q/9sEgKrpxzRlQlEANgegyvrJAGtC4M+mAVRlP6HxRxEBNhOgivsJjT+KC7DJAFXfT2j8UXSAxQeAKvEnNP4IRgAsSgBUlT+h8UeQAmCxAqAK/QmNP4IXAIsYAFXrT2j8EdQAWNwAqHJ/QuOPYAfAogdAVfwTOv4TBAFYDAGooj+sySoXECQBWCwBqMI/rGkEEkQBWEwBqOL/wAxGcAVgkQWg6v8DMxhBF4DFF4CiAGA9QB8SELQ4A/A08Un7AEEbgMUbgKICYE07AUEdgMUdgKIDYE17AUEfgMUfgKIEYE27AUEhgMUhgKIFhmBE0Ahg8QjgaRzSjkRQCWBxCeBpHNKeRNAJYPEJoCgCWNOhnKAUwOIUQNEEMMCAE7QCWLwCKKoAgHYlgloAi1sAXy9qaSgT9AJY/AIoymCAVSYYBrAoBlCswVB/AocWywCKOBjYBkDwDGARDaC4Axhi8wkYWmQDKP4ABhh9gm8Ai3AAxSHAAKtPcA5gkQ6geAQA2hUJ3gEs4gF8DUOyGA4E9wAW+QCBhiHtSQT/ABYBAYGGIe1JBAcBFgkBilcAQUcTgocAi4iAQO8IoT2J4CLAIiNA8QtAM/5A8BFgERKgOAYQNBIJTgIsUgKCwf1GQLASYNESoJgGEANbUwgcWtQEKLYBBA1kgp0Ai54AxTiAoIFMMBRgURQQroeDAUFSgMVSgCIeQNBTCkFUgMVUQDiyKYngKsAiKyDUKIxJGBN8BViEBYQahbQjEZwFWKQFKB4CaA4TCN4CLOICFBcBksckcEhwF2CRF6D4CPBoRyL4C7AIDAjjsUEkcGiRGKB4CaAJUSB4DLCIDFDcBHgkcw4ElwEWmQGKnwCPdiWCzwCL0ADFUYBHuxLBaYBFaoDiKcCjXYHgNcAiNiDSu+ToOYHgNsAiNyDSSKShTPAbYBEcoDgLoHdOAMFxgEVyQBSNQJngOcAiOkBxF0BzvUBwHWCRHaD4C6BZVyD4DrAID4jHkEhwHmCRHqBZjwFfIHgPsIgPUFwG0PQtENwHWOQHKD5jIKYS9AdY/AfE/shbJCgQsDgQiPWWTXpiJGgQsHgQiMORmEpQIWBxIaDoDaCJaCDoELD4EIg1EOlwQFAiYHEiEGsgDmwAJYBo8SKgqA6gSWkgqBGwuBFIYCRJJegRsPgRSDQQ6XhCUCRgcSSgaA8I6NdI0CRg8SSgqA+gGW4gqBKwuBJQ9AfQLDcQdAlYfAkkegMx7YwEZQIWZwKKBgGa7QaCNgGLNwFFhQDNeANBnYDFnYCiQ4BmvYGgT8DiT4TiQ4BmvgVBoAiLQBGKEAGa/RYEgyIsBkUoRgRoBlwQFIqwKBShKBGaQBIEhSIsCkUoSoTcaSYIBkVYDIpQjAjQJLwgKBRhUShCUSJAE/GC4FCExaGItd7LTm+vJkgUYZEoQpEiQHPqgmBRhMWiCMWKAM2rC4JGERaNIhQtAjS3LggeRVg8ilC8CF3FFASPIiweRegDFDQ/LwgiRVhEitCHKGiOXhBMirCYFKEPUtCLdkFQKcKiUoQ+TEET/YLgUoTFpQh9oIIm+wVBpgiLTBH6UAVN+AuCTREWmyK6gxW0KxB0irDoFKEPV0S0KxB8irDPV+gDFhHtCtQRC/uMhSJIIKJdgTpm4ZyzUEikNwEI8qiFhURFkUBEQ5k6bmGft1AcCUQ0lKkjF/aZC0WSAM3KC+rYhX3uQrMqEY1E6uiFffZCsyo0ry6o4xf2+QvFkgBNTAvqCIZ9BkMfwiCdmTqEYbEqQpEkNK8uCFJFWKSK0KQKTYwLglQRFqkiNKlCM+OCIFWERaoITarQ1LggSBVhkSpCkyo0Ny4IUkVYpIrQpApNjguCVBEWqSI0qUKz44IgVYRFqgjFkdAbYwTBqQiLUxGaU6HpdUFwKsLiVITmVGh+XRCcirA4FaE5FZpgFwSnIixORWhOhWbYBcGpCItTEYojoRlaQXAqwuJUhOZUaIpeEKSKsEgVoUkVmqMXBKsiLFZFaFaFJukFwaoIi1URmlWhWXpBsCrCYlWEZlVoml4QrIqwWBWhWRWapxcEqyIsVkVoVoVeLgqCVREWqyIUSUJvdBIEqSIsUkUojoTe6CQITkVYnIoIhncUCoJSERalIoLhTYWCYFSExaiIYHhfoSAIlf5v6pj8U1a32fatPi5/e6svvfm++ld3gF4WNpUWeZZeli5ffv/zz9OR+Zff/0Sn5uVvUlNaFNVzszsUbb4vsiYr9JVgWO7pa/jfVyKZIbVqH7Nafu5FfdsCizxdc/d95XlaSLDmij5eooOfPcbP7vNEyWtxsIwEywh4Mvb7It+k9pitT5KYj6Vuk0bGrH1kjCdYQu7kVy2NgY4BSVlHPCnH+9CxOViQ4Aqqnhv50ZWTHPRQvHekP0VpiPBOMkLmuCghxsD4CIEh7xVpKW32tTWgHCIoRx2Umc/2LTdAjCDMc7O7b6o9EoEiQMwSsbGCCHqaUHfpnklSq1yB1V7dC4rlYtfiDXd/0Qx2UAzCsLNPEhT6PzFv2LXgTN8eiaQL9OySheXLUh/pOV6Bhw3GIiMeWF2Rm9Pni5DsBMmWE/15srPjd4iw6AiL5gLJFl2oiyLxAGMQhHNeFhZbH78uhS0OsMVcpLqi+y9pYdE+Fs2bFVzR1rVRGCMCj8qs95ht6/4OTwLOOBsIYYZcM95KkugkKOYPwGNVNdk2r9Xbys0EQHh4ULUQn+8fSrTMLEyh+E0FnVBeGiSF1lnaDtqL8dUFHp+NYJ1WPebbbVZmX9usbKx0wUP5ggedeK7f7dO7vMhby+IEiZTbpXmyWisL8QP87png7K7sQWICHLgT5ripS47NGRLwe5BbbZiC9ndVWluScHrFlFNZ4T3AQhKmZ+DL25AsI7/SfeMOCeB3HgI6U5H/Ef2cx7T90LTVLpOXYhkoCVCQiHgg2aZtamQMyJWZ1myzu4OVXuL36nGltOnm0VwH4GWAl3TZWOevzBi4VRfTm+8Gv2efKSZPi+rBDPX4IZmD3YUjY6GC13le3D0kD31Dl0phIz0Maq6Zzb5InWzUR2+DObNlu9xIrwOU5TCXZPq79eYLxJlHwHuBA1dd4ZHCE2PCfL7yKa+rUv0NR2vs/jBbVF7eV6Y/4ZfoMR/YSSHwmpqXDxMRBs1DIU+InNjv88KaRHyE+pAHSynIwSSyh+nK2hazgIHG1+uiMxNXxxsW8TSL1wkxb1q7L1IzvIRISMwcIH2VGsY0jlEJ0xJ8LRqWhZ8qYb579AVwLAqvSNa8BaS6JxEPMg4CCS/ZfJBXl5gLZCQl5j3TQ9ZuDnVtuXyEXD7hQecha+VbN8zBK35e3HjI2n2d71JzUomQXyTsAaZeFS4XrtkP1tZp2eyr2nxnAs+8zLLK8b44bFSCjeK9+6HPVqOojRHVrzgEcx3zmO7bfGN63xoL9Hje139xFb1KNC8lXXBKumRIbpvR/2GOpr6SDtmILOxF8iWRqx9cF/C6DJdZ0DCXB5FRZeqzZ8Ebx3y3Tzcm+gCvoTyes2sxTfvNTkHwwPk8B3OmdaOc1D2e3y8FmEvd/gu8ZmzExQJmWMvLp8qs8MQ4YQAeKvLKcgEsw2OioEl3W7npH7kmnscELwrlTVobYQOX2OSRV7YQyxY8uIL5lhq3/okX48zyWd5s0/rLrjJ9OMZ+ArysOm+cVVuCX5VgG5Tdp4fCcDSc/oRcc45XVmL/WOMwz5ZE5xwhYGG8hCpvirw8fDVGCafizDRaXki5MYTgpYZgRg98qyV+LoGfi+th6IZKLAvX8dbMQNsQ6UeM/RW4/trku0OROqUU7LbMMlrePKubF8waGs4+gCeoSO8sr0WvLuQ5/+kaGjzUuLq55uFolzVN+mDI8dBAe11lJOgp2FlSdYWD4Hp85DgBD1+nq2/wE+OiK5M1kqHOnNoA+x+zfm8vGrHLxP18u+7/I/rcionb6k4W7aU8c+rD9WtgDlx1f2+stpEI5g6A6v7eWuLgNSCTSjcTOlwXjZhjUh45DZMdwXGBL8t4e7iixSThqrLfEmEIQsCOeHg073zF4Mb0HTCt2melXIcYyTOe8pjj04kZdGC86mLmK1KmvCECjxaKAjwZViaIw7jHxGHjlsJwEt9zN/LDCxx5+7SV2bIR3VDyFPLmhP3jtybfpAWxPwPPMMz5s5f2XNVf0jozivExDiHMkNmvcN2Rw3mrx0uATjdV4tU3fgVJvwD1ebNNd3USZmQN8pAHDIqdxxubIOzMAuY7/b9D1jgPalQZmGLMejeuK7IE1KlFVuACJzA5HUkyGAtp5LtMXksz0WbsxmCMeuoq5g2Me/UpDp64HMik74gCJY6d8uMJ3SzOQxS6EhVbhqtfzKVVty/AHDuctYRJP3a8AN8JbNIiTxuLyMYj13FHzP0Sx1us8eNixDM5rbpqnf1yMY71zNmwPpRtvstc2sJgvueIGqhB4lfB5Nma9ClzZmq8nuQ9Yi9mcKbGWxaZRaVmkxbZfWoT0DEetTXPpdxFc4RDKrNOr6WYVAYSw6RDGplnGU+EF0zMrWH2TeUIBbhcL5hrQfK2cSwT51nM0oB5cTgWhmdF5uwzdus3Fo2XnswkE1/hjUXhNR3XC9B93FgUzqCZaWqTteSEEhlZNG/pRd7PjWXiUMssj5lXbWNheF4RzCDUX52N5eCALXgBW+63tna9motqthR9nzZmmPG4M7cVN4NkES6xB2xAOGRRbARqpqur+7NRsEciuryCuXdeStJbxdwNbhjxXf2fWdGwgYBDT3KsaHSrI9GzMCJgemiRZWa4xPur5Qc8WFLQ7aw4qcZjySQ5mm9NmxlvNcaLGWbKqKWoPQ12RQh8Y3MEM9wqeY4owHkyMxnTovobEw1puGLCpNEc9zYqUTwRj1b5HO9phDDoM1je48lsTN2ajG3C7h3x3NKJOHhN4nVG9Zk/cz+hc1c6Dh14pmTuY3OvoscCjVIMb+q175TH4nCSwDxV0sqNsY0MRvt08yV9yJq0Gdgmizfoe92oMpf5zhkhXP1l7jo2L5DHj42zDmYpCd8Gj0XhGMysIuGr3bEonCAw6yuUx6+Nvay8UPSc5kWjIpsx5uj9MdfpJ0Hy5IdMdw2BKAgwZ4GTQHunKd7EwfT/kzA76uJtKhEvSj6ndWmfizJ4c54UfSsvmoqRT/bFsSTuZ+Kon4l5wUSzWOaEgG30+1ICc8vR8LkFfA4LYl4oed5Z3AyeRJkHAMmyJ7aFyY0ZJC1eSyR9jXjt92PPs8zcT4VGPen3aqz7PULMvYJuaoqXAcwNQlJIbsVDvApgbmyRYuwpGR8yEMz6g5RTZ421OQ9vgBWcfZm/X6/2+T4r8jJbvbz9/c8//x+InAjg"; \ No newline at end of file +window.searchData = "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"; \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html index f1ae69413..1d49a3101 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelError.html @@ -2,7 +2,7 @@ in case a CancellablePromise is cancelled successfully.

            The value of the name property is the string "CancelError". The value of the cause property is the cause passed to the cancel method, if any.

            -

            Hierarchy

            Constructors

            Hierarchy

            Constructors

            Properties

            cause? message name diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html index 7084e82a5..fcc068fc8 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancellablePromise.html @@ -35,7 +35,7 @@ This might be reconsidered in case the proposal is retired.

            and is compliant with the Promises/A+ specification (it passes the compliance suite) if so is the underlying implementation.

            -

            Type Parameters

            • T

            Hierarchy

            Implements

            Constructors

            Type Parameters

            • T

            Hierarchy

            Implements

            Constructors

            Properties

            Methods

            cancel cancelOn diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html index 6e18c34d9..48f30a330 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/CancelledRejectionError.html @@ -6,7 +6,7 @@ The value of the promise property holds a reference to the original promise.

            -

            Hierarchy

            • Error
              • CancelledRejectionError

            Constructors

            Hierarchy

            • Error
              • CancelledRejectionError

            Constructors

            Properties

            cause? message name diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html index 23541adeb..af1191e71 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/_internal_.Window.html @@ -1,4 +1,4 @@ -Window | @wailsio/runtime

            Methods

            Center +Window | @wailsio/runtime

            Methods

            • Gets the specified window.

              Parameters

              • name: string

                The name of the window to get.

              Returns Window

              The corresponding window object.

              -
            • Handles file drops originating from platform-specific code (e.g., macOS native drag-and-drop). +

            • Handles file drops originating from platform-specific code (e.g., macOS/Linux native drag-and-drop). Gathers information about the drop target element and sends it back to the Go backend.

              Parameters

              • filenames: string[]

                An array of file paths (strings) that were dropped.

                -
              • x: number

                The x-coordinate of the drop event.

                -
              • y: number

                The y-coordinate of the drop event.

                -

              Returns void

            • Returns the height of the window.

              +
            • x: number

              The x-coordinate of the drop event (CSS pixels).

              +
            • y: number

              The y-coordinate of the drop event (CSS pixels).

              +

            Returns void

            • Returns true if the window is focused.

              Returns Promise<boolean>

              Whether the window is currently focused.

              -
            • Returns true if the window is fullscreen.

              Returns Promise<boolean>

              Whether the window is currently fullscreen.

              -
            • Returns true if the window is maximised.

              Returns Promise<boolean>

              Whether the window is currently maximised.

              -
            • Returns true if the window is minimised.

              Returns Promise<boolean>

              Whether the window is currently minimised.

              -
            • Returns true if the window is resizable.

              Returns Promise<boolean>

              Whether the window is currently resizable.

              -
            • Restores the window to its previous state if it was previously minimised, maximised or fullscreen.

              -

              Returns Promise<void>

            • Restores the window to its previous state if it was previously minimised, maximised or fullscreen.

              +

              Returns Promise<void>

            • Sets the window to be always on top.

              Parameters

              • alwaysOnTop: boolean

                Whether the window should stay on top.

                -

              Returns Promise<void>

            • Sets the background colour of the window.

              +

            Returns Promise<void>

            • Sets the background colour of the window.

              Parameters

              • r: number

                The desired red component of the window background.

              • g: number

                The desired green component of the window background.

              • b: number

                The desired blue component of the window background.

              • a: number

                The desired alpha component of the window background.

                -

              Returns Promise<void>

            • Removes the window frame and title bar.

              +

            Returns Promise<void>

            • Removes the window frame and title bar.

              Parameters

              • frameless: boolean

                Whether the window should be frameless.

                -

              Returns Promise<void>

            • Disables the system fullscreen button.

              +

            Returns Promise<void>

            • Disables the system fullscreen button.

              Parameters

              • enabled: boolean

                Whether the fullscreen button should be enabled.

                -

              Returns Promise<void>

            • Sets the maximum size of the window.

              +

            Returns Promise<void>

            • Sets the maximum size of the window.

              Parameters

              • width: number

                The desired maximum width of the window.

              • height: number

                The desired maximum height of the window.

                -

              Returns Promise<void>

            • Sets the minimum size of the window.

              +

            Returns Promise<void>

            • Sets the minimum size of the window.

              Parameters

              • width: number

                The desired minimum width of the window.

              • height: number

                The desired minimum height of the window.

                -

              Returns Promise<void>

            • Sets the absolute position of the window.

              +

            Returns Promise<void>

            • Sets the absolute position of the window.

              Parameters

              • x: number

                The desired horizontal absolute position of the window.

              • y: number

                The desired vertical absolute position of the window.

                -

              Returns Promise<void>

            • Sets the relative position of the window to the screen.

              +

            Returns Promise<void>

            • Sets the relative position of the window to the screen.

              Parameters

              • x: number

                The desired horizontal relative position of the window.

              • y: number

                The desired vertical relative position of the window.

                -

              Returns Promise<void>

            • Sets whether the window is resizable.

              +

            Returns Promise<void>

            • Sets whether the window is resizable.

              Parameters

              • resizable: boolean

                Whether the window should be resizable.

                -

              Returns Promise<void>

            • Sets the size of the window.

              +

            Returns Promise<void>

            • Sets the size of the window.

              Parameters

              • width: number

                The desired width of the window.

              • height: number

                The desired height of the window.

                -

              Returns Promise<void>

            • Sets the title of the window.

              +

            Returns Promise<void>

            • Sets the title of the window.

              Parameters

              • title: string

                The desired title of the window.

                -

              Returns Promise<void>

            • Sets the zoom level of the window.

              +

            Returns Promise<void>

            • Sets the zoom level of the window.

              Parameters

              • zoom: number

                The desired zoom level.

                -

              Returns Promise<void>

            Returns Promise<void>

            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html index a1a7d4346..d8b16a640 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Capabilities.html @@ -1,3 +1,3 @@ Capabilities | @wailsio/runtime
            • Fetches the capabilities of the application from the server.

              Returns Promise<Record<string, any>>

              A promise that resolves to an object containing the capabilities.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html index 47e1891f1..83e16aed1 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.Environment.html @@ -1,3 +1,3 @@ Environment | @wailsio/runtime
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.HandlePlatformFileDrop.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.HandlePlatformFileDrop.html deleted file mode 100644 index 6beac984d..000000000 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.HandlePlatformFileDrop.html +++ /dev/null @@ -1,6 +0,0 @@ -HandlePlatformFileDrop | @wailsio/runtime

            Function HandlePlatformFileDrop

            • Handles file drops originating from platform-specific code (e.g., macOS native drag-and-drop). -Gathers information about the drop target element and sends it back to the Go backend.

              -

              Parameters

              • filenames: string[]

                An array of file paths (strings) that were dropped.

                -
              • x: number

                The x-coordinate of the drop event.

                -
              • y: number

                The y-coordinate of the drop event.

                -

              Returns void

            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html index 8adb43b2f..8463205b4 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsAMD64.html @@ -1,3 +1,3 @@ IsAMD64 | @wailsio/runtime
            • Checks if the current environment architecture is AMD64.

              Returns boolean

              True if the current environment architecture is AMD64, false otherwise.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html index e60f1118f..4e85b9117 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM.html @@ -1,3 +1,3 @@ IsARM | @wailsio/runtime
            • Checks if the current architecture is ARM.

              Returns boolean

              True if the current architecture is ARM, false otherwise.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html index 61a332039..8b6f9a174 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsARM64.html @@ -1,3 +1,3 @@ IsARM64 | @wailsio/runtime
            • Checks if the current environment is ARM64 architecture.

              Returns boolean

              Returns true if the environment is ARM64 architecture, otherwise returns false.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html index 480e901d0..461dc8e2e 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDarkMode.html @@ -1,3 +1,3 @@ IsDarkMode | @wailsio/runtime
            • Retrieves the system dark mode status.

              Returns Promise<boolean>

              A promise that resolves to a boolean value indicating if the system is in dark mode.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html index 04f19b050..f9fa983df 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsDebug.html @@ -1,3 +1,3 @@ IsDebug | @wailsio/runtime
            • Reports whether the app is being run in debug mode.

              Returns boolean

              True if the app is being run in debug mode.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html index cee1a14fe..98a91aeb2 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsLinux.html @@ -1,3 +1,3 @@ IsLinux | @wailsio/runtime
            • Checks if the current operating system is Linux.

              Returns boolean

              Returns true if the current operating system is Linux, false otherwise.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html index 059804d90..ec0843efc 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsMac.html @@ -1,3 +1,3 @@ IsMac | @wailsio/runtime
            • Checks if the current environment is a macOS operating system.

              Returns boolean

              True if the environment is macOS, false otherwise.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html index dccf2541c..658f88991 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.IsWindows.html @@ -1,3 +1,3 @@ IsWindows | @wailsio/runtime
            • Checks if the current operating system is Windows.

              Returns boolean

              True if the operating system is Windows, otherwise false.

              -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html index c5e46e105..5a660b237 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.invoke.html @@ -1 +1 @@ -invoke | @wailsio/runtime
            +invoke | @wailsio/runtime
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html index eead3c931..4ee5aac30 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html @@ -1 +1 @@ -@wailsio/runtime
            +@wailsio/runtime
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html index 185e9eaf9..16b194a24 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.EnvironmentInfo.html @@ -1,11 +1,11 @@ -EnvironmentInfo | @wailsio/runtime

            Interface EnvironmentInfo

            interface EnvironmentInfo {
                Arch: string;
                Debug: boolean;
                OS: string;
                OSInfo: OSInfo;
                PlatformInfo: Record<string, any>;
            }

            Properties

            Arch +EnvironmentInfo | @wailsio/runtime

            Interface EnvironmentInfo

            interface EnvironmentInfo {
                Arch: string;
                Debug: boolean;
                OS: string;
                OSInfo: OSInfo;
                PlatformInfo: Record<string, any>;
            }

            Properties

            Arch: string

            The architecture of the system.

            -
            Debug: boolean

            True if the application is running in debug mode, otherwise false.

            -
            OS: string

            The operating system in use.

            -
            OSInfo: OSInfo

            Details of the operating system.

            -
            PlatformInfo: Record<string, any>

            Additional platform information.

            -
            +
            Debug: boolean

            True if the application is running in debug mode, otherwise false.

            +
            OS: string

            The operating system in use.

            +
            OSInfo: OSInfo

            Details of the operating system.

            +
            PlatformInfo: Record<string, any>

            Additional platform information.

            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html index 94ae84b8d..a008978cf 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/System.OSInfo.html @@ -1,9 +1,9 @@ -OSInfo | @wailsio/runtime
            interface OSInfo {
                Branding: string;
                ID: string;
                Name: string;
                Version: string;
            }

            Properties

            Branding +OSInfo | @wailsio/runtime
            interface OSInfo {
                Branding: string;
                ID: string;
                Name: string;
                Version: string;
            }

            Properties

            Properties

            Branding: string

            The branding of the OS.

            -
            ID: string

            The ID of the OS.

            -
            Name: string

            The name of the OS.

            -
            Version: string

            The version of the OS.

            -
            +
            ID: string

            The ID of the OS.

            +
            Name: string

            The name of the OS.

            +
            Version: string

            The version of the OS.

            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html index 9a3cbd2e2..8cbcc9c3a 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Position.html @@ -1,6 +1,6 @@ Position | @wailsio/runtime

            A record describing the position of a window.

            -
            interface Position {
                x: number;
                y: number;
            }

            Properties

            x +
            interface Position {
                x: number;
                y: number;
            }

            Properties

            x y

            Properties

            x: number

            The horizontal position of the window.

            -
            y: number

            The vertical position of the window.

            -
            +
            y: number

            The vertical position of the window.

            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html index dcfaf60bc..e7b5f32de 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.Size.html @@ -1,6 +1,6 @@ Size | @wailsio/runtime

            A record describing the size of a window.

            -
            interface Size {
                height: number;
                width: number;
            }

            Properties

            interface Size {
                height: number;
                width: number;
            }

            Properties

            Properties

            height: number

            The height of the window.

            -
            width: number

            The width of the window.

            -
            +
            width: number

            The width of the window.

            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html index c9043eddd..bc891a645 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/System.html @@ -1 +1 @@ -System | @wailsio/runtime
            +System | @wailsio/runtime
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html index 5d0e94277..564d38e84 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html @@ -1 +1 @@ -Types | @wailsio/runtime

            Variable TypesConst

            Types: Readonly<
                {
                    Common: Readonly<
                        {
                            ApplicationLaunchedWithUrl: "common:ApplicationLaunchedWithUrl";
                            ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile";
                            ApplicationStarted: "common:ApplicationStarted";
                            ThemeChanged: "common:ThemeChanged";
                            WindowClosing: "common:WindowClosing";
                            WindowDidMove: "common:WindowDidMove";
                            WindowDidResize: "common:WindowDidResize";
                            WindowDPIChanged: "common:WindowDPIChanged";
                            WindowDropZoneFilesDropped: "common:WindowDropZoneFilesDropped";
                            WindowFilesDropped: "common:WindowFilesDropped";
                            WindowFocus: "common:WindowFocus";
                            WindowFullscreen: "common:WindowFullscreen";
                            WindowHide: "common:WindowHide";
                            WindowLostFocus: "common:WindowLostFocus";
                            WindowMaximise: "common:WindowMaximise";
                            WindowMinimise: "common:WindowMinimise";
                            WindowRestore: "common:WindowRestore";
                            WindowRuntimeReady: "common:WindowRuntimeReady";
                            WindowShow: "common:WindowShow";
                            WindowToggleFrameless: "common:WindowToggleFrameless";
                            WindowUnFullscreen: "common:WindowUnFullscreen";
                            WindowUnMaximise: "common:WindowUnMaximise";
                            WindowUnMinimise: "common:WindowUnMinimise";
                            WindowZoom: "common:WindowZoom";
                            WindowZoomIn: "common:WindowZoomIn";
                            WindowZoomOut: "common:WindowZoomOut";
                            WindowZoomReset: "common:WindowZoomReset";
                        },
                    >;
                    iOS: Readonly<
                        {
                            ApplicationDidBecomeActive: "ios:ApplicationDidBecomeActive";
                            ApplicationDidEnterBackground: "ios:ApplicationDidEnterBackground";
                            ApplicationDidFinishLaunching: "ios:ApplicationDidFinishLaunching";
                            ApplicationDidReceiveMemoryWarning: "ios:ApplicationDidReceiveMemoryWarning";
                            ApplicationWillEnterForeground: "ios:ApplicationWillEnterForeground";
                            ApplicationWillResignActive: "ios:ApplicationWillResignActive";
                            ApplicationWillTerminate: "ios:ApplicationWillTerminate";
                            WebViewDecidePolicyForNavigationAction: "ios:WebViewDecidePolicyForNavigationAction";
                            WebViewDidFailNavigation: "ios:WebViewDidFailNavigation";
                            WebViewDidFinishNavigation: "ios:WebViewDidFinishNavigation";
                            WebViewDidStartNavigation: "ios:WebViewDidStartNavigation";
                            WindowDidAppear: "ios:WindowDidAppear";
                            WindowDidDisappear: "ios:WindowDidDisappear";
                            WindowDidLoad: "ios:WindowDidLoad";
                            WindowOrientationChanged: "ios:WindowOrientationChanged";
                            WindowSafeAreaInsetsChanged: "ios:WindowSafeAreaInsetsChanged";
                            WindowTouchBegan: "ios:WindowTouchBegan";
                            WindowTouchCancelled: "ios:WindowTouchCancelled";
                            WindowTouchEnded: "ios:WindowTouchEnded";
                            WindowTouchMoved: "ios:WindowTouchMoved";
                            WindowWillAppear: "ios:WindowWillAppear";
                            WindowWillDisappear: "ios:WindowWillDisappear";
                        },
                    >;
                    Linux: Readonly<
                        {
                            ApplicationStartup: "linux:ApplicationStartup";
                            SystemThemeChanged: "linux:SystemThemeChanged";
                            WindowDeleteEvent: "linux:WindowDeleteEvent";
                            WindowDidMove: "linux:WindowDidMove";
                            WindowDidResize: "linux:WindowDidResize";
                            WindowFocusIn: "linux:WindowFocusIn";
                            WindowFocusOut: "linux:WindowFocusOut";
                            WindowLoadCommitted: "linux:WindowLoadCommitted";
                            WindowLoadFinished: "linux:WindowLoadFinished";
                            WindowLoadRedirected: "linux:WindowLoadRedirected";
                            WindowLoadStarted: "linux:WindowLoadStarted";
                        },
                    >;
                    Mac: Readonly<
                        {
                            ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive";
                            ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties";
                            ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance";
                            ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon";
                            ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState";
                            ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters";
                            ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame";
                            ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation";
                            ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme";
                            ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching";
                            ApplicationDidHide: "mac:ApplicationDidHide";
                            ApplicationDidResignActive: "mac:ApplicationDidResignActive";
                            ApplicationDidUnhide: "mac:ApplicationDidUnhide";
                            ApplicationDidUpdate: "mac:ApplicationDidUpdate";
                            ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen";
                            ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive";
                            ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching";
                            ApplicationWillHide: "mac:ApplicationWillHide";
                            ApplicationWillResignActive: "mac:ApplicationWillResignActive";
                            ApplicationWillTerminate: "mac:ApplicationWillTerminate";
                            ApplicationWillUnhide: "mac:ApplicationWillUnhide";
                            ApplicationWillUpdate: "mac:ApplicationWillUpdate";
                            MenuDidAddItem: "mac:MenuDidAddItem";
                            MenuDidBeginTracking: "mac:MenuDidBeginTracking";
                            MenuDidClose: "mac:MenuDidClose";
                            MenuDidDisplayItem: "mac:MenuDidDisplayItem";
                            MenuDidEndTracking: "mac:MenuDidEndTracking";
                            MenuDidHighlightItem: "mac:MenuDidHighlightItem";
                            MenuDidOpen: "mac:MenuDidOpen";
                            MenuDidPopUp: "mac:MenuDidPopUp";
                            MenuDidRemoveItem: "mac:MenuDidRemoveItem";
                            MenuDidSendAction: "mac:MenuDidSendAction";
                            MenuDidSendActionToItem: "mac:MenuDidSendActionToItem";
                            MenuDidUpdate: "mac:MenuDidUpdate";
                            MenuWillAddItem: "mac:MenuWillAddItem";
                            MenuWillBeginTracking: "mac:MenuWillBeginTracking";
                            MenuWillDisplayItem: "mac:MenuWillDisplayItem";
                            MenuWillEndTracking: "mac:MenuWillEndTracking";
                            MenuWillHighlightItem: "mac:MenuWillHighlightItem";
                            MenuWillOpen: "mac:MenuWillOpen";
                            MenuWillPopUp: "mac:MenuWillPopUp";
                            MenuWillRemoveItem: "mac:MenuWillRemoveItem";
                            MenuWillSendAction: "mac:MenuWillSendAction";
                            MenuWillSendActionToItem: "mac:MenuWillSendActionToItem";
                            MenuWillUpdate: "mac:MenuWillUpdate";
                            WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation";
                            WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation";
                            WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation";
                            WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation";
                            WindowDidBecomeKey: "mac:WindowDidBecomeKey";
                            WindowDidBecomeMain: "mac:WindowDidBecomeMain";
                            WindowDidBeginSheet: "mac:WindowDidBeginSheet";
                            WindowDidChangeAlpha: "mac:WindowDidChangeAlpha";
                            WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation";
                            WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties";
                            WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior";
                            WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance";
                            WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState";
                            WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode";
                            WindowDidChangeScreen: "mac:WindowDidChangeScreen";
                            WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters";
                            WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile";
                            WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace";
                            WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties";
                            WindowDidChangeSharingType: "mac:WindowDidChangeSharingType";
                            WindowDidChangeSpace: "mac:WindowDidChangeSpace";
                            WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode";
                            WindowDidChangeTitle: "mac:WindowDidChangeTitle";
                            WindowDidChangeToolbar: "mac:WindowDidChangeToolbar";
                            WindowDidDeminiaturize: "mac:WindowDidDeminiaturize";
                            WindowDidEndSheet: "mac:WindowDidEndSheet";
                            WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen";
                            WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser";
                            WindowDidExitFullScreen: "mac:WindowDidExitFullScreen";
                            WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser";
                            WindowDidExpose: "mac:WindowDidExpose";
                            WindowDidFocus: "mac:WindowDidFocus";
                            WindowDidMiniaturize: "mac:WindowDidMiniaturize";
                            WindowDidMove: "mac:WindowDidMove";
                            WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen";
                            WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen";
                            WindowDidResignKey: "mac:WindowDidResignKey";
                            WindowDidResignMain: "mac:WindowDidResignMain";
                            WindowDidResize: "mac:WindowDidResize";
                            WindowDidUpdate: "mac:WindowDidUpdate";
                            WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha";
                            WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior";
                            WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties";
                            WindowDidUpdateShadow: "mac:WindowDidUpdateShadow";
                            WindowDidUpdateTitle: "mac:WindowDidUpdateTitle";
                            WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar";
                            WindowDidZoom: "mac:WindowDidZoom";
                            WindowFileDraggingEntered: "mac:WindowFileDraggingEntered";
                            WindowFileDraggingExited: "mac:WindowFileDraggingExited";
                            WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed";
                            WindowHide: "mac:WindowHide";
                            WindowMaximise: "mac:WindowMaximise";
                            WindowMinimise: "mac:WindowMinimise";
                            WindowShouldClose: "mac:WindowShouldClose";
                            WindowShow: "mac:WindowShow";
                            WindowUnMaximise: "mac:WindowUnMaximise";
                            WindowUnMinimise: "mac:WindowUnMinimise";
                            WindowWillBecomeKey: "mac:WindowWillBecomeKey";
                            WindowWillBecomeMain: "mac:WindowWillBecomeMain";
                            WindowWillBeginSheet: "mac:WindowWillBeginSheet";
                            WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode";
                            WindowWillClose: "mac:WindowWillClose";
                            WindowWillDeminiaturize: "mac:WindowWillDeminiaturize";
                            WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen";
                            WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser";
                            WindowWillExitFullScreen: "mac:WindowWillExitFullScreen";
                            WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser";
                            WindowWillFocus: "mac:WindowWillFocus";
                            WindowWillMiniaturize: "mac:WindowWillMiniaturize";
                            WindowWillMove: "mac:WindowWillMove";
                            WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen";
                            WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen";
                            WindowWillResignMain: "mac:WindowWillResignMain";
                            WindowWillResize: "mac:WindowWillResize";
                            WindowWillUnfocus: "mac:WindowWillUnfocus";
                            WindowWillUpdate: "mac:WindowWillUpdate";
                            WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha";
                            WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior";
                            WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties";
                            WindowWillUpdateShadow: "mac:WindowWillUpdateShadow";
                            WindowWillUpdateTitle: "mac:WindowWillUpdateTitle";
                            WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar";
                            WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility";
                            WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame";
                            WindowZoomIn: "mac:WindowZoomIn";
                            WindowZoomOut: "mac:WindowZoomOut";
                            WindowZoomReset: "mac:WindowZoomReset";
                        },
                    >;
                    Windows: Readonly<
                        {
                            APMPowerSettingChange: "windows:APMPowerSettingChange";
                            APMPowerStatusChange: "windows:APMPowerStatusChange";
                            APMResumeAutomatic: "windows:APMResumeAutomatic";
                            APMResumeSuspend: "windows:APMResumeSuspend";
                            APMSuspend: "windows:APMSuspend";
                            ApplicationStarted: "windows:ApplicationStarted";
                            SystemThemeChanged: "windows:SystemThemeChanged";
                            WebViewNavigationCompleted: "windows:WebViewNavigationCompleted";
                            WindowActive: "windows:WindowActive";
                            WindowBackgroundErase: "windows:WindowBackgroundErase";
                            WindowClickActive: "windows:WindowClickActive";
                            WindowClosing: "windows:WindowClosing";
                            WindowDidMove: "windows:WindowDidMove";
                            WindowDidResize: "windows:WindowDidResize";
                            WindowDPIChanged: "windows:WindowDPIChanged";
                            WindowDragDrop: "windows:WindowDragDrop";
                            WindowDragEnter: "windows:WindowDragEnter";
                            WindowDragLeave: "windows:WindowDragLeave";
                            WindowDragOver: "windows:WindowDragOver";
                            WindowEndMove: "windows:WindowEndMove";
                            WindowEndResize: "windows:WindowEndResize";
                            WindowFullscreen: "windows:WindowFullscreen";
                            WindowHide: "windows:WindowHide";
                            WindowInactive: "windows:WindowInactive";
                            WindowKeyDown: "windows:WindowKeyDown";
                            WindowKeyUp: "windows:WindowKeyUp";
                            WindowKillFocus: "windows:WindowKillFocus";
                            WindowMaximise: "windows:WindowMaximise";
                            WindowMinimise: "windows:WindowMinimise";
                            WindowNonClientHit: "windows:WindowNonClientHit";
                            WindowNonClientMouseDown: "windows:WindowNonClientMouseDown";
                            WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave";
                            WindowNonClientMouseMove: "windows:WindowNonClientMouseMove";
                            WindowNonClientMouseUp: "windows:WindowNonClientMouseUp";
                            WindowPaint: "windows:WindowPaint";
                            WindowRestore: "windows:WindowRestore";
                            WindowSetFocus: "windows:WindowSetFocus";
                            WindowShow: "windows:WindowShow";
                            WindowStartMove: "windows:WindowStartMove";
                            WindowStartResize: "windows:WindowStartResize";
                            WindowUnFullscreen: "windows:WindowUnFullscreen";
                            WindowUnMaximise: "windows:WindowUnMaximise";
                            WindowUnMinimise: "windows:WindowUnMinimise";
                            WindowZOrderChanged: "windows:WindowZOrderChanged";
                        },
                    >;
                },
            > = ...
            +Types | @wailsio/runtime

            Variable TypesConst

            Types: Readonly<
                {
                    Common: Readonly<
                        {
                            ApplicationLaunchedWithUrl: "common:ApplicationLaunchedWithUrl";
                            ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile";
                            ApplicationStarted: "common:ApplicationStarted";
                            ThemeChanged: "common:ThemeChanged";
                            WindowClosing: "common:WindowClosing";
                            WindowDidMove: "common:WindowDidMove";
                            WindowDidResize: "common:WindowDidResize";
                            WindowDPIChanged: "common:WindowDPIChanged";
                            WindowFilesDropped: "common:WindowFilesDropped";
                            WindowFocus: "common:WindowFocus";
                            WindowFullscreen: "common:WindowFullscreen";
                            WindowHide: "common:WindowHide";
                            WindowLostFocus: "common:WindowLostFocus";
                            WindowMaximise: "common:WindowMaximise";
                            WindowMinimise: "common:WindowMinimise";
                            WindowRestore: "common:WindowRestore";
                            WindowRuntimeReady: "common:WindowRuntimeReady";
                            WindowShow: "common:WindowShow";
                            WindowToggleFrameless: "common:WindowToggleFrameless";
                            WindowUnFullscreen: "common:WindowUnFullscreen";
                            WindowUnMaximise: "common:WindowUnMaximise";
                            WindowUnMinimise: "common:WindowUnMinimise";
                            WindowZoom: "common:WindowZoom";
                            WindowZoomIn: "common:WindowZoomIn";
                            WindowZoomOut: "common:WindowZoomOut";
                            WindowZoomReset: "common:WindowZoomReset";
                        },
                    >;
                    iOS: Readonly<
                        {
                            ApplicationDidBecomeActive: "ios:ApplicationDidBecomeActive";
                            ApplicationDidEnterBackground: "ios:ApplicationDidEnterBackground";
                            ApplicationDidFinishLaunching: "ios:ApplicationDidFinishLaunching";
                            ApplicationDidReceiveMemoryWarning: "ios:ApplicationDidReceiveMemoryWarning";
                            ApplicationWillEnterForeground: "ios:ApplicationWillEnterForeground";
                            ApplicationWillResignActive: "ios:ApplicationWillResignActive";
                            ApplicationWillTerminate: "ios:ApplicationWillTerminate";
                            WebViewDecidePolicyForNavigationAction: "ios:WebViewDecidePolicyForNavigationAction";
                            WebViewDidFailNavigation: "ios:WebViewDidFailNavigation";
                            WebViewDidFinishNavigation: "ios:WebViewDidFinishNavigation";
                            WebViewDidStartNavigation: "ios:WebViewDidStartNavigation";
                            WindowDidAppear: "ios:WindowDidAppear";
                            WindowDidDisappear: "ios:WindowDidDisappear";
                            WindowDidLoad: "ios:WindowDidLoad";
                            WindowOrientationChanged: "ios:WindowOrientationChanged";
                            WindowSafeAreaInsetsChanged: "ios:WindowSafeAreaInsetsChanged";
                            WindowTouchBegan: "ios:WindowTouchBegan";
                            WindowTouchCancelled: "ios:WindowTouchCancelled";
                            WindowTouchEnded: "ios:WindowTouchEnded";
                            WindowTouchMoved: "ios:WindowTouchMoved";
                            WindowWillAppear: "ios:WindowWillAppear";
                            WindowWillDisappear: "ios:WindowWillDisappear";
                        },
                    >;
                    Linux: Readonly<
                        {
                            ApplicationStartup: "linux:ApplicationStartup";
                            SystemThemeChanged: "linux:SystemThemeChanged";
                            WindowDeleteEvent: "linux:WindowDeleteEvent";
                            WindowDidMove: "linux:WindowDidMove";
                            WindowDidResize: "linux:WindowDidResize";
                            WindowFocusIn: "linux:WindowFocusIn";
                            WindowFocusOut: "linux:WindowFocusOut";
                            WindowLoadCommitted: "linux:WindowLoadCommitted";
                            WindowLoadFinished: "linux:WindowLoadFinished";
                            WindowLoadRedirected: "linux:WindowLoadRedirected";
                            WindowLoadStarted: "linux:WindowLoadStarted";
                        },
                    >;
                    Mac: Readonly<
                        {
                            ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive";
                            ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties";
                            ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance";
                            ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon";
                            ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState";
                            ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters";
                            ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame";
                            ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation";
                            ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme";
                            ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching";
                            ApplicationDidHide: "mac:ApplicationDidHide";
                            ApplicationDidResignActive: "mac:ApplicationDidResignActive";
                            ApplicationDidUnhide: "mac:ApplicationDidUnhide";
                            ApplicationDidUpdate: "mac:ApplicationDidUpdate";
                            ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen";
                            ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive";
                            ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching";
                            ApplicationWillHide: "mac:ApplicationWillHide";
                            ApplicationWillResignActive: "mac:ApplicationWillResignActive";
                            ApplicationWillTerminate: "mac:ApplicationWillTerminate";
                            ApplicationWillUnhide: "mac:ApplicationWillUnhide";
                            ApplicationWillUpdate: "mac:ApplicationWillUpdate";
                            MenuDidAddItem: "mac:MenuDidAddItem";
                            MenuDidBeginTracking: "mac:MenuDidBeginTracking";
                            MenuDidClose: "mac:MenuDidClose";
                            MenuDidDisplayItem: "mac:MenuDidDisplayItem";
                            MenuDidEndTracking: "mac:MenuDidEndTracking";
                            MenuDidHighlightItem: "mac:MenuDidHighlightItem";
                            MenuDidOpen: "mac:MenuDidOpen";
                            MenuDidPopUp: "mac:MenuDidPopUp";
                            MenuDidRemoveItem: "mac:MenuDidRemoveItem";
                            MenuDidSendAction: "mac:MenuDidSendAction";
                            MenuDidSendActionToItem: "mac:MenuDidSendActionToItem";
                            MenuDidUpdate: "mac:MenuDidUpdate";
                            MenuWillAddItem: "mac:MenuWillAddItem";
                            MenuWillBeginTracking: "mac:MenuWillBeginTracking";
                            MenuWillDisplayItem: "mac:MenuWillDisplayItem";
                            MenuWillEndTracking: "mac:MenuWillEndTracking";
                            MenuWillHighlightItem: "mac:MenuWillHighlightItem";
                            MenuWillOpen: "mac:MenuWillOpen";
                            MenuWillPopUp: "mac:MenuWillPopUp";
                            MenuWillRemoveItem: "mac:MenuWillRemoveItem";
                            MenuWillSendAction: "mac:MenuWillSendAction";
                            MenuWillSendActionToItem: "mac:MenuWillSendActionToItem";
                            MenuWillUpdate: "mac:MenuWillUpdate";
                            WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation";
                            WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation";
                            WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation";
                            WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation";
                            WindowDidBecomeKey: "mac:WindowDidBecomeKey";
                            WindowDidBecomeMain: "mac:WindowDidBecomeMain";
                            WindowDidBeginSheet: "mac:WindowDidBeginSheet";
                            WindowDidChangeAlpha: "mac:WindowDidChangeAlpha";
                            WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation";
                            WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties";
                            WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior";
                            WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance";
                            WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState";
                            WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode";
                            WindowDidChangeScreen: "mac:WindowDidChangeScreen";
                            WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters";
                            WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile";
                            WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace";
                            WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties";
                            WindowDidChangeSharingType: "mac:WindowDidChangeSharingType";
                            WindowDidChangeSpace: "mac:WindowDidChangeSpace";
                            WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode";
                            WindowDidChangeTitle: "mac:WindowDidChangeTitle";
                            WindowDidChangeToolbar: "mac:WindowDidChangeToolbar";
                            WindowDidDeminiaturize: "mac:WindowDidDeminiaturize";
                            WindowDidEndSheet: "mac:WindowDidEndSheet";
                            WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen";
                            WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser";
                            WindowDidExitFullScreen: "mac:WindowDidExitFullScreen";
                            WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser";
                            WindowDidExpose: "mac:WindowDidExpose";
                            WindowDidFocus: "mac:WindowDidFocus";
                            WindowDidMiniaturize: "mac:WindowDidMiniaturize";
                            WindowDidMove: "mac:WindowDidMove";
                            WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen";
                            WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen";
                            WindowDidResignKey: "mac:WindowDidResignKey";
                            WindowDidResignMain: "mac:WindowDidResignMain";
                            WindowDidResize: "mac:WindowDidResize";
                            WindowDidUpdate: "mac:WindowDidUpdate";
                            WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha";
                            WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior";
                            WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties";
                            WindowDidUpdateShadow: "mac:WindowDidUpdateShadow";
                            WindowDidUpdateTitle: "mac:WindowDidUpdateTitle";
                            WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar";
                            WindowDidZoom: "mac:WindowDidZoom";
                            WindowFileDraggingEntered: "mac:WindowFileDraggingEntered";
                            WindowFileDraggingExited: "mac:WindowFileDraggingExited";
                            WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed";
                            WindowHide: "mac:WindowHide";
                            WindowMaximise: "mac:WindowMaximise";
                            WindowMinimise: "mac:WindowMinimise";
                            WindowShouldClose: "mac:WindowShouldClose";
                            WindowShow: "mac:WindowShow";
                            WindowUnMaximise: "mac:WindowUnMaximise";
                            WindowUnMinimise: "mac:WindowUnMinimise";
                            WindowWillBecomeKey: "mac:WindowWillBecomeKey";
                            WindowWillBecomeMain: "mac:WindowWillBecomeMain";
                            WindowWillBeginSheet: "mac:WindowWillBeginSheet";
                            WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode";
                            WindowWillClose: "mac:WindowWillClose";
                            WindowWillDeminiaturize: "mac:WindowWillDeminiaturize";
                            WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen";
                            WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser";
                            WindowWillExitFullScreen: "mac:WindowWillExitFullScreen";
                            WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser";
                            WindowWillFocus: "mac:WindowWillFocus";
                            WindowWillMiniaturize: "mac:WindowWillMiniaturize";
                            WindowWillMove: "mac:WindowWillMove";
                            WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen";
                            WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen";
                            WindowWillResignMain: "mac:WindowWillResignMain";
                            WindowWillResize: "mac:WindowWillResize";
                            WindowWillUnfocus: "mac:WindowWillUnfocus";
                            WindowWillUpdate: "mac:WindowWillUpdate";
                            WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha";
                            WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior";
                            WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties";
                            WindowWillUpdateShadow: "mac:WindowWillUpdateShadow";
                            WindowWillUpdateTitle: "mac:WindowWillUpdateTitle";
                            WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar";
                            WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility";
                            WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame";
                            WindowZoomIn: "mac:WindowZoomIn";
                            WindowZoomOut: "mac:WindowZoomOut";
                            WindowZoomReset: "mac:WindowZoomReset";
                        },
                    >;
                    Windows: Readonly<
                        {
                            APMPowerSettingChange: "windows:APMPowerSettingChange";
                            APMPowerStatusChange: "windows:APMPowerStatusChange";
                            APMResumeAutomatic: "windows:APMResumeAutomatic";
                            APMResumeSuspend: "windows:APMResumeSuspend";
                            APMSuspend: "windows:APMSuspend";
                            ApplicationStarted: "windows:ApplicationStarted";
                            SystemThemeChanged: "windows:SystemThemeChanged";
                            WebViewNavigationCompleted: "windows:WebViewNavigationCompleted";
                            WindowActive: "windows:WindowActive";
                            WindowBackgroundErase: "windows:WindowBackgroundErase";
                            WindowClickActive: "windows:WindowClickActive";
                            WindowClosing: "windows:WindowClosing";
                            WindowDidMove: "windows:WindowDidMove";
                            WindowDidResize: "windows:WindowDidResize";
                            WindowDPIChanged: "windows:WindowDPIChanged";
                            WindowDragDrop: "windows:WindowDragDrop";
                            WindowDragEnter: "windows:WindowDragEnter";
                            WindowDragLeave: "windows:WindowDragLeave";
                            WindowDragOver: "windows:WindowDragOver";
                            WindowEndMove: "windows:WindowEndMove";
                            WindowEndResize: "windows:WindowEndResize";
                            WindowFullscreen: "windows:WindowFullscreen";
                            WindowHide: "windows:WindowHide";
                            WindowInactive: "windows:WindowInactive";
                            WindowKeyDown: "windows:WindowKeyDown";
                            WindowKeyUp: "windows:WindowKeyUp";
                            WindowKillFocus: "windows:WindowKillFocus";
                            WindowMaximise: "windows:WindowMaximise";
                            WindowMinimise: "windows:WindowMinimise";
                            WindowNonClientHit: "windows:WindowNonClientHit";
                            WindowNonClientMouseDown: "windows:WindowNonClientMouseDown";
                            WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave";
                            WindowNonClientMouseMove: "windows:WindowNonClientMouseMove";
                            WindowNonClientMouseUp: "windows:WindowNonClientMouseUp";
                            WindowPaint: "windows:WindowPaint";
                            WindowRestore: "windows:WindowRestore";
                            WindowSetFocus: "windows:WindowSetFocus";
                            WindowShow: "windows:WindowShow";
                            WindowStartMove: "windows:WindowStartMove";
                            WindowStartResize: "windows:WindowStartResize";
                            WindowUnFullscreen: "windows:WindowUnFullscreen";
                            WindowUnMaximise: "windows:WindowUnMaximise";
                            WindowUnMinimise: "windows:WindowUnMinimise";
                            WindowZOrderChanged: "windows:WindowZOrderChanged";
                        },
                    >;
                },
            > = ...
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html index ee1c89b56..8b7134654 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Window.html @@ -1,2 +1,2 @@ Window | @wailsio/runtime

            Variable WindowConst

            Window: Window = ...

            The window within which the script is running.

            -
            +
            diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json index 87d3dc6da..decb9300b 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json @@ -1,12 +1,12 @@ { "name": "@wailsio/runtime", - "version": "3.0.0-alpha.77", + "version": "3.0.0-alpha.78", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@wailsio/runtime", - "version": "3.0.0-alpha.77", + "version": "3.0.0-alpha.78", "license": "MIT", "devDependencies": { "happy-dom": "^17.1.1", diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/package.json b/v3/internal/runtime/desktop/@wailsio/runtime/package.json index d2aec346e..bbbc94976 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/package.json +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@wailsio/runtime", "type": "module", - "version": "3.0.0-alpha.77", + "version": "3.0.0-alpha.78", "description": "Wails Runtime", "types": "types/index.d.ts", "exports": { diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts index e5b00e014..db381077d 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts @@ -256,6 +256,5 @@ export const Types = Object.freeze({ WindowZoomIn: "common:WindowZoomIn", WindowZoomOut: "common:WindowZoomOut", WindowZoomReset: "common:WindowZoomReset", - WindowDropZoneFilesDropped: "common:WindowDropZoneFilesDropped", }), }); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts index 4a6f74c46..1ea862e31 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts @@ -26,7 +26,7 @@ import * as Flags from "./flags.js"; import * as Screens from "./screens.js"; import * as System from "./system.js"; import * as IOS from "./ios.js"; -import Window from "./window.js"; +import Window, { handleDragEnter, handleDragLeave, handleDragOver } from "./window.js"; import * as WML from "./wml.js"; export { @@ -70,4 +70,9 @@ window._wails.invoke = System.invoke; // Binding ensures 'this' correctly refers to the current window instance window._wails.handlePlatformFileDrop = Window.HandlePlatformFileDrop.bind(Window); +// Linux-specific drag handlers (GTK intercepts DOM drag events) +window._wails.handleDragEnter = handleDragEnter; +window._wails.handleDragLeave = handleDragLeave; +window._wails.handleDragOver = handleDragOver; + System.invoke("wails:runtime:ready"); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts index 660ed5c33..c4fdd6cfe 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts @@ -15,7 +15,6 @@ const call = newRuntimeCaller(objectNames.System); const SystemIsDarkMode = 0; const SystemEnvironment = 1; const SystemCapabilities = 2; -const ApplicationFilesDroppedWithContext = 100; // New method ID for enriched drop event const _invoke = (function () { try { @@ -158,35 +157,3 @@ export function IsDebug(): boolean { return Boolean((window as any)._wails?.environment?.Debug); } -/** - * Handles file drops originating from platform-specific code (e.g., macOS native drag-and-drop). - * Gathers information about the drop target element and sends it back to the Go backend. - * - * @param filenames - An array of file paths (strings) that were dropped. - * @param x - The x-coordinate of the drop event. - * @param y - The y-coordinate of the drop event. - */ -export function HandlePlatformFileDrop(filenames: string[], x: number, y: number): void { - const element = document.elementFromPoint(x, y); - const elementId = element ? element.id : ''; - const classList = element ? Array.from(element.classList) : []; - - const payload = { - filenames, - x, - y, - elementId, - classList, - }; - - call(ApplicationFilesDroppedWithContext, payload) - .then(() => { - // Optional: Log success or handle if needed - console.log("Platform file drop processed and sent to Go."); - }) - .catch(err => { - // Optional: Log error - console.error("Error sending platform file drop to Go:", err); - }); -} - diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts index 69d70b442..7a80fc17a 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts @@ -11,10 +11,10 @@ The electron alternative for Go import {newRuntimeCaller, objectNames} from "./runtime.js"; import type { Screen } from "./screens.js"; -// NEW: Dropzone constants -const DROPZONE_ATTRIBUTE = 'data-wails-dropzone'; -const DROPZONE_HOVER_CLASS = 'wails-dropzone-hover'; // User can style this class -let currentHoveredDropzone: Element | null = null; +// Drop target constants +const DROP_TARGET_ATTRIBUTE = 'data-file-drop-target'; +const DROP_TARGET_ACTIVE_CLASS = 'file-drop-target-active'; +let currentDropTarget: Element | null = null; const PositionMethod = 0; const CenterMethod = 1; @@ -66,17 +66,108 @@ const ZoomInMethod = 46; const ZoomOutMethod = 47; const ZoomResetMethod = 48; const SnapAssistMethod = 49; -const WindowDropZoneDropped = 50; +const FilesDropped = 50; const PrintMethod = 51; -function getDropzoneElement(element: Element | null): Element | null { +/** + * Finds the nearest drop target element by walking up the DOM tree. + */ +function getDropTargetElement(element: Element | null): Element | null { if (!element) { return null; } - // Allow dropzone attribute to be on the element itself or any parent - return element.closest(`[${DROPZONE_ATTRIBUTE}]`); + return element.closest(`[${DROP_TARGET_ATTRIBUTE}]`); } +/** + * Check if we can use WebView2's postMessageWithAdditionalObjects (Windows) + * Also checks that EnableFileDrop is true for this window. + */ +function canResolveFilePaths(): boolean { + // Must have WebView2's postMessageWithAdditionalObjects API (Windows only) + if ((window as any).chrome?.webview?.postMessageWithAdditionalObjects == null) { + return false; + } + // Must have EnableFileDrop set to true for this window + // This flag is set by the Go backend during runtime initialization + return (window as any)._wails?.flags?.enableFileDrop === true; +} + +/** + * Send file drop to backend via WebView2 (Windows only) + */ +function resolveFilePaths(x: number, y: number, files: File[]): void { + if ((window as any).chrome?.webview?.postMessageWithAdditionalObjects) { + (window as any).chrome.webview.postMessageWithAdditionalObjects(`file:drop:${x}:${y}`, files); + } +} + +// Native drag state (Linux/macOS intercept DOM drag events) +let nativeDragActive = false; + +/** + * Cleans up native drag state and hover effects. + * Called on drop or when drag leaves the window. + */ +function cleanupNativeDrag(): void { + nativeDragActive = false; + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } +} + +/** + * Called from Go when a file drag enters the window on Linux/macOS. + */ +function handleDragEnter(): void { + // Check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; // File drops disabled, don't activate drag state + } + nativeDragActive = true; +} + +/** + * Called from Go when a file drag leaves the window on Linux/macOS. + */ +function handleDragLeave(): void { + cleanupNativeDrag(); +} + +/** + * Called from Go during file drag to update hover state on Linux/macOS. + * @param x - X coordinate in CSS pixels + * @param y - Y coordinate in CSS pixels + */ +function handleDragOver(x: number, y: number): void { + if (!nativeDragActive) return; + + // Check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; // File drops disabled, don't show hover effects + } + + const targetElement = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(targetElement); + + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = dropTarget; + } else { + currentDropTarget = null; + } +} + + + +// Export the handlers for use by Go via index.ts +export { handleDragEnter, handleDragLeave, handleDragOver }; + /** * A record describing the position of a window. */ @@ -536,33 +627,34 @@ class Window { } /** - * Handles file drops originating from platform-specific code (e.g., macOS native drag-and-drop). + * Handles file drops originating from platform-specific code (e.g., macOS/Linux native drag-and-drop). * Gathers information about the drop target element and sends it back to the Go backend. * * @param filenames - An array of file paths (strings) that were dropped. - * @param x - The x-coordinate of the drop event. - * @param y - The y-coordinate of the drop event. + * @param x - The x-coordinate of the drop event (CSS pixels). + * @param y - The y-coordinate of the drop event (CSS pixels). */ HandlePlatformFileDrop(filenames: string[], x: number, y: number): void { + // Check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; // File drops disabled, ignore the drop + } + const element = document.elementFromPoint(x, y); + const dropTarget = getDropTargetElement(element); - // NEW: Check if the drop target is a valid dropzone - const dropzoneTarget = getDropzoneElement(element); - - if (!dropzoneTarget) { - console.log(`Wails Runtime: Drop on element (or no element) at ${x},${y} which is not a designated dropzone. Ignoring. Element:`, element); - // No need to call backend if not a valid dropzone target + if (!dropTarget) { + // Drop was not on a designated drop target - ignore return; } - console.log(`Wails Runtime: Drop on designated dropzone. Element at (${x}, ${y}):`, element, 'Effective dropzone:', dropzoneTarget); const elementDetails = { - id: dropzoneTarget.id, - classList: Array.from(dropzoneTarget.classList), + id: dropTarget.id, + classList: Array.from(dropTarget.classList), attributes: {} as { [key: string]: string }, }; - for (let i = 0; i < dropzoneTarget.attributes.length; i++) { - const attr = dropzoneTarget.attributes[i]; + for (let i = 0; i < dropTarget.attributes.length; i++) { + const attr = dropTarget.attributes[i]; elementDetails.attributes[attr.name] = attr.value; } @@ -573,7 +665,10 @@ class Window { elementDetails, }; - this[callerSym](WindowDropZoneDropped, payload); + this[callerSym](FilesDropped, payload); + + // Clean up native drag state after drop + cleanupNativeDrag(); } /* Triggers Windows 11 Snap Assist feature (Windows only). @@ -596,81 +691,146 @@ class Window { */ const thisWindow = new Window(''); -// NEW: Global Drag Event Listeners -function setupGlobalDropzoneListeners() { +/** + * Sets up global drag and drop event listeners for file drops. + * Handles visual feedback (hover state) and file drop processing. + */ +function setupDropTargetListeners() { const docElement = document.documentElement; - let dragEnterCounter = 0; // To handle dragenter/dragleave on child elements + let dragEnterCounter = 0; docElement.addEventListener('dragenter', (event) => { - event.preventDefault(); - if (event.dataTransfer && event.dataTransfer.types.includes('Files')) { - dragEnterCounter++; - const targetElement = document.elementFromPoint(event.clientX, event.clientY); - const dropzone = getDropzoneElement(targetElement); + if (!event.dataTransfer?.types.includes('Files')) { + return; // Only handle file drags, let other drags pass through + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + event.dataTransfer.dropEffect = 'none'; // Show "no drop" cursor + return; // File drops disabled, don't show hover effects + } + dragEnterCounter++; + + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); - // Clear previous hover regardless, then apply new if valid - if (currentHoveredDropzone && currentHoveredDropzone !== dropzone) { - currentHoveredDropzone.classList.remove(DROPZONE_HOVER_CLASS); - } + // Update hover state + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } - if (dropzone) { - dropzone.classList.add(DROPZONE_HOVER_CLASS); - event.dataTransfer.dropEffect = 'copy'; - currentHoveredDropzone = dropzone; - } else { - event.dataTransfer.dropEffect = 'none'; - currentHoveredDropzone = null; // Ensure it's cleared if no dropzone found - } + if (dropTarget) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); + event.dataTransfer.dropEffect = 'copy'; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = 'none'; + currentDropTarget = null; } }, false); docElement.addEventListener('dragover', (event) => { - event.preventDefault(); // Necessary to allow drop - if (event.dataTransfer && event.dataTransfer.types.includes('Files')) { - // No need to query elementFromPoint again if already handled by dragenter correctly - // Just ensure dropEffect is continuously set based on currentHoveredDropzone - if (currentHoveredDropzone) { - // Re-apply class just in case it was removed by some other JS - if(!currentHoveredDropzone.classList.contains(DROPZONE_HOVER_CLASS)) { - currentHoveredDropzone.classList.add(DROPZONE_HOVER_CLASS); - } - event.dataTransfer.dropEffect = 'copy'; - } else { - event.dataTransfer.dropEffect = 'none'; + if (!event.dataTransfer?.types.includes('Files')) { + return; // Only handle file drags + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + event.dataTransfer.dropEffect = 'none'; // Show "no drop" cursor + return; // File drops disabled, don't show hover effects + } + + // Update drop target as cursor moves + const targetElement = document.elementFromPoint(event.clientX, event.clientY); + const dropTarget = getDropTargetElement(targetElement); + + if (currentDropTarget && currentDropTarget !== dropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + } + + if (dropTarget) { + if (!dropTarget.classList.contains(DROP_TARGET_ACTIVE_CLASS)) { + dropTarget.classList.add(DROP_TARGET_ACTIVE_CLASS); } + event.dataTransfer.dropEffect = 'copy'; + currentDropTarget = dropTarget; + } else { + event.dataTransfer.dropEffect = 'none'; + currentDropTarget = null; } }, false); docElement.addEventListener('dragleave', (event) => { - event.preventDefault(); - if (event.dataTransfer && event.dataTransfer.types.includes('Files')) { - dragEnterCounter--; - // Only remove hover if drag truly left the window or the last dropzone - if (dragEnterCounter === 0 || event.relatedTarget === null || (currentHoveredDropzone && !currentHoveredDropzone.contains(event.relatedTarget as Node))) { - if (currentHoveredDropzone) { - currentHoveredDropzone.classList.remove(DROPZONE_HOVER_CLASS); - currentHoveredDropzone = null; - } - dragEnterCounter = 0; // Reset counter if it went negative or left window + if (!event.dataTransfer?.types.includes('Files')) { + return; + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; + } + + // On Linux/WebKitGTK and macOS, dragleave fires immediately with relatedTarget=null when native + // drag handling is involved. Ignore these spurious events - we'll clean up on drop instead. + if (event.relatedTarget === null) { + return; + } + + dragEnterCounter--; + + if (dragEnterCounter === 0 || + (currentDropTarget && !currentDropTarget.contains(event.relatedTarget as Node))) { + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; } + dragEnterCounter = 0; } }, false); docElement.addEventListener('drop', (event) => { - event.preventDefault(); // Prevent default browser file handling - dragEnterCounter = 0; // Reset counter - if (currentHoveredDropzone) { - currentHoveredDropzone.classList.remove(DROPZONE_HOVER_CLASS); - currentHoveredDropzone = null; + if (!event.dataTransfer?.types.includes('Files')) { + return; // Only handle file drops + } + event.preventDefault(); // Always prevent default to stop browser navigation + // On Windows, check if file drops are enabled for this window + if ((window as any)._wails?.flags?.enableFileDrop === false) { + return; + } + dragEnterCounter = 0; + + if (currentDropTarget) { + currentDropTarget.classList.remove(DROP_TARGET_ACTIVE_CLASS); + currentDropTarget = null; + } + + // On Windows, handle file drops via JavaScript + // On macOS/Linux, native code will call HandlePlatformFileDrop + if (canResolveFilePaths()) { + const files: File[] = []; + if (event.dataTransfer.items) { + for (const item of event.dataTransfer.items) { + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file) files.push(file); + } + } + } else if (event.dataTransfer.files) { + for (const file of event.dataTransfer.files) { + files.push(file); + } + } + + if (files.length > 0) { + resolveFilePaths(event.clientX, event.clientY, files); + } } - // The actual drop processing is initiated by the native side calling HandlePlatformFileDrop - // HandlePlatformFileDrop will then check if the drop was on a valid zone. }, false); } // Initialize listeners when the script loads if (typeof window !== "undefined" && typeof document !== "undefined") { - setupGlobalDropzoneListeners(); + setupDropTargetListeners(); } export default thisWindow; diff --git a/v3/internal/runtime/runtime.go b/v3/internal/runtime/runtime.go index 0c08108df..d35ff0552 100644 --- a/v3/internal/runtime/runtime.go +++ b/v3/internal/runtime/runtime.go @@ -1,11 +1,12 @@ package runtime import ( - "encoding/json" "fmt" + + "encoding/json" ) -var runtimeInit = `window._wails=window._wails||{};window.wails=window.wails||{};` +var runtimeInit = `window._wails=window._wails||{};window._wails.flags=window._wails.flags||{};window.wails=window.wails||{};` func Core(flags map[string]any) string { flagsStr := "" diff --git a/v3/internal/sliceutil/sliceutil.go b/v3/internal/sliceutil/sliceutil.go new file mode 100644 index 000000000..5023d6110 --- /dev/null +++ b/v3/internal/sliceutil/sliceutil.go @@ -0,0 +1,39 @@ +// Package sliceutil provides generic utility functions not available in stdlib. +// For most slice operations, use the standard library "slices" package directly. +// This package only contains functions that have no stdlib equivalent. +package sliceutil + +// Unique returns a new slice with duplicate elements removed. +// Preserves the order of first occurrence. +// The original slice is not modified. +// +// Unique returns a new slice containing the first occurrence of each element from the input slice, preserving their original order. +// If the input slice is nil, Unique returns nil. +// The original slice is not modified. +func Unique[T comparable](slice []T) []T { + if slice == nil { + return nil + } + seen := make(map[T]struct{}, len(slice)) + result := make([]T, 0, len(slice)) + for _, v := range slice { + if _, ok := seen[v]; !ok { + seen[v] = struct{}{} + result = append(result, v) + } + } + return result +} + +// FindMapKey returns the first key in map m whose value equals val. +// FindMapKey returns the first key in m whose value equals val. +// If no such key exists it returns the zero value of K and false. If multiple keys map to val, the returned key depends on Go's map iteration order. +func FindMapKey[K comparable, V comparable](m map[K]V, val V) (K, bool) { + for k, v := range m { + if v == val { + return k, true + } + } + var zero K + return zero, false +} \ No newline at end of file diff --git a/v3/internal/sliceutil/sliceutil_test.go b/v3/internal/sliceutil/sliceutil_test.go new file mode 100644 index 000000000..1b10cc123 --- /dev/null +++ b/v3/internal/sliceutil/sliceutil_test.go @@ -0,0 +1,172 @@ +package sliceutil + +import ( + "reflect" + "testing" +) + +func TestUnique(t *testing.T) { + tests := []struct { + name string + slice []int + want []int + }{ + { + name: "no duplicates", + slice: []int{1, 2, 3}, + want: []int{1, 2, 3}, + }, + { + name: "with duplicates", + slice: []int{1, 2, 2, 3, 3, 3}, + want: []int{1, 2, 3}, + }, + { + name: "all duplicates", + slice: []int{1, 1, 1}, + want: []int{1}, + }, + { + name: "preserves order", + slice: []int{3, 1, 2, 1, 3, 2}, + want: []int{3, 1, 2}, + }, + { + name: "single element", + slice: []int{1}, + want: []int{1}, + }, + { + name: "empty slice", + slice: []int{}, + want: []int{}, + }, + { + name: "nil slice", + slice: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Unique(tt.slice) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Unique() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnique_Strings(t *testing.T) { + slice := []string{"a", "b", "a", "c", "b"} + got := Unique(slice) + want := []string{"a", "b", "c"} + if !reflect.DeepEqual(got, want) { + t.Errorf("Unique() = %v, want %v", got, want) + } +} + +func TestUnique_DoesNotModifyOriginal(t *testing.T) { + original := []int{1, 2, 2, 3} + originalCopy := make([]int, len(original)) + copy(originalCopy, original) + + _ = Unique(original) + + if !reflect.DeepEqual(original, originalCopy) { + t.Errorf("Unique() modified original slice: got %v, want %v", original, originalCopy) + } +} + +func TestFindMapKey(t *testing.T) { + tests := []struct { + name string + m map[string]int + val int + wantKey string + wantFound bool + }{ + { + name: "find existing value", + m: map[string]int{"a": 1, "b": 2, "c": 3}, + val: 2, + wantKey: "b", + wantFound: true, + }, + { + name: "value not found", + m: map[string]int{"a": 1, "b": 2}, + val: 3, + wantKey: "", + wantFound: false, + }, + { + name: "empty map", + m: map[string]int{}, + val: 1, + wantKey: "", + wantFound: false, + }, + { + name: "nil map", + m: nil, + val: 1, + wantKey: "", + wantFound: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotKey, gotFound := FindMapKey(tt.m, tt.val) + if gotFound != tt.wantFound { + t.Errorf("FindMapKey() found = %v, want %v", gotFound, tt.wantFound) + } + if gotFound && gotKey != tt.wantKey { + t.Errorf("FindMapKey() key = %v, want %v", gotKey, tt.wantKey) + } + }) + } +} + +func TestFindMapKey_DuplicateValues(t *testing.T) { + // When multiple keys have the same value, any matching key is acceptable + m := map[string]int{"a": 1, "b": 1, "c": 2} + key, found := FindMapKey(m, 1) + if !found { + t.Error("FindMapKey() should find a key") + } + if key != "a" && key != "b" { + t.Errorf("FindMapKey() = %v, want 'a' or 'b'", key) + } +} + +func TestFindMapKey_IntKeys(t *testing.T) { + m := map[int]string{1: "one", 2: "two", 3: "three"} + key, found := FindMapKey(m, "two") + if !found || key != 2 { + t.Errorf("FindMapKey() = (%v, %v), want (2, true)", key, found) + } +} + +// Benchmarks + +func BenchmarkUnique(b *testing.B) { + slice := []int{1, 2, 3, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 10} + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Unique(slice) + } +} + +func BenchmarkFindMapKey(b *testing.B) { + m := make(map[string]int) + for i := 0; i < 100; i++ { + m[string(rune('a'+i))] = i + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindMapKey(m, 50) + } +} diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index f24592cb0..d0db23e95 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.50 \ No newline at end of file +v3.0.0-alpha.62 \ No newline at end of file diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 5a8c6f068..8483f8166 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -151,7 +151,7 @@ func New(appOptions Options) *App { if err != nil { result.fatal("failed to configure transport for serving assets: %w", err) } - result.info("Transport configured to serve assets") + result.debug("Transport configured to serve assets") } result.bindings = NewBindings(appOptions.MarshalError, appOptions.BindAliases) @@ -239,9 +239,9 @@ type OriginInfo struct { var windowMessageBuffer = make(chan *windowMessage, 5) -// DropZoneDetails contains information about the HTML element -// at the location of a file drop. -type DropZoneDetails struct { +// DropTargetDetails contains information about the HTML element +// where files were dropped (the element with data-file-drop-target attribute). +type DropTargetDetails struct { X int `json:"x"` Y int `json:"y"` ElementID string `json:"id"` @@ -250,20 +250,20 @@ type DropZoneDetails struct { } type dragAndDropMessage struct { - windowId uint - filenames []string - X int - Y int - DropZone *DropZoneDetails + windowId uint + filenames []string + X int + Y int + DropTarget *DropTargetDetails } var windowDragAndDropBuffer = make(chan *dragAndDropMessage, 5) -func addDragAndDropMessage(windowId uint, filenames []string, dropZone *DropZoneDetails) { +func addDragAndDropMessage(windowId uint, filenames []string, dropTarget *DropTargetDetails) { windowDragAndDropBuffer <- &dragAndDropMessage{ - windowId: windowId, - filenames: filenames, - DropZone: dropZone, + windowId: windowId, + filenames: filenames, + DropTarget: dropTarget, } } @@ -619,11 +619,6 @@ func (a *App) Run() error { go func() { for { dragAndDropMessage := <-windowDragAndDropBuffer - a.Logger.Debug( - "[DragDropDebug] App.Run: Received message from windowDragAndDropBuffer", - "message", - fmt.Sprintf("%+v", dragAndDropMessage), - ) go a.handleDragAndDropMessage(dragAndDropMessage) } }() @@ -715,13 +710,7 @@ func (a *App) shutdownServices() { } func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) { - a.Logger.Debug( - "[DragDropDebug] App.handleDragAndDropMessage: Called with event", - "event", - fmt.Sprintf("%+v", event), - ) defer handlePanic() - // Get window from window map a.windowsLock.Lock() window, ok := a.windows[event.windowId] a.windowsLock.Unlock() @@ -729,13 +718,7 @@ func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) { a.warning("WebviewWindow #%d not found", event.windowId) return } - // Get callback from window - a.Logger.Debug( - "[DragDropDebug] App.handleDragAndDropMessage: Calling window.HandleDragAndDropMessage", - "windowID", - event.windowId, - ) - window.HandleDragAndDropMessage(event.filenames, event.DropZone) + window.handleDragAndDropMessage(event.filenames, event.DropTarget) } func (a *App) handleWindowMessage(event *windowMessage) { @@ -750,7 +733,7 @@ func (a *App) handleWindowMessage(event *windowMessage) { } a.windowsLock.RUnlock() - a.info("handleWindowMessage: Looking for window", "windowId", event.windowId, "availableIDs", ids) + a.debug("handleWindowMessage: Looking for window", "windowId", event.windowId, "availableIDs", ids) if !ok { a.warning("WebviewWindow #%d not found", event.windowId) @@ -758,7 +741,7 @@ func (a *App) handleWindowMessage(event *windowMessage) { } // Check if the message starts with "wails:" if strings.HasPrefix(event.message, "wails:") { - a.info("handleWindowMessage: Processing wails message", "message", event.message) + a.debug("handleWindowMessage: Processing wails message", "message", event.message) window.HandleMessage(event.message) } else { if a.options.RawMessageHandler != nil { @@ -771,10 +754,10 @@ func (a *App) handleWebViewRequest(request *webViewAssetRequest) { defer handlePanic() // Log that we're processing the request url, _ := request.Request.URL() - a.info("handleWebViewRequest: Processing request", "url", url) + a.debug("handleWebViewRequest: Processing request", "url", url) // IMPORTANT: pass the wrapper request so our injected headers (x-wails-window-id/name) are used a.assets.ServeWebViewRequest(request) - a.info("handleWebViewRequest: Request processing complete", "url", url) + a.debug("handleWebViewRequest: Request processing complete", "url", url) } func (a *App) handleWindowEvent(event *windowEvent) { diff --git a/v3/pkg/application/application_android.go b/v3/pkg/application/application_android.go index 46a45fc17..87bb1b2f4 100644 --- a/v3/pkg/application/application_android.go +++ b/v3/pkg/application/application_android.go @@ -139,7 +139,6 @@ static void executeJavaScriptOnBridge(const char* js) { import "C" import ( - "encoding/json" "fmt" "io" "net/http" @@ -149,6 +148,8 @@ import ( "time" "unsafe" + "encoding/json" + "github.com/wailsapp/wails/v3/internal/runtime" ) diff --git a/v3/pkg/application/application_android_nocgo.go b/v3/pkg/application/application_android_nocgo.go index cd897031e..ea769f3e1 100644 --- a/v3/pkg/application/application_android_nocgo.go +++ b/v3/pkg/application/application_android_nocgo.go @@ -3,10 +3,11 @@ package application import ( - "encoding/json" "fmt" "sync" "unsafe" + + "encoding/json" ) var ( diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index b70f98870..f5738f93f 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -196,12 +196,14 @@ static void startSingleInstanceListener(const char *uniqueID) { */ import "C" import ( - "encoding/json" + "sync" + "time" "unsafe" - "github.com/wailsapp/wails/v3/internal/operatingsystem" + "encoding/json" "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/operatingsystem" "github.com/wailsapp/wails/v3/pkg/events" ) @@ -414,6 +416,254 @@ func processDragItems(windowID C.uint, arr **C.char, length C.int, x C.int, y C. targetWindow.InitiateFrontendDropProcessing(filenames, int(x), int(y)) } +//export macosOnDragEnter +func macosOnDragEnter(windowID C.uint) { + window, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || window == nil { + return + } + + // Call JavaScript to show drag entered state + window.ExecJS("window._wails.handleDragEnter();") +} + +//export macosOnDragExit +func macosOnDragExit(windowID C.uint) { + window, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || window == nil { + return + } + + // Call JavaScript to clean up drag state + window.ExecJS("window._wails.handleDragLeave();") +} + +var ( + // Pre-allocated buffer for drag JS calls to avoid allocations + dragOverJSBuffer = make([]byte, 128) // Increased for safety + dragOverJSMutex sync.Mutex // Protects dragOverJSBuffer + dragOverJSPrefix = []byte("window._wails.handleDragOver(") + + // Cache window references to avoid repeated lookups + windowImplCache sync.Map // windowID -> *macosWebviewWindow + + // Per-window drag throttle state + dragThrottle sync.Map // windowID -> *dragThrottleState +) + +type dragThrottleState struct { + mu sync.Mutex // Protects all fields below + lastX, lastY int + timer *time.Timer + pendingX int + pendingY int + hasPending bool +} + +// clearWindowDragCache removes cached references for a window +func clearWindowDragCache(windowID uint) { + windowImplCache.Delete(windowID) + + // Cancel any pending timer + if throttleVal, ok := dragThrottle.Load(windowID); ok { + if throttle, ok := throttleVal.(*dragThrottleState); ok { + throttle.mu.Lock() + if throttle.timer != nil { + throttle.timer.Stop() + } + throttle.mu.Unlock() + } + } + dragThrottle.Delete(windowID) +} + +// writeInt writes an integer to a byte slice and returns the number of bytes written +func writeInt(buf []byte, n int) int { + if n < 0 { + if len(buf) == 0 { + return 0 + } + buf[0] = '-' + return 1 + writeInt(buf[1:], -n) + } + if n == 0 { + if len(buf) == 0 { + return 0 + } + buf[0] = '0' + return 1 + } + + // Count digits + tmp := n + digits := 0 + for tmp > 0 { + digits++ + tmp /= 10 + } + + // Bounds check + if digits > len(buf) { + return 0 + } + + // Write digits in reverse + for i := digits - 1; i >= 0; i-- { + buf[i] = byte('0' + n%10) + n /= 10 + } + return digits +} + +//export macosOnDragOver +func macosOnDragOver(windowID C.uint, x C.int, y C.int) { + winID := uint(windowID) + intX, intY := int(x), int(y) + + // Get or create throttle state + throttleKey := winID + throttleVal, _ := dragThrottle.LoadOrStore(throttleKey, &dragThrottleState{ + lastX: intX, + lastY: intY, + }) + throttle := throttleVal.(*dragThrottleState) + + throttle.mu.Lock() + + // Update pending position + throttle.pendingX = intX + throttle.pendingY = intY + throttle.hasPending = true + + // If timer is already running, just update the pending position + if throttle.timer != nil { + throttle.mu.Unlock() + return + } + + // Apply 5-pixel threshold for immediate update + dx := intX - throttle.lastX + dy := intY - throttle.lastY + if dx < 0 { + dx = -dx + } + if dy < 0 { + dy = -dy + } + + // Check if we should send an immediate update + shouldSendNow := dx >= 5 || dy >= 5 + + if shouldSendNow { + // Update last position + throttle.lastX = intX + throttle.lastY = intY + throttle.hasPending = false + + // Send this update immediately (unlock before JS call to avoid deadlock) + throttle.mu.Unlock() + sendDragUpdate(winID, intX, intY) + throttle.mu.Lock() + } + + // Start 50ms timer for next update (whether we sent now or not) + throttle.timer = time.AfterFunc(50*time.Millisecond, func() { + // Execute on main thread to ensure UI updates + InvokeSync(func() { + throttle.mu.Lock() + // Clear timer reference + throttle.timer = nil + + // Send pending update if any + if throttle.hasPending { + pendingX, pendingY := throttle.pendingX, throttle.pendingY + throttle.lastX = pendingX + throttle.lastY = pendingY + throttle.hasPending = false + throttle.mu.Unlock() + sendDragUpdate(winID, pendingX, pendingY) + } else { + throttle.mu.Unlock() + } + }) + }) + throttle.mu.Unlock() +} + +// sendDragUpdate sends the actual drag update to JavaScript +func sendDragUpdate(winID uint, x, y int) { + // Try cached implementation first + var darwinImpl *macosWebviewWindow + var needsExecJS bool + + if cached, found := windowImplCache.Load(winID); found { + darwinImpl = cached.(*macosWebviewWindow) + if darwinImpl != nil && darwinImpl.nsWindow != nil { + needsExecJS = true + } else { + // Invalid cache entry, remove it + windowImplCache.Delete(winID) + } + } + + if !needsExecJS { + // Fallback to full lookup + window, ok := globalApplication.Window.GetByID(winID) + if !ok || window == nil { + return + } + + // Type assert to WebviewWindow + webviewWindow, ok := window.(*WebviewWindow) + if !ok || webviewWindow == nil { + return + } + + // Get implementation + darwinImpl, ok = webviewWindow.impl.(*macosWebviewWindow) + if !ok { + return + } + + // Cache for next time + windowImplCache.Store(winID, darwinImpl) + needsExecJS = true + } + + if !needsExecJS || darwinImpl == nil { + return + } + + // Protect shared buffer access + dragOverJSMutex.Lock() + + // Build JS string with zero allocations + // Format: "window._wails.handleDragOver(X,Y)" + // Max length with int32 coords: 30 + 11 + 1 + 11 + 1 + 1 = 55 bytes + n := copy(dragOverJSBuffer[:], dragOverJSPrefix) + n += writeInt(dragOverJSBuffer[n:], x) + if n < len(dragOverJSBuffer) { + dragOverJSBuffer[n] = ',' + n++ + } + n += writeInt(dragOverJSBuffer[n:], y) + if n < len(dragOverJSBuffer) { + dragOverJSBuffer[n] = ')' + n++ + } + if n < len(dragOverJSBuffer) { + dragOverJSBuffer[n] = 0 // null terminate for C + } else { + // Buffer overflow - this should not happen with 128 byte buffer + dragOverJSMutex.Unlock() + return + } + + // Call JavaScript with zero allocations + darwinImpl.execJSDragOver(dragOverJSBuffer[:n+1]) // Include null terminator + dragOverJSMutex.Unlock() +} + //export processMenuItemClick func processMenuItemClick(menuID C.uint) { menuItemClicked <- uint(menuID) diff --git a/v3/pkg/application/application_ios.go b/v3/pkg/application/application_ios.go index 8965f2afb..90e896b07 100644 --- a/v3/pkg/application/application_ios.go +++ b/v3/pkg/application/application_ios.go @@ -15,12 +15,13 @@ package application import "C" import ( - "encoding/json" "fmt" "strings" "time" "unsafe" + "encoding/json" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" "github.com/wailsapp/wails/v3/pkg/events" ) @@ -107,6 +108,12 @@ type iosApp struct { parent *App } +// newPlatformApp creates an iosApp for the provided App and applies iOS-specific +// configuration derived from app.options. It sets input accessory visibility, +// scrolling/bounce/indicator behavior, navigation gestures, link preview, +// media playback, inspector, user agent strings, app background color, and +// native tabs (marshaling items to JSON when enabled). The function invokes +// platform bindings to apply these settings and returns the configured *iosApp. func newPlatformApp(app *App) *iosApp { iosConsoleLogf("info", "🔵 [application_ios.go] START newPlatformApp()") // iOS initialization @@ -161,19 +168,19 @@ func newPlatformApp(app *App) *iosApp { // Ensure it's marked as not set to allow delegate to fallback to white C.ios_set_app_background_color(255, 255, 255, 255, C.bool(false)) } - // Native tabs option: only enable when explicitly requested - if app.options.IOS.EnableNativeTabs { - if len(app.options.IOS.NativeTabsItems) > 0 { - if data, err := json.Marshal(app.options.IOS.NativeTabsItems); err == nil { - cjson := C.CString(string(data)) - C.ios_native_tabs_set_items_json(cjson) - C.free(unsafe.Pointer(cjson)) - } else if globalApplication != nil { - globalApplication.error("Failed to marshal IOS.NativeTabsItems: %v", err) - } - } - C.ios_native_tabs_set_enabled(C.bool(true)) - } + // Native tabs option: only enable when explicitly requested + if app.options.IOS.EnableNativeTabs { + if len(app.options.IOS.NativeTabsItems) > 0 { + if data, err := json.Marshal(app.options.IOS.NativeTabsItems); err == nil { + cjson := C.CString(string(data)) + C.ios_native_tabs_set_items_json(cjson) + C.free(unsafe.Pointer(cjson)) + } else if globalApplication != nil { + globalApplication.error("Failed to marshal IOS.NativeTabsItems: %v", err) + } + } + C.ios_native_tabs_set_enabled(C.bool(true)) + } iosConsoleLogf("info", "🔵 [application_ios.go] END newPlatformApp() - iosApp created") return result @@ -452,4 +459,4 @@ func hasListeners(eventID C.uint) C.bool { // For now, return true to enable all events // TODO: Check actual listener registration return C.bool(true) -} +} \ No newline at end of file diff --git a/v3/pkg/application/application_linux.go b/v3/pkg/application/application_linux.go index f0e14f47d..04924a6c9 100644 --- a/v3/pkg/application/application_linux.go +++ b/v3/pkg/application/application_linux.go @@ -144,7 +144,7 @@ func (a *linuxApp) run() error { arg1 := os.Args[1] // Check if the argument is likely a URL from a custom protocol invocation if strings.Contains(arg1, "://") { - a.parent.info("Application launched with argument, potentially a URL from custom protocol", "url", arg1) + a.parent.debug("Application launched with argument, potentially a URL from custom protocol", "url", arg1) eventContext := newApplicationEventContext() eventContext.setURL(arg1) applicationEvents <- &ApplicationEvent{ @@ -156,7 +156,7 @@ func (a *linuxApp) run() error { if a.parent.options.FileAssociations != nil { ext := filepath.Ext(arg1) if slices.Contains(a.parent.options.FileAssociations, ext) { - a.parent.info("File opened via file association", "file", arg1, "extension", ext) + a.parent.debug("File opened via file association", "file", arg1, "extension", ext) eventContext := newApplicationEventContext() eventContext.setOpenedWithFile(arg1) applicationEvents <- &ApplicationEvent{ @@ -166,11 +166,11 @@ func (a *linuxApp) run() error { return nil } } - a.parent.info("Application launched with single argument (not a URL), potential file open?", "arg", arg1) + a.parent.debug("Application launched with single argument (not a URL), potential file open?", "arg", arg1) } } else if len(os.Args) > 2 { // Log if multiple arguments are passed - a.parent.info("Application launched with multiple arguments", "args", os.Args[1:]) + a.parent.debug("Application launched with multiple arguments", "args", os.Args[1:]) } a.parent.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(evt *ApplicationEvent) { @@ -226,8 +226,8 @@ func (a *linuxApp) monitorThemeChanges() { defer handlePanic() conn, err := dbus.ConnectSessionBus() if err != nil { - a.parent.info( - "[WARNING] Failed to connect to session bus; monitoring for theme changes will not function:", + a.parent.warning( + "[WARNING] Failed to connect to session bus; monitoring for theme changes will not function: %v", err, ) return diff --git a/v3/pkg/application/application_options_test.go b/v3/pkg/application/application_options_test.go new file mode 100644 index 000000000..77ddbe522 --- /dev/null +++ b/v3/pkg/application/application_options_test.go @@ -0,0 +1,301 @@ +package application + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestActivationPolicy_Constants(t *testing.T) { + if ActivationPolicyRegular != 0 { + t.Error("ActivationPolicyRegular should be 0") + } + if ActivationPolicyAccessory != 1 { + t.Error("ActivationPolicyAccessory should be 1") + } + if ActivationPolicyProhibited != 2 { + t.Error("ActivationPolicyProhibited should be 2") + } +} + +func TestNativeTabIcon_Constants(t *testing.T) { + tests := []struct { + name string + icon NativeTabIcon + expected string + }{ + {"NativeTabIconNone", NativeTabIconNone, ""}, + {"NativeTabIconHouse", NativeTabIconHouse, "house"}, + {"NativeTabIconGear", NativeTabIconGear, "gear"}, + {"NativeTabIconStar", NativeTabIconStar, "star"}, + {"NativeTabIconPerson", NativeTabIconPerson, "person"}, + {"NativeTabIconBell", NativeTabIconBell, "bell"}, + {"NativeTabIconMagnify", NativeTabIconMagnify, "magnifyingglass"}, + {"NativeTabIconList", NativeTabIconList, "list.bullet"}, + {"NativeTabIconFolder", NativeTabIconFolder, "folder"}, + } + + for _, tt := range tests { + if string(tt.icon) != tt.expected { + t.Errorf("%s = %q, want %q", tt.name, string(tt.icon), tt.expected) + } + } +} + +func TestChainMiddleware_Empty(t *testing.T) { + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("base")) + }) + + chained := ChainMiddleware() + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("Status = %d, want %d", rec.Code, http.StatusOK) + } + if rec.Body.String() != "base" { + t.Errorf("Body = %q, want %q", rec.Body.String(), "base") + } +} + +func TestChainMiddleware_Single(t *testing.T) { + callOrder := []string{} + + middleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware") + next.ServeHTTP(w, r) + }) + } + + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "base") + w.WriteHeader(http.StatusOK) + }) + + chained := ChainMiddleware(middleware) + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + if len(callOrder) != 2 { + t.Errorf("Expected 2 calls, got %d", len(callOrder)) + } + if callOrder[0] != "middleware" { + t.Errorf("First call should be middleware, got %s", callOrder[0]) + } + if callOrder[1] != "base" { + t.Errorf("Second call should be base, got %s", callOrder[1]) + } +} + +func TestChainMiddleware_Multiple(t *testing.T) { + callOrder := []string{} + + middleware1 := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware1") + next.ServeHTTP(w, r) + }) + } + + middleware2 := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware2") + next.ServeHTTP(w, r) + }) + } + + middleware3 := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "middleware3") + next.ServeHTTP(w, r) + }) + } + + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callOrder = append(callOrder, "base") + w.WriteHeader(http.StatusOK) + }) + + chained := ChainMiddleware(middleware1, middleware2, middleware3) + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + expected := []string{"middleware1", "middleware2", "middleware3", "base"} + if len(callOrder) != len(expected) { + t.Errorf("Expected %d calls, got %d", len(expected), len(callOrder)) + } + for i, v := range expected { + if i < len(callOrder) && callOrder[i] != v { + t.Errorf("Call %d: expected %s, got %s", i, v, callOrder[i]) + } + } +} + +func TestOptions_Defaults(t *testing.T) { + opts := Options{} + + if opts.Name != "" { + t.Error("Name should default to empty string") + } + if opts.Description != "" { + t.Error("Description should default to empty string") + } + if opts.Icon != nil { + t.Error("Icon should default to nil") + } + if opts.Logger != nil { + t.Error("Logger should default to nil") + } + if opts.DisableDefaultSignalHandler != false { + t.Error("DisableDefaultSignalHandler should default to false") + } +} + +func TestMacOptions_Defaults(t *testing.T) { + opts := MacOptions{} + + if opts.ActivationPolicy != ActivationPolicyRegular { + t.Error("ActivationPolicy should default to ActivationPolicyRegular") + } + if opts.ApplicationShouldTerminateAfterLastWindowClosed != false { + t.Error("ApplicationShouldTerminateAfterLastWindowClosed should default to false") + } +} + +func TestWindowsOptions_Defaults(t *testing.T) { + opts := WindowsOptions{} + + if opts.WndClass != "" { + t.Error("WndClass should default to empty string") + } + if opts.DisableQuitOnLastWindowClosed != false { + t.Error("DisableQuitOnLastWindowClosed should default to false") + } + if opts.WebviewUserDataPath != "" { + t.Error("WebviewUserDataPath should default to empty string") + } + if opts.WebviewBrowserPath != "" { + t.Error("WebviewBrowserPath should default to empty string") + } +} + +func TestLinuxOptions_Defaults(t *testing.T) { + opts := LinuxOptions{} + + if opts.DisableQuitOnLastWindowClosed != false { + t.Error("DisableQuitOnLastWindowClosed should default to false") + } + if opts.ProgramName != "" { + t.Error("ProgramName should default to empty string") + } +} + +func TestIOSOptions_Defaults(t *testing.T) { + opts := IOSOptions{} + + if opts.DisableInputAccessoryView != false { + t.Error("DisableInputAccessoryView should default to false") + } + if opts.DisableScroll != false { + t.Error("DisableScroll should default to false") + } + if opts.DisableBounce != false { + t.Error("DisableBounce should default to false") + } + if opts.EnableBackForwardNavigationGestures != false { + t.Error("EnableBackForwardNavigationGestures should default to false") + } + if opts.EnableNativeTabs != false { + t.Error("EnableNativeTabs should default to false") + } +} + +func TestAndroidOptions_Defaults(t *testing.T) { + opts := AndroidOptions{} + + if opts.DisableScroll != false { + t.Error("DisableScroll should default to false") + } + if opts.DisableOverscroll != false { + t.Error("DisableOverscroll should default to false") + } + if opts.EnableZoom != false { + t.Error("EnableZoom should default to false") + } + if opts.DisableHardwareAcceleration != false { + t.Error("DisableHardwareAcceleration should default to false") + } +} + +func TestAssetOptions_Defaults(t *testing.T) { + opts := AssetOptions{} + + if opts.Handler != nil { + t.Error("Handler should default to nil") + } + if opts.Middleware != nil { + t.Error("Middleware should default to nil") + } + if opts.DisableLogging != false { + t.Error("DisableLogging should default to false") + } +} + +func TestNativeTabItem_Fields(t *testing.T) { + item := NativeTabItem{ + Title: "Home", + SystemImage: NativeTabIconHouse, + } + + if item.Title != "Home" { + t.Errorf("Title = %q, want %q", item.Title, "Home") + } + if item.SystemImage != NativeTabIconHouse { + t.Errorf("SystemImage = %q, want %q", item.SystemImage, NativeTabIconHouse) + } +} + +func TestMiddleware_ShortCircuit(t *testing.T) { + shortCircuit := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("forbidden")) + // Don't call next + }) + } + + baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("base")) + }) + + chained := ChainMiddleware(shortCircuit) + handler := chained(baseHandler) + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusForbidden { + t.Errorf("Status = %d, want %d", rec.Code, http.StatusForbidden) + } + if rec.Body.String() != "forbidden" { + t.Errorf("Body = %q, want %q", rec.Body.String(), "forbidden") + } +} diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go index 364bb902c..09dad6acf 100644 --- a/v3/pkg/application/application_windows.go +++ b/v3/pkg/application/application_windows.go @@ -160,7 +160,7 @@ func (m *windowsApp) run() error { arg1 := os.Args[1] // Check if the argument is likely a URL from a custom protocol invocation if strings.Contains(arg1, "://") { - m.parent.info("Application launched with argument, potentially a URL from custom protocol", "url", arg1) + m.parent.debug("Application launched with argument, potentially a URL from custom protocol", "url", arg1) eventContext := newApplicationEventContext() eventContext.setURL(arg1) applicationEvents <- &ApplicationEvent{ @@ -172,7 +172,7 @@ func (m *windowsApp) run() error { if m.parent.options.FileAssociations != nil { ext := filepath.Ext(arg1) if slices.Contains(m.parent.options.FileAssociations, ext) { - m.parent.info("Application launched with file via file association", "file", arg1) + m.parent.debug("Application launched with file via file association", "file", arg1) eventContext := newApplicationEventContext() eventContext.setOpenedWithFile(arg1) applicationEvents <- &ApplicationEvent{ @@ -184,7 +184,7 @@ func (m *windowsApp) run() error { } } else if len(os.Args) > 2 { // Log if multiple arguments are passed, though typical protocol/file launch is a single arg. - m.parent.info("Application launched with multiple arguments", "args", os.Args[1:]) + m.parent.debug("Application launched with multiple arguments", "args", os.Args[1:]) } _ = m.runMainLoop() @@ -358,6 +358,18 @@ func setupDPIAwareness() error { // https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process // https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows + // Check if DPI awareness has already been set (e.g., via application manifest). + // Windows only allows setting DPI awareness once per process - either via manifest + // or API, not both. If already set, skip the API call to avoid "Access is denied" errors. + // See: https://github.com/wailsapp/wails/issues/4803 + if w32.HasGetProcessDpiAwarenessFunc() { + awareness, err := w32.GetProcessDpiAwareness() + if err == nil && awareness != w32.PROCESS_DPI_UNAWARE { + // DPI awareness already set (likely via manifest), skip API call + return nil + } + } + if w32.HasSetProcessDpiAwarenessContextFunc() { // This is most recent version with the best results // supported beginning with Windows 10, version 1703 diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go index 431caab62..e16948399 100644 --- a/v3/pkg/application/bindings.go +++ b/v3/pkg/application/bindings.go @@ -10,14 +10,15 @@ import ( "strings" "github.com/wailsapp/wails/v3/internal/hash" - - "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/sliceutil" ) +// CallOptions defines the options for a method call. +// Field order is optimized to minimize struct padding. type CallOptions struct { - MethodID uint32 `json:"methodID"` MethodName string `json:"methodName"` Args []json.RawMessage `json:"args"` + MethodID uint32 `json:"methodID"` } type ErrorKind string @@ -28,10 +29,12 @@ const ( RuntimeError ErrorKind = "RuntimeError" ) +// CallError represents an error that occurred during a method call. +// Field order is optimized to minimize struct padding. type CallError struct { - Kind ErrorKind `json:"kind"` Message string `json:"message"` Cause any `json:"cause,omitempty"` + Kind ErrorKind `json:"kind"` } func (e *CallError) Error() string { @@ -64,18 +67,19 @@ func (p *Parameter) IsError() bool { } // BoundMethod defines all the data related to a Go method that is -// bound to the Wails application +// bound to the Wails application. +// Field order is optimized to minimize struct padding (136 bytes vs 144 bytes). type BoundMethod struct { - ID uint32 `json:"id"` - Name string `json:"name"` - Inputs []*Parameter `json:"inputs,omitempty"` - Outputs []*Parameter `json:"outputs,omitempty"` - Comments string `json:"comments,omitempty"` - Method reflect.Value `json:"-"` - FQN string - + Method reflect.Value `json:"-"` + Name string `json:"name"` + FQN string `json:"-"` + Comments string `json:"comments,omitempty"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` marshalError func(error) []byte + ID uint32 `json:"id"` needsContext bool + isVariadic bool // cached at registration to avoid reflect call per invocation } type Bindings struct { @@ -114,7 +118,7 @@ func (b *Bindings) Add(service Service) error { // Log attrs := []any{"fqn", method.FQN, "id", method.ID} - if alias, ok := lo.FindKey(b.methodAliases, method.ID); ok { + if alias, ok := sliceutil.FindMapKey(b.methodAliases, method.ID); ok { attrs = append(attrs, "alias", alias) } globalApplication.debug("Registering bound method:", attrs...) @@ -165,6 +169,10 @@ var internalServiceMethods = map[string]bool{ var ctxType = reflect.TypeFor[context.Context]() +// getMethods returns the list of BoundMethod descriptors for the methods of the named pointer type provided by value. +// +// It returns an error if value is not a pointer to a named type, if a function value is supplied (binding functions is deprecated), or if a generic type is supplied. +// The returned BoundMethod slice includes only exported methods that are not listed in internalServiceMethods. Each BoundMethod has its FQN, ID (computed from the FQN), Method reflect.Value, Inputs and Outputs populated, isVariadic cached from the method signature, and needsContext set when the first parameter is context.Context. func getMethods(value any) ([]*BoundMethod, error) { // Create result placeholder var result []*BoundMethod @@ -203,19 +211,20 @@ func getMethods(value any) ([]*BoundMethod, error) { fqn := fmt.Sprintf("%s.%s.%s", packagePath, typeName, methodName) - // Create new method - boundMethod := &BoundMethod{ - ID: hash.Fnv(fqn), - FQN: fqn, - Name: methodName, - Inputs: nil, - Outputs: nil, - Comments: "", - Method: method, - } - // Iterate inputs methodType := method.Type() + + // Create new method with cached flags + boundMethod := &BoundMethod{ + ID: hash.Fnv(fqn), + FQN: fqn, + Name: methodName, + Inputs: nil, + Outputs: nil, + Comments: "", + Method: method, + isVariadic: methodType.IsVariadic(), // cache to avoid reflect call per invocation + } inputParamCount := methodType.NumIn() var inputs []*Parameter for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ { @@ -268,14 +277,20 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result if argCount != len(b.Inputs) { err = &CallError{ - Kind: TypeError, Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount), + Kind: TypeError, } return } - // Convert inputs to values of appropriate type - callArgs := make([]reflect.Value, argCount) + // Use stack-allocated buffer for common case (<=8 args), heap for larger + var argBuffer [8]reflect.Value + var callArgs []reflect.Value + if argCount <= len(argBuffer) { + callArgs = argBuffer[:argCount] + } else { + callArgs = make([]reflect.Value, argCount) + } base := 0 if b.needsContext { @@ -289,24 +304,28 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result err = json.Unmarshal(arg, value.Interface()) if err != nil { err = &CallError{ - Kind: TypeError, Message: fmt.Sprintf("could not parse argument #%d: %s", index, err), Cause: json.RawMessage(b.marshalError(err)), + Kind: TypeError, } return } callArgs[base+index] = value.Elem() } - // Do the call + // Do the call using cached isVariadic flag var callResults []reflect.Value - if b.Method.Type().IsVariadic() { + if b.isVariadic { callResults = b.Method.CallSlice(callArgs) } else { callResults = b.Method.Call(callArgs) } - var nonErrorOutputs = make([]any, 0, len(callResults)) + // Process results - optimized for common case of 0-2 return values + // to avoid slice allocation + var firstResult any + var hasFirstResult bool + var nonErrorOutputs []any // only allocated if >1 non-error results var errorOutputs []error for _, field := range callResults { @@ -315,12 +334,22 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result continue } if errorOutputs == nil { - errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs)) - nonErrorOutputs = nil + errorOutputs = make([]error, 0, len(callResults)) } errorOutputs = append(errorOutputs, field.Interface().(error)) - } else if nonErrorOutputs != nil { - nonErrorOutputs = append(nonErrorOutputs, field.Interface()) + } else if errorOutputs == nil { + // Only collect non-error outputs if no errors yet + val := field.Interface() + if !hasFirstResult { + firstResult = val + hasFirstResult = true + } else if nonErrorOutputs == nil { + // Second result - need to allocate slice + nonErrorOutputs = make([]any, 0, len(callResults)) + nonErrorOutputs = append(nonErrorOutputs, firstResult, val) + } else { + nonErrorOutputs = append(nonErrorOutputs, val) + } } } @@ -331,19 +360,19 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result } cerr := &CallError{ - Kind: RuntimeError, Message: errors.Join(errorOutputs...).Error(), Cause: info, + Kind: RuntimeError, } if len(info) == 1 { cerr.Cause = info[0] } err = cerr - } else if len(nonErrorOutputs) == 1 { - result = nonErrorOutputs[0] - } else if len(nonErrorOutputs) > 1 { + } else if nonErrorOutputs != nil { result = nonErrorOutputs + } else if hasFirstResult { + result = firstResult } return diff --git a/v3/pkg/application/bindings_bench_test.go b/v3/pkg/application/bindings_bench_test.go new file mode 100644 index 000000000..2e23cc92d --- /dev/null +++ b/v3/pkg/application/bindings_bench_test.go @@ -0,0 +1,506 @@ +//go:build bench + +package application_test + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/wailsapp/wails/v3/internal/hash" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// BenchmarkService provides methods with varying complexity for benchmarking +type BenchmarkService struct{} + +func (s *BenchmarkService) NoArgs() {} + +func (s *BenchmarkService) StringArg(str string) string { + return str +} + +func (s *BenchmarkService) IntArg(i int) int { + return i +} + +func (s *BenchmarkService) MultipleArgs(s1 string, i int, b bool) (string, int, bool) { + return s1, i, b +} + +func (s *BenchmarkService) StructArg(p BenchPerson) BenchPerson { + return p +} + +func (s *BenchmarkService) ComplexStruct(c ComplexData) ComplexData { + return c +} + +func (s *BenchmarkService) SliceArg(items []int) []int { + return items +} + +func (s *BenchmarkService) VariadicArg(items ...string) []string { + return items +} + +func (s *BenchmarkService) WithContext(ctx context.Context, s1 string) string { + return s1 +} + +func (s *BenchmarkService) Method1() {} +func (s *BenchmarkService) Method2() {} +func (s *BenchmarkService) Method3() {} +func (s *BenchmarkService) Method4() {} +func (s *BenchmarkService) Method5() {} +func (s *BenchmarkService) Method6() {} +func (s *BenchmarkService) Method7() {} +func (s *BenchmarkService) Method8() {} +func (s *BenchmarkService) Method9() {} +func (s *BenchmarkService) Method10() {} + +type BenchPerson struct { + Name string `json:"name"` + Age int `json:"age"` + Email string `json:"email"` + Address string `json:"address"` +} + +type ComplexData struct { + ID int `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata map[string]interface{} `json:"metadata"` + Nested *NestedData `json:"nested"` +} + +type NestedData struct { + Value float64 `json:"value"` + Enabled bool `json:"enabled"` +} + +// Helper to create JSON args +func benchArgs(jsonArgs ...string) []json.RawMessage { + args := make([]json.RawMessage, len(jsonArgs)) + for i, j := range jsonArgs { + args[i] = json.RawMessage(j) + } + return args +} + +// BenchmarkMethodBinding measures the cost of registering services with varying method counts +func BenchmarkMethodBinding(b *testing.B) { + // Initialize global application (required for bindings) + _ = application.New(application.Options{}) + + b.Run("SingleService", func(b *testing.B) { + for b.Loop() { + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + } + }) + + b.Run("MultipleServices", func(b *testing.B) { + for b.Loop() { + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + _ = bindings.Add(application.NewService(&BenchPerson{})) // Will fail but tests the path + } + }) +} + +// BenchmarkMethodLookupByID measures method lookup by ID performance +func BenchmarkMethodLookupByID(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + // Get a valid method ID + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + methodID := method.ID + + b.ResetTimer() + for b.Loop() { + _ = bindings.GetByID(methodID) + } +} + +// BenchmarkMethodLookupByName measures method lookup by name performance +func BenchmarkMethodLookupByName(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + + b.ResetTimer() + for b.Loop() { + _ = bindings.Get(callOptions) + } +} + +// BenchmarkSimpleCall measures the cost of calling a method with a simple string argument +func BenchmarkSimpleCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := benchArgs(`"hello world"`) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkComplexCall measures the cost of calling a method with a complex struct argument +func BenchmarkComplexCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.ComplexStruct", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + complexArg := `{ + "id": 12345, + "name": "Test Complex Data", + "tags": ["tag1", "tag2", "tag3", "tag4", "tag5"], + "metadata": {"key1": "value1", "key2": 42, "key3": true}, + "nested": {"value": 3.14159, "enabled": true} + }` + args := benchArgs(complexArg) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkVariadicCall measures the cost of calling a variadic method +func BenchmarkVariadicCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.VariadicArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := benchArgs(`["one", "two", "three", "four", "five"]`) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkCallWithContext measures the cost of calling a method that requires context +func BenchmarkCallWithContext(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.WithContext", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := benchArgs(`"context test"`) + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +// BenchmarkJSONMarshalResult measures JSON marshaling overhead for results +func BenchmarkJSONMarshalResult(b *testing.B) { + person := BenchPerson{ + Name: "John Doe", + Age: 30, + Email: "john@example.com", + Address: "123 Main St, City, Country", + } + + b.Run("SimplePerson", func(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(person) + } + }) + + complex := ComplexData{ + ID: 12345, + Name: "Complex Test", + Tags: []string{"tag1", "tag2", "tag3", "tag4", "tag5"}, + Metadata: map[string]interface{}{ + "key1": "value1", + "key2": 42, + "key3": true, + }, + Nested: &NestedData{ + Value: 3.14159, + Enabled: true, + }, + } + + b.Run("ComplexData", func(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(complex) + } + }) +} + +// BenchmarkHashComputation measures the FNV hash computation used for method IDs +func BenchmarkHashComputation(b *testing.B) { + testCases := []struct { + name string + fqn string + }{ + {"Short", "pkg.Service.Method"}, + {"Medium", "github.com/user/project/pkg.Service.Method"}, + {"Long", "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.ComplexStruct"}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + for b.Loop() { + _ = hash.Fnv(tc.fqn) + } + }) + } +} + +// BenchmarkJSONUnmarshal measures JSON unmarshaling overhead for arguments +func BenchmarkJSONUnmarshal(b *testing.B) { + b.Run("String", func(b *testing.B) { + data := []byte(`"hello world"`) + for b.Loop() { + var s string + _ = json.Unmarshal(data, &s) + } + }) + + b.Run("Int", func(b *testing.B) { + data := []byte(`12345`) + for b.Loop() { + var i int + _ = json.Unmarshal(data, &i) + } + }) + + b.Run("Struct", func(b *testing.B) { + data := []byte(`{"name":"John","age":30,"email":"john@example.com","address":"123 Main St"}`) + for b.Loop() { + var p BenchPerson + _ = json.Unmarshal(data, &p) + } + }) + + b.Run("ComplexStruct", func(b *testing.B) { + data := []byte(`{"id":12345,"name":"Test","tags":["a","b","c"],"metadata":{"k":"v"},"nested":{"value":3.14,"enabled":true}}`) + for b.Loop() { + var c ComplexData + _ = json.Unmarshal(data, &c) + } + }) +} + +// BenchmarkMethodLookupWithAliases measures method lookup with alias resolution +func BenchmarkMethodLookupWithAliases(b *testing.B) { + _ = application.New(application.Options{}) + + // Create aliases map + aliases := make(map[uint32]uint32) + for i := uint32(0); i < 100; i++ { + aliases[i+1000] = i + } + + bindings := application.NewBindings(nil, aliases) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + b.Run("DirectLookup", func(b *testing.B) { + id := method.ID + for b.Loop() { + _ = bindings.GetByID(id) + } + }) + + b.Run("AliasLookup", func(b *testing.B) { + // Add an alias for this method + aliases[9999] = method.ID + for b.Loop() { + _ = bindings.GetByID(9999) + } + }) +} + +// BenchmarkReflectValueCall measures the overhead of reflect.Value.Call +func BenchmarkReflectValueCall(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + ctx := context.Background() + + b.Run("NoArgs", func(b *testing.B) { + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.NoArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + args := benchArgs() + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) + + b.Run("MultipleArgs", func(b *testing.B) { + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + args := benchArgs(`"test"`, `42`, `true`) + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) +} + +// BenchmarkBindingsScaling measures how bindings performance scales with service count +func BenchmarkBindingsScaling(b *testing.B) { + _ = application.New(application.Options{}) + + // We can only add one service of each type, so we test lookup scaling + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + // Generate method names for lookup + methodNames := []string{ + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.NoArgs", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.IntArg", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.MultipleArgs", + "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StructArg", + } + + b.Run("SequentialLookup", func(b *testing.B) { + for b.Loop() { + for _, name := range methodNames { + _ = bindings.Get(&application.CallOptions{MethodName: name}) + } + } + }) +} + +// BenchmarkCallErrorPath measures the cost of error handling in method calls +func BenchmarkCallErrorPath(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + ctx := context.Background() + + b.Run("WrongArgCount", func(b *testing.B) { + args := benchArgs() // No args when one is expected + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) + + b.Run("WrongArgType", func(b *testing.B) { + args := benchArgs(`123`) // Int when string is expected + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) +} + +// BenchmarkSliceArgSizes measures performance with varying slice sizes +func BenchmarkSliceArgSizes(b *testing.B) { + _ = application.New(application.Options{}) + bindings := application.NewBindings(nil, nil) + _ = bindings.Add(application.NewService(&BenchmarkService{})) + + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.BenchmarkService.SliceArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + ctx := context.Background() + + sizes := []int{1, 10, 100, 1000} + for _, size := range sizes { + b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) { + // Build slice JSON + slice := make([]int, size) + for i := range slice { + slice[i] = i + } + data, _ := json.Marshal(slice) + args := []json.RawMessage{data} + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } + }) + } +} diff --git a/v3/pkg/application/bindings_optimized_bench_test.go b/v3/pkg/application/bindings_optimized_bench_test.go new file mode 100644 index 000000000..1a9b137f5 --- /dev/null +++ b/v3/pkg/application/bindings_optimized_bench_test.go @@ -0,0 +1,469 @@ +//go:build bench + +package application + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "sync" + "testing" + + jsonv2 "github.com/go-json-experiment/json" +) + +// This file contains optimized versions of BoundMethod.Call for benchmarking. +// These demonstrate potential optimizations that could be applied. + +// Pools for reducing allocations +var ( + // Pool for []reflect.Value slices (sized for typical arg counts) + callArgsPool = sync.Pool{ + New: func() any { + // Pre-allocate for up to 8 args (covers vast majority of methods) + return make([]reflect.Value, 0, 8) + }, + } + + // Pool for []any slices + anySlicePool = sync.Pool{ + New: func() any { + return make([]any, 0, 4) + }, + } + + // Pool for CallError structs + callErrorPool = sync.Pool{ + New: func() any { + return &CallError{} + }, + } +) + +// CallOptimized is an optimized version of BoundMethod.Call that uses sync.Pool +func (b *BoundMethod) CallOptimized(ctx context.Context, args []json.RawMessage) (result any, err error) { + defer handlePanic(handlePanicOptions{skipEnd: 5}) + + argCount := len(args) + if b.needsContext { + argCount++ + } + + if argCount != len(b.Inputs) { + cerr := callErrorPool.Get().(*CallError) + cerr.Kind = TypeError + cerr.Message = fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount) + cerr.Cause = nil + return nil, cerr + } + + // Get callArgs from pool + callArgs := callArgsPool.Get().([]reflect.Value) + callArgs = callArgs[:0] // Reset length but keep capacity + + // Ensure capacity + if cap(callArgs) < argCount { + callArgs = make([]reflect.Value, 0, argCount) + } + callArgs = callArgs[:argCount] + + base := 0 + if b.needsContext { + callArgs[0] = reflect.ValueOf(ctx) + base++ + } + + // Iterate over given arguments + for index, arg := range args { + value := reflect.New(b.Inputs[base+index].ReflectType) + err = json.Unmarshal(arg, value.Interface()) + if err != nil { + // Return callArgs to pool before returning error + callArgsPool.Put(callArgs[:0]) + + cerr := callErrorPool.Get().(*CallError) + cerr.Kind = TypeError + cerr.Message = fmt.Sprintf("could not parse argument #%d: %s", index, err) + cerr.Cause = json.RawMessage(b.marshalError(err)) + return nil, cerr + } + callArgs[base+index] = value.Elem() + } + + // Do the call - use cached isVariadic flag like production code + var callResults []reflect.Value + if b.isVariadic { + callResults = b.Method.CallSlice(callArgs) + } else { + callResults = b.Method.Call(callArgs) + } + + // Return callArgs to pool + callArgsPool.Put(callArgs[:0]) + + // Get output slice from pool + nonErrorOutputs := anySlicePool.Get().([]any) + nonErrorOutputs = nonErrorOutputs[:0] + defer func() { + anySlicePool.Put(nonErrorOutputs[:0]) + }() + + var errorOutputs []error + + for _, field := range callResults { + if field.Type() == errorType { + if field.IsNil() { + continue + } + if errorOutputs == nil { + errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs)) + nonErrorOutputs = nil + } + errorOutputs = append(errorOutputs, field.Interface().(error)) + } else if nonErrorOutputs != nil { + nonErrorOutputs = append(nonErrorOutputs, field.Interface()) + } + } + + if len(errorOutputs) > 0 { + info := make([]json.RawMessage, len(errorOutputs)) + for i, err := range errorOutputs { + info[i] = b.marshalError(err) + } + + cerr := &CallError{ + Kind: RuntimeError, + Message: errors.Join(errorOutputs...).Error(), + Cause: info, + } + if len(info) == 1 { + cerr.Cause = info[0] + } + return nil, cerr + } + + if len(nonErrorOutputs) == 1 { + result = nonErrorOutputs[0] + } else if len(nonErrorOutputs) > 1 { + // Need to copy since we're returning the pooled slice + resultSlice := make([]any, len(nonErrorOutputs)) + copy(resultSlice, nonErrorOutputs) + result = resultSlice + } + + return result, nil +} + +// Benchmark comparing original vs optimized Call +func BenchmarkCallOriginal(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +func BenchmarkCallOptimized(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallOptimized(ctx, args) + } +} + +// benchService for internal tests +type benchService struct{} + +func (s *benchService) StringArg(str string) string { + return str +} + +func (s *benchService) MultipleArgs(s1 string, i int, b bool) (string, int, bool) { + return s1, i, b +} + +func BenchmarkCallOriginal_MultiArgs(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{ + json.RawMessage(`"test"`), + json.RawMessage(`42`), + json.RawMessage(`true`), + } + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.Call(ctx, args) + } +} + +func BenchmarkCallOptimized_MultiArgs(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{ + json.RawMessage(`"test"`), + json.RawMessage(`42`), + json.RawMessage(`true`), + } + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallOptimized(ctx, args) + } +} + +// CallWithJSONv2 uses the new JSON v2 library for unmarshaling +func (b *BoundMethod) CallWithJSONv2(ctx context.Context, args []json.RawMessage) (result any, err error) { + defer handlePanic(handlePanicOptions{skipEnd: 5}) + + argCount := len(args) + if b.needsContext { + argCount++ + } + + if argCount != len(b.Inputs) { + return nil, &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount), + } + } + + // Convert inputs to values of appropriate type + callArgs := make([]reflect.Value, argCount) + base := 0 + + if b.needsContext { + callArgs[0] = reflect.ValueOf(ctx) + base++ + } + + // Iterate over given arguments - use JSON v2 for unmarshaling + for index, arg := range args { + value := reflect.New(b.Inputs[base+index].ReflectType) + err = jsonv2.Unmarshal(arg, value.Interface()) + if err != nil { + return nil, &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("could not parse argument #%d: %s", index, err), + Cause: json.RawMessage(b.marshalError(err)), + } + } + callArgs[base+index] = value.Elem() + } + + // Do the call + var callResults []reflect.Value + if b.Method.Type().IsVariadic() { + callResults = b.Method.CallSlice(callArgs) + } else { + callResults = b.Method.Call(callArgs) + } + + var nonErrorOutputs = make([]any, 0, len(callResults)) + var errorOutputs []error + + for _, field := range callResults { + if field.Type() == errorType { + if field.IsNil() { + continue + } + if errorOutputs == nil { + errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs)) + nonErrorOutputs = nil + } + errorOutputs = append(errorOutputs, field.Interface().(error)) + } else if nonErrorOutputs != nil { + nonErrorOutputs = append(nonErrorOutputs, field.Interface()) + } + } + + if len(errorOutputs) > 0 { + info := make([]json.RawMessage, len(errorOutputs)) + for i, err := range errorOutputs { + info[i] = b.marshalError(err) + } + + cerr := &CallError{ + Kind: RuntimeError, + Message: errors.Join(errorOutputs...).Error(), + Cause: info, + } + if len(info) == 1 { + cerr.Cause = info[0] + } + return nil, cerr + } + + if len(nonErrorOutputs) == 1 { + result = nonErrorOutputs[0] + } else if len(nonErrorOutputs) > 1 { + result = nonErrorOutputs + } + + return result, nil +} + +func BenchmarkCallJSONv2(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallWithJSONv2(ctx, args) + } +} + +func BenchmarkCallJSONv2_MultiArgs(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.MultipleArgs", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{ + json.RawMessage(`"test"`), + json.RawMessage(`42`), + json.RawMessage(`true`), + } + ctx := context.Background() + + b.ResetTimer() + for b.Loop() { + _, _ = method.CallWithJSONv2(ctx, args) + } +} + +// Concurrent benchmark to test pool effectiveness under load +func BenchmarkCallOriginal_Concurrent(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = method.Call(ctx, args) + } + }) +} + +func BenchmarkCallOptimized_Concurrent(b *testing.B) { + _ = New(Options{}) + bindings := NewBindings(nil, nil) + + service := &benchService{} + _ = bindings.Add(NewService(service)) + + callOptions := &CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application.benchService.StringArg", + } + method := bindings.Get(callOptions) + if method == nil { + b.Fatal("method not found") + } + + args := []json.RawMessage{json.RawMessage(`"hello world"`)} + ctx := context.Background() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = method.CallOptimized(ctx, args) + } + }) +} diff --git a/v3/pkg/application/context_test.go b/v3/pkg/application/context_test.go new file mode 100644 index 000000000..268ca5af9 --- /dev/null +++ b/v3/pkg/application/context_test.go @@ -0,0 +1,120 @@ +package application + +import ( + "testing" +) + +func TestNewContext(t *testing.T) { + ctx := newContext() + if ctx == nil { + t.Fatal("newContext() returned nil") + } + if ctx.data == nil { + t.Error("newContext() should initialize data map") + } +} + +func TestContext_ClickedMenuItem_NotExists(t *testing.T) { + ctx := newContext() + result := ctx.ClickedMenuItem() + if result != nil { + t.Error("ClickedMenuItem() should return nil when not set") + } +} + +func TestContext_ClickedMenuItem_Exists(t *testing.T) { + ctx := newContext() + menuItem := &MenuItem{label: "Test"} + ctx.withClickedMenuItem(menuItem) + + result := ctx.ClickedMenuItem() + if result == nil { + t.Fatal("ClickedMenuItem() should return the menu item") + } + if result != menuItem { + t.Error("ClickedMenuItem() should return the same menu item") + } +} + +func TestContext_IsChecked_NotSet(t *testing.T) { + ctx := newContext() + if ctx.IsChecked() { + t.Error("IsChecked() should return false when not set") + } +} + +func TestContext_IsChecked_True(t *testing.T) { + ctx := newContext() + ctx.withChecked(true) + if !ctx.IsChecked() { + t.Error("IsChecked() should return true when set to true") + } +} + +func TestContext_IsChecked_False(t *testing.T) { + ctx := newContext() + ctx.withChecked(false) + if ctx.IsChecked() { + t.Error("IsChecked() should return false when set to false") + } +} + +func TestContext_ContextMenuData_Empty(t *testing.T) { + ctx := newContext() + result := ctx.ContextMenuData() + if result != "" { + t.Errorf("ContextMenuData() should return empty string when not set, got %q", result) + } +} + +func TestContext_ContextMenuData_Exists(t *testing.T) { + ctx := newContext() + data := &ContextMenuData{Data: "test-data"} + ctx.withContextMenuData(data) + + result := ctx.ContextMenuData() + if result != "test-data" { + t.Errorf("ContextMenuData() = %q, want %q", result, "test-data") + } +} + +func TestContext_ContextMenuData_NilData(t *testing.T) { + ctx := newContext() + ctx.withContextMenuData(nil) + + result := ctx.ContextMenuData() + if result != "" { + t.Errorf("ContextMenuData() should return empty string for nil data, got %q", result) + } +} + +func TestContext_ContextMenuData_WrongType(t *testing.T) { + ctx := newContext() + // Manually set wrong type to test type assertion + ctx.data[contextMenuData] = 123 + + result := ctx.ContextMenuData() + if result != "" { + t.Errorf("ContextMenuData() should return empty string for non-string type, got %q", result) + } +} + +func TestContext_WithClickedMenuItem_Chaining(t *testing.T) { + ctx := newContext() + menuItem := &MenuItem{label: "Test"} + + returnedCtx := ctx.withClickedMenuItem(menuItem) + if returnedCtx != ctx { + t.Error("withClickedMenuItem should return the same context for chaining") + } +} + +func TestContext_WithContextMenuData_Chaining(t *testing.T) { + ctx := newContext() + data := &ContextMenuData{Data: "test"} + + returnedCtx := ctx.withContextMenuData(data) + if returnedCtx != ctx { + t.Error("withContextMenuData should return the same context for chaining") + } +} diff --git a/v3/pkg/application/context_window_event.go b/v3/pkg/application/context_window_event.go index a40ff9d48..ad4a97a8a 100644 --- a/v3/pkg/application/context_window_event.go +++ b/v3/pkg/application/context_window_event.go @@ -3,8 +3,8 @@ package application var blankWindowEventContext = &WindowEventContext{} const ( - droppedFiles = "droppedFiles" - dropZoneDetailsKey = "dropZoneDetails" + droppedFiles = "droppedFiles" + dropTargetDetailsKey = "dropTargetDetails" ) type WindowEventContext struct { @@ -42,33 +42,31 @@ func (c WindowEventContext) setCoordinates(x, y int) { c.data["y"] = y } -func (c WindowEventContext) setDropZoneDetails(details *DropZoneDetails) { +func (c WindowEventContext) setDropTargetDetails(details *DropTargetDetails) { if c.data == nil { c.data = make(map[string]any) } if details == nil { - c.data[dropZoneDetailsKey] = nil + c.data[dropTargetDetailsKey] = nil return } - c.data[dropZoneDetailsKey] = details + c.data[dropTargetDetailsKey] = details } -// DropZoneDetails retrieves the detailed drop zone information, if available. -func (c WindowEventContext) DropZoneDetails() *DropZoneDetails { +// DropTargetDetails retrieves information about the drop target element. +func (c WindowEventContext) DropTargetDetails() *DropTargetDetails { if c.data == nil { c.data = make(map[string]any) } - details, ok := c.data[dropZoneDetailsKey] + details, ok := c.data[dropTargetDetailsKey] if !ok { return nil } - // Explicitly type assert, handle if it's nil (though setDropZoneDetails should handle it) if details == nil { return nil } - result, ok := details.(*DropZoneDetails) + result, ok := details.(*DropTargetDetails) if !ok { - // This case indicates a programming error if data was set incorrectly return nil } return result diff --git a/v3/pkg/application/dialogs_test.go b/v3/pkg/application/dialogs_test.go new file mode 100644 index 000000000..60a923cb2 --- /dev/null +++ b/v3/pkg/application/dialogs_test.go @@ -0,0 +1,223 @@ +package application + +import ( + "testing" +) + +func TestGetDialogID(t *testing.T) { + // Get first dialog ID + id1 := getDialogID() + + // Get second dialog ID - should be different + id2 := getDialogID() + if id1 == id2 { + t.Error("getDialogID should return unique IDs") + } + + // Free first ID + freeDialogID(id1) + + // Get another ID - should recycle the freed id1 or be unique from id2 + id3 := getDialogID() + if id3 == id2 { + t.Error("getDialogID should not return the same ID as an active dialog") + } + // Verify recycling behavior: id3 should either be id1 (recycled) or a new unique ID + if id3 != id1 && id3 <= id2 { + t.Errorf("getDialogID returned unexpected ID: got %d, expected recycled %d or new > %d", id3, id1, id2) + } + + // Cleanup + freeDialogID(id2) + freeDialogID(id3) +} + +func TestFreeDialogID(t *testing.T) { + id := getDialogID() + freeDialogID(id) + + // Should be able to get the same ID again after freeing + newID := getDialogID() + freeDialogID(newID) + // Just verify it doesn't panic +} + +func TestButton_OnClick(t *testing.T) { + button := &Button{Label: "Test"} + called := false + + result := button.OnClick(func() { + called = true + }) + + // Should return the same button for chaining + if result != button { + t.Error("OnClick should return the same button") + } + + // Callback should be set + if button.Callback == nil { + t.Error("Callback should be set") + } + + // Call the callback + button.Callback() + if !called { + t.Error("Callback should have been called") + } +} + +func TestButton_SetAsDefault(t *testing.T) { + button := &Button{Label: "Test"} + + result := button.SetAsDefault() + + // Should return the same button for chaining + if result != button { + t.Error("SetAsDefault should return the same button") + } + + if !button.IsDefault { + t.Error("IsDefault should be true") + } +} + +func TestButton_SetAsCancel(t *testing.T) { + button := &Button{Label: "Test"} + + result := button.SetAsCancel() + + // Should return the same button for chaining + if result != button { + t.Error("SetAsCancel should return the same button") + } + + if !button.IsCancel { + t.Error("IsCancel should be true") + } +} + +func TestButton_Chaining(t *testing.T) { + button := &Button{Label: "OK"} + + button.SetAsDefault().SetAsCancel().OnClick(func() {}) + + if !button.IsDefault { + t.Error("IsDefault should be true after chaining") + } + if !button.IsCancel { + t.Error("IsCancel should be true after chaining") + } + if button.Callback == nil { + t.Error("Callback should be set after chaining") + } +} + +func TestDialogType_Constants(t *testing.T) { + // Verify dialog type constants are distinct + types := []DialogType{InfoDialogType, QuestionDialogType, WarningDialogType, ErrorDialogType} + seen := make(map[DialogType]bool) + + for _, dt := range types { + if seen[dt] { + t.Errorf("DialogType %d is duplicated", dt) + } + seen[dt] = true + } +} + +func TestMessageDialogOptions_Fields(t *testing.T) { + opts := MessageDialogOptions{ + DialogType: InfoDialogType, + Title: "Test Title", + Message: "Test Message", + Buttons: []*Button{{Label: "OK"}}, + Icon: []byte{1, 2, 3}, + } + + if opts.DialogType != InfoDialogType { + t.Error("DialogType not set correctly") + } + if opts.Title != "Test Title" { + t.Error("Title not set correctly") + } + if opts.Message != "Test Message" { + t.Error("Message not set correctly") + } + if len(opts.Buttons) != 1 { + t.Error("Buttons not set correctly") + } + if len(opts.Icon) != 3 { + t.Error("Icon not set correctly") + } +} + +func TestFileFilter_Fields(t *testing.T) { + filter := FileFilter{ + DisplayName: "Image Files (*.jpg, *.png)", + Pattern: "*.jpg;*.png", + } + + if filter.DisplayName != "Image Files (*.jpg, *.png)" { + t.Error("DisplayName not set correctly") + } + if filter.Pattern != "*.jpg;*.png" { + t.Error("Pattern not set correctly") + } +} + +func TestOpenFileDialogOptions_Fields(t *testing.T) { + opts := OpenFileDialogOptions{ + CanChooseDirectories: true, + CanChooseFiles: true, + CanCreateDirectories: true, + ShowHiddenFiles: true, + ResolvesAliases: true, + AllowsMultipleSelection: true, + Title: "Open", + Message: "Select a file", + ButtonText: "Choose", + Directory: "/home", + Filters: []FileFilter{ + {DisplayName: "All Files", Pattern: "*"}, + }, + } + + if !opts.CanChooseDirectories { + t.Error("CanChooseDirectories not set correctly") + } + if !opts.CanChooseFiles { + t.Error("CanChooseFiles not set correctly") + } + if opts.Title != "Open" { + t.Error("Title not set correctly") + } + if len(opts.Filters) != 1 { + t.Error("Filters not set correctly") + } +} + +func TestSaveFileDialogOptions_Fields(t *testing.T) { + opts := SaveFileDialogOptions{ + CanCreateDirectories: true, + ShowHiddenFiles: true, + Title: "Save", + Message: "Save as", + Directory: "/home", + Filename: "file.txt", + ButtonText: "Save", + Filters: []FileFilter{ + {DisplayName: "Text Files", Pattern: "*.txt"}, + }, + } + + if !opts.CanCreateDirectories { + t.Error("CanCreateDirectories not set correctly") + } + if opts.Title != "Save" { + t.Error("Title not set correctly") + } + if opts.Filename != "file.txt" { + t.Error("Filename not set correctly") + } +} diff --git a/v3/pkg/application/event_manager.go b/v3/pkg/application/event_manager.go index 54f1d421b..7555ff685 100644 --- a/v3/pkg/application/event_manager.go +++ b/v3/pkg/application/event_manager.go @@ -3,7 +3,6 @@ package application import ( "slices" - "github.com/samber/lo" "github.com/wailsapp/wails/v3/pkg/events" ) @@ -100,7 +99,9 @@ func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType em.app.applicationEventListenersLock.Lock() defer em.app.applicationEventListenersLock.Unlock() // Remove listener - em.app.applicationEventListeners[eventID] = lo.Without(em.app.applicationEventListeners[eventID], listener) + em.app.applicationEventListeners[eventID] = slices.DeleteFunc(em.app.applicationEventListeners[eventID], func(l *EventListener) bool { + return l == listener + }) } } @@ -116,7 +117,9 @@ func (em *EventManager) RegisterApplicationEventHook(eventType events.Applicatio return func() { em.app.applicationEventHooksLock.Lock() - em.app.applicationEventHooks[eventID] = lo.Without(em.app.applicationEventHooks[eventID], thisHook) + em.app.applicationEventHooks[eventID] = slices.DeleteFunc(em.app.applicationEventHooks[eventID], func(h *eventHook) bool { + return h == thisHook + }) em.app.applicationEventHooksLock.Unlock() } } diff --git a/v3/pkg/application/events.go b/v3/pkg/application/events.go index 2c5afc960..dbe6d20a2 100644 --- a/v3/pkg/application/events.go +++ b/v3/pkg/application/events.go @@ -1,13 +1,14 @@ package application import ( - "encoding/json" "fmt" "reflect" + "slices" "sync" "sync/atomic" - "github.com/samber/lo" + "encoding/json" + "github.com/wailsapp/wails/v3/pkg/events" ) @@ -197,8 +198,8 @@ func (e *EventProcessor) registerListener(eventName string, callback func(*Custo if _, ok := e.listeners[eventName]; !ok { return } - e.listeners[eventName] = lo.Filter(e.listeners[eventName], func(l *eventListener, i int) bool { - return l != thisListener + e.listeners[eventName] = slices.DeleteFunc(e.listeners[eventName], func(l *eventListener) bool { + return l == thisListener }) } } @@ -220,8 +221,8 @@ func (e *EventProcessor) RegisterHook(eventName string, callback func(*CustomEve if _, ok := e.hooks[eventName]; !ok { return } - e.hooks[eventName] = lo.Filter(e.hooks[eventName], func(l *hook, i int) bool { - return l != thisHook + e.hooks[eventName] = slices.DeleteFunc(e.hooks[eventName], func(h *hook) bool { + return h == thisHook }) } } @@ -268,8 +269,8 @@ func (e *EventProcessor) dispatchEventToListeners(event *CustomEvent) { // Do we have items to delete? if itemsToDelete == true { - e.listeners[event.Name] = lo.Filter(listeners, func(l *eventListener, i int) bool { - return l.delete == false + e.listeners[event.Name] = slices.DeleteFunc(listeners, func(l *eventListener) bool { + return l.delete == true }) } } diff --git a/v3/pkg/application/events_bench_test.go b/v3/pkg/application/events_bench_test.go new file mode 100644 index 000000000..c6ed4341f --- /dev/null +++ b/v3/pkg/application/events_bench_test.go @@ -0,0 +1,380 @@ +//go:build bench + +package application_test + +import ( + "fmt" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// mockWindowDispatcher implements a no-op dispatcher for benchmarking +type mockWindowDispatcher struct { + count atomic.Int64 +} + +func (m *mockWindowDispatcher) dispatchEventToWindows(event *application.CustomEvent) { + m.count.Add(1) +} + +// BenchmarkEventEmit measures event emission with varying listener counts +func BenchmarkEventEmit(b *testing.B) { + listenerCounts := []int{0, 1, 10, 100} + + for _, count := range listenerCounts { + b.Run(fmt.Sprintf("Listeners%d", count), func(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register listeners + for i := 0; i < count; i++ { + processor.On("benchmark-event", func(event *application.CustomEvent) { + // Minimal work + _ = event.Data + }) + } + + event := &application.CustomEvent{ + Name: "benchmark-event", + Data: "test payload", + } + + b.ResetTimer() + for b.Loop() { + _ = processor.Emit(event) + } + }) + } +} + +// BenchmarkEventRegistration measures the cost of registering event listeners +func BenchmarkEventRegistration(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("SingleRegistration", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + processor.On("test-event", func(event *application.CustomEvent) {}) + } + }) + + b.Run("MultipleRegistrations", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + for i := 0; i < 10; i++ { + processor.On(fmt.Sprintf("test-event-%d", i), func(event *application.CustomEvent) {}) + } + } + }) + + b.Run("SameEventMultipleListeners", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + for i := 0; i < 10; i++ { + processor.On("test-event", func(event *application.CustomEvent) {}) + } + } + }) +} + +// BenchmarkEventUnregistration measures the cost of unregistering event listeners +func BenchmarkEventUnregistration(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("SingleUnregister", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + cancel := processor.On("test-event", func(event *application.CustomEvent) {}) + cancel() + } + }) + + b.Run("UnregisterFromMany", func(b *testing.B) { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + // Pre-register many listeners + cancels := make([]func(), 100) + for i := 0; i < 100; i++ { + cancels[i] = processor.On("test-event", func(event *application.CustomEvent) {}) + } + + b.ResetTimer() + for i := 0; b.Loop(); i++ { + // Re-register to have something to unregister + if i%100 == 0 { + for j := 0; j < 100; j++ { + cancels[j] = processor.On("test-event", func(event *application.CustomEvent) {}) + } + } + cancels[i%100]() + } + }) + + b.Run("OffAllListeners", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + for i := 0; i < 10; i++ { + processor.On("test-event", func(event *application.CustomEvent) {}) + } + processor.Off("test-event") + } + }) +} + +// BenchmarkHookExecution measures the cost of hook execution during emit +func BenchmarkHookExecution(b *testing.B) { + hookCounts := []int{0, 1, 5, 10} + + for _, count := range hookCounts { + b.Run(fmt.Sprintf("Hooks%d", count), func(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register hooks + for i := 0; i < count; i++ { + processor.RegisterHook("benchmark-event", func(event *application.CustomEvent) { + // Minimal work - don't cancel + _ = event.Data + }) + } + + event := &application.CustomEvent{ + Name: "benchmark-event", + Data: "test payload", + } + + b.ResetTimer() + for b.Loop() { + _ = processor.Emit(event) + } + }) + } +} + +// BenchmarkConcurrentEmit measures event emission under concurrent load +func BenchmarkConcurrentEmit(b *testing.B) { + concurrencyLevels := []int{1, 4, 16} + + for _, concurrency := range concurrencyLevels { + b.Run(fmt.Sprintf("Goroutines%d", concurrency), func(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register a few listeners + for i := 0; i < 5; i++ { + processor.On("benchmark-event", func(event *application.CustomEvent) { + _ = event.Data + }) + } + + b.ResetTimer() + b.SetParallelism(concurrency) + b.RunParallel(func(pb *testing.PB) { + event := &application.CustomEvent{ + Name: "benchmark-event", + Data: "test payload", + } + for pb.Next() { + _ = processor.Emit(event) + } + }) + }) + } +} + +// BenchmarkEventToJSON measures CustomEvent JSON serialization +func BenchmarkEventToJSON(b *testing.B) { + b.Run("SimpleData", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test-event", + Data: "simple string payload", + } + for b.Loop() { + _ = event.ToJSON() + } + }) + + b.Run("ComplexData", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test-event", + Data: map[string]interface{}{ + "id": 12345, + "name": "Test Event", + "tags": []string{"tag1", "tag2", "tag3"}, + "enabled": true, + "nested": map[string]interface{}{ + "value": 3.14159, + }, + }, + } + for b.Loop() { + _ = event.ToJSON() + } + }) + + b.Run("WithSender", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test-event", + Data: "payload", + Sender: "main-window", + } + for b.Loop() { + _ = event.ToJSON() + } + }) +} + +// BenchmarkAtomicCancel measures the atomic cancel/check operations +func BenchmarkAtomicCancel(b *testing.B) { + b.Run("Cancel", func(b *testing.B) { + for b.Loop() { + event := &application.CustomEvent{ + Name: "test", + Data: nil, + } + event.Cancel() + } + }) + + b.Run("IsCancelled", func(b *testing.B) { + event := &application.CustomEvent{ + Name: "test", + Data: nil, + } + for b.Loop() { + _ = event.IsCancelled() + } + }) + + b.Run("CancelAndCheck", func(b *testing.B) { + for b.Loop() { + event := &application.CustomEvent{ + Name: "test", + Data: nil, + } + event.Cancel() + _ = event.IsCancelled() + } + }) +} + +// BenchmarkEventProcessorCreation measures processor instantiation +func BenchmarkEventProcessorCreation(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + for b.Loop() { + _ = application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + } +} + +// BenchmarkOnceEvent measures the Once registration and auto-unregistration +func BenchmarkOnceEvent(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("RegisterAndTrigger", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + var wg sync.WaitGroup + wg.Add(1) + processor.Once("once-event", func(event *application.CustomEvent) { + wg.Done() + }) + _ = processor.Emit(&application.CustomEvent{Name: "once-event", Data: nil}) + wg.Wait() + } + }) +} + +// BenchmarkOnMultipleEvent measures the OnMultiple registration +func BenchmarkOnMultipleEvent(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("ThreeEvents", func(b *testing.B) { + for b.Loop() { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + var wg sync.WaitGroup + wg.Add(3) + processor.OnMultiple("multi-event", func(event *application.CustomEvent) { + wg.Done() + }, 3) + for i := 0; i < 3; i++ { + _ = processor.Emit(&application.CustomEvent{Name: "multi-event", Data: nil}) + } + wg.Wait() + } + }) +} + +// BenchmarkMixedEventOperations simulates realistic event usage patterns +func BenchmarkMixedEventOperations(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + + b.Run("RegisterEmitUnregister", func(b *testing.B) { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + for b.Loop() { + cancel := processor.On("mixed-event", func(event *application.CustomEvent) { + _ = event.Data + }) + _ = processor.Emit(&application.CustomEvent{Name: "mixed-event", Data: "test"}) + cancel() + } + }) + + b.Run("HookAndEmit", func(b *testing.B) { + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + processor.RegisterHook("hooked-event", func(event *application.CustomEvent) { + // Validation hook + if event.Data == nil { + event.Cancel() + } + }) + processor.On("hooked-event", func(event *application.CustomEvent) { + _ = event.Data + }) + + event := &application.CustomEvent{Name: "hooked-event", Data: "valid"} + + b.ResetTimer() + for b.Loop() { + _ = processor.Emit(event) + } + }) +} + +// BenchmarkEventNameLookup measures the map lookup performance for event names +func BenchmarkEventNameLookup(b *testing.B) { + dispatcher := &mockWindowDispatcher{} + processor := application.NewWailsEventProcessor(dispatcher.dispatchEventToWindows) + + // Register events with different name lengths + shortName := "evt" + mediumName := "application:user:action" + longName := "com.mycompany.myapp.module.submodule.event.type.action" + + processor.On(shortName, func(event *application.CustomEvent) {}) + processor.On(mediumName, func(event *application.CustomEvent) {}) + processor.On(longName, func(event *application.CustomEvent) {}) + + b.Run("ShortName", func(b *testing.B) { + event := &application.CustomEvent{Name: shortName, Data: nil} + for b.Loop() { + _ = processor.Emit(event) + } + }) + + b.Run("MediumName", func(b *testing.B) { + event := &application.CustomEvent{Name: mediumName, Data: nil} + for b.Loop() { + _ = processor.Emit(event) + } + }) + + b.Run("LongName", func(b *testing.B) { + event := &application.CustomEvent{Name: longName, Data: nil} + for b.Loop() { + _ = processor.Emit(event) + } + }) +} diff --git a/v3/pkg/application/ios_runtime_ios.go b/v3/pkg/application/ios_runtime_ios.go index 551e3e029..6a388d9a9 100644 --- a/v3/pkg/application/ios_runtime_ios.go +++ b/v3/pkg/application/ios_runtime_ios.go @@ -10,10 +10,13 @@ package application */ import "C" import ( - "encoding/json" "unsafe" + + "encoding/json" ) +// iosHapticsImpact triggers an iOS haptic impact using the provided style. +// The style parameter specifies the impact style name understood by the native haptic engine. func iosHapticsImpact(style string) { cstr := C.CString(style) defer C.free(unsafe.Pointer(cstr)) @@ -39,27 +42,38 @@ func iosDeviceInfo() deviceInfo { return out } -// Live mutations +// iosSetScrollEnabled sets whether scrolling is enabled in the iOS runtime. func iosSetScrollEnabled(enabled bool) { C.ios_runtime_set_scroll_enabled(C.bool(enabled)) } +// iosSetBounceEnabled sets whether scroll bounce (rubber-band) behavior is enabled at runtime. +// If enabled is true, scrollable content will bounce when pulled past its edges; if false, that bounce is disabled. func iosSetBounceEnabled(enabled bool) { C.ios_runtime_set_bounce_enabled(C.bool(enabled)) } +// iosSetScrollIndicatorsEnabled configures whether the iOS runtime shows scroll indicators. +// The enabled parameter controls visibility: true shows indicators, false hides them. func iosSetScrollIndicatorsEnabled(enabled bool) { - C.ios_runtime_set_scroll_indicators_enabled(C.bool(enabled)) + C.ios_runtime_set_scroll_indicators_enabled(C.bool(enabled)) } +// iosSetBackForwardGesturesEnabled enables back-forward navigation gestures when enabled is true and disables them when enabled is false. func iosSetBackForwardGesturesEnabled(enabled bool) { - C.ios_runtime_set_back_forward_gestures_enabled(C.bool(enabled)) + C.ios_runtime_set_back_forward_gestures_enabled(C.bool(enabled)) } +// iosSetLinkPreviewEnabled sets whether link previews are enabled in the iOS runtime. +// Pass true to enable link previews, false to disable them. func iosSetLinkPreviewEnabled(enabled bool) { C.ios_runtime_set_link_preview_enabled(C.bool(enabled)) } +// iosSetInspectableEnabled sets whether runtime web content inspection is enabled. +// When enabled is true the runtime allows inspection of web content; when false inspection is disabled. func iosSetInspectableEnabled(enabled bool) { C.ios_runtime_set_inspectable_enabled(C.bool(enabled)) } +// iosSetCustomUserAgent sets the runtime's custom User-Agent string. +// If ua is an empty string, the custom User-Agent is cleared. func iosSetCustomUserAgent(ua string) { - var cstr *C.char - if ua != "" { - cstr = C.CString(ua) - defer C.free(unsafe.Pointer(cstr)) - } - C.ios_runtime_set_custom_user_agent(cstr) + var cstr *C.char + if ua != "" { + cstr = C.CString(ua) + defer C.free(unsafe.Pointer(cstr)) + } + C.ios_runtime_set_custom_user_agent(cstr) } // Native tabs func iosSetNativeTabsEnabled(enabled bool) { C.ios_native_tabs_set_enabled(C.bool(enabled)) } func iosNativeTabsIsEnabled() bool { return bool(C.ios_native_tabs_is_enabled()) } -func iosSelectNativeTab(index int) { C.ios_native_tabs_select_index(C.int(index)) } +func iosSelectNativeTab(index int) { C.ios_native_tabs_select_index(C.int(index)) } \ No newline at end of file diff --git a/v3/pkg/application/json_libs_bench_test.go b/v3/pkg/application/json_libs_bench_test.go new file mode 100644 index 000000000..c3e7e4918 --- /dev/null +++ b/v3/pkg/application/json_libs_bench_test.go @@ -0,0 +1,314 @@ +//go:build bench + +// Disabled: goccy/go-json causes Windows panics. See PR #4859. + +package application_test + +/* +import ( + "encoding/json" + "testing" + + "github.com/bytedance/sonic" + gojson "github.com/goccy/go-json" + jsoniter "github.com/json-iterator/go" +) + +// Test structures matching real Wails binding patterns + +type SimpleBindingArg struct { + Name string `json:"name"` + Value int `json:"value"` +} + +type ComplexBindingArg struct { + ID int `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata map[string]interface{} `json:"metadata"` + Nested *NestedBindingArg `json:"nested,omitempty"` +} + +type NestedBindingArg struct { + Value float64 `json:"value"` + Enabled bool `json:"enabled"` +} + +// Test data simulating frontend calls +var ( + simpleJSON = []byte(`{"name":"test","value":42}`) + + complexJSON = []byte(`{"id":12345,"name":"Test Complex Data","tags":["tag1","tag2","tag3","tag4","tag5"],"metadata":{"key1":"value1","key2":42,"key3":true},"nested":{"value":3.14159,"enabled":true}}`) + + stringJSON = []byte(`"hello world this is a test string"`) + + multiArgsJSON = [][]byte{ + []byte(`"arg1"`), + []byte(`42`), + []byte(`true`), + []byte(`{"key":"value"}`), + } +) + +// Configure jsoniter for maximum compatibility +var jsoniterStd = jsoniter.ConfigCompatibleWithStandardLibrary + +// ============================================================================ +// UNMARSHAL BENCHMARKS - This is the HOT PATH (bindings.go:289) +// ============================================================================ + +// --- Simple struct unmarshal --- + +func BenchmarkUnmarshal_Simple_StdLib(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = json.Unmarshal(simpleJSON, &arg) + } +} + +func BenchmarkUnmarshal_Simple_GoJSON(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = gojson.Unmarshal(simpleJSON, &arg) + } +} + +func BenchmarkUnmarshal_Simple_JSONIter(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = jsoniterStd.Unmarshal(simpleJSON, &arg) + } +} + +func BenchmarkUnmarshal_Simple_Sonic(b *testing.B) { + for b.Loop() { + var arg SimpleBindingArg + _ = sonic.Unmarshal(simpleJSON, &arg) + } +} + +// --- Complex struct unmarshal --- + +func BenchmarkUnmarshal_Complex_StdLib(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = json.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Complex_GoJSON(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = gojson.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Complex_JSONIter(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = jsoniterStd.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Complex_Sonic(b *testing.B) { + for b.Loop() { + var arg ComplexBindingArg + _ = sonic.Unmarshal(complexJSON, &arg) + } +} + +// --- String unmarshal (most common single arg) --- + +func BenchmarkUnmarshal_String_StdLib(b *testing.B) { + for b.Loop() { + var arg string + _ = json.Unmarshal(stringJSON, &arg) + } +} + +func BenchmarkUnmarshal_String_GoJSON(b *testing.B) { + for b.Loop() { + var arg string + _ = gojson.Unmarshal(stringJSON, &arg) + } +} + +func BenchmarkUnmarshal_String_JSONIter(b *testing.B) { + for b.Loop() { + var arg string + _ = jsoniterStd.Unmarshal(stringJSON, &arg) + } +} + +func BenchmarkUnmarshal_String_Sonic(b *testing.B) { + for b.Loop() { + var arg string + _ = sonic.Unmarshal(stringJSON, &arg) + } +} + +// --- Interface{} unmarshal (dynamic typing) --- + +func BenchmarkUnmarshal_Interface_StdLib(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = json.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Interface_GoJSON(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = gojson.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Interface_JSONIter(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = jsoniterStd.Unmarshal(complexJSON, &arg) + } +} + +func BenchmarkUnmarshal_Interface_Sonic(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = sonic.Unmarshal(complexJSON, &arg) + } +} + +// --- Multi-arg unmarshal (simulating typical method call) --- + +func BenchmarkUnmarshal_MultiArgs_StdLib(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = json.Unmarshal(multiArgsJSON[0], &s) + _ = json.Unmarshal(multiArgsJSON[1], &i) + _ = json.Unmarshal(multiArgsJSON[2], &bl) + _ = json.Unmarshal(multiArgsJSON[3], &m) + } +} + +func BenchmarkUnmarshal_MultiArgs_GoJSON(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = gojson.Unmarshal(multiArgsJSON[0], &s) + _ = gojson.Unmarshal(multiArgsJSON[1], &i) + _ = gojson.Unmarshal(multiArgsJSON[2], &bl) + _ = gojson.Unmarshal(multiArgsJSON[3], &m) + } +} + +func BenchmarkUnmarshal_MultiArgs_JSONIter(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = jsoniterStd.Unmarshal(multiArgsJSON[0], &s) + _ = jsoniterStd.Unmarshal(multiArgsJSON[1], &i) + _ = jsoniterStd.Unmarshal(multiArgsJSON[2], &bl) + _ = jsoniterStd.Unmarshal(multiArgsJSON[3], &m) + } +} + +func BenchmarkUnmarshal_MultiArgs_Sonic(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = sonic.Unmarshal(multiArgsJSON[0], &s) + _ = sonic.Unmarshal(multiArgsJSON[1], &i) + _ = sonic.Unmarshal(multiArgsJSON[2], &bl) + _ = sonic.Unmarshal(multiArgsJSON[3], &m) + } +} + +// ============================================================================ +// MARSHAL BENCHMARKS - Result serialization +// ============================================================================ + +type BindingResult struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +var simpleResult = BindingResult{ + Success: true, + Data: "hello world", +} + +var complexResult = BindingResult{ + Success: true, + Data: ComplexBindingArg{ + ID: 12345, + Name: "Result Data", + Tags: []string{"a", "b", "c"}, + Metadata: map[string]interface{}{ + "processed": true, + "count": 100, + }, + Nested: &NestedBindingArg{Value: 2.718, Enabled: true}, + }, +} + +// --- Simple result marshal --- + +func BenchmarkMarshal_Simple_StdLib(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(simpleResult) + } +} + +func BenchmarkMarshal_Simple_GoJSON(b *testing.B) { + for b.Loop() { + _, _ = gojson.Marshal(simpleResult) + } +} + +func BenchmarkMarshal_Simple_JSONIter(b *testing.B) { + for b.Loop() { + _, _ = jsoniterStd.Marshal(simpleResult) + } +} + +func BenchmarkMarshal_Simple_Sonic(b *testing.B) { + for b.Loop() { + _, _ = sonic.Marshal(simpleResult) + } +} + +// --- Complex result marshal --- + +func BenchmarkMarshal_Complex_StdLib(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(complexResult) + } +} + +func BenchmarkMarshal_Complex_GoJSON(b *testing.B) { + for b.Loop() { + _, _ = gojson.Marshal(complexResult) + } +} + +func BenchmarkMarshal_Complex_JSONIter(b *testing.B) { + for b.Loop() { + _, _ = jsoniterStd.Marshal(complexResult) + } +} + +func BenchmarkMarshal_Complex_Sonic(b *testing.B) { + for b.Loop() { + _, _ = sonic.Marshal(complexResult) + } +} +*/ diff --git a/v3/pkg/application/json_v2_bench_test.go b/v3/pkg/application/json_v2_bench_test.go new file mode 100644 index 000000000..d7b45193c --- /dev/null +++ b/v3/pkg/application/json_v2_bench_test.go @@ -0,0 +1,270 @@ +//go:build bench && goexperiment.jsonv2 + +package application_test + +import ( + "encoding/json" + "encoding/json/jsontext" + jsonv2 "encoding/json/v2" + "testing" +) + +// Benchmark structures matching real Wails usage patterns + +type SimpleArg struct { + Name string `json:"name"` + Value int `json:"value"` +} + +type ComplexArg struct { + ID int `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata map[string]interface{} `json:"metadata"` + Nested *NestedArg `json:"nested,omitempty"` +} + +type NestedArg struct { + Value float64 `json:"value"` + Enabled bool `json:"enabled"` +} + +type CallResult struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +// Test data +var ( + simpleArgJSON = []byte(`{"name":"test","value":42}`) + complexArgJSON = []byte(`{ + "id": 12345, + "name": "Test Complex Data", + "tags": ["tag1", "tag2", "tag3", "tag4", "tag5"], + "metadata": {"key1": "value1", "key2": 42, "key3": true}, + "nested": {"value": 3.14159, "enabled": true} + }`) + + simpleResult = CallResult{ + Success: true, + Data: "hello world", + } + + complexResult = CallResult{ + Success: true, + Data: ComplexArg{ + ID: 12345, + Name: "Result Data", + Tags: []string{"a", "b", "c"}, + Metadata: map[string]interface{}{ + "processed": true, + "count": 100, + }, + Nested: &NestedArg{Value: 2.718, Enabled: true}, + }, + } +) + +// === UNMARSHAL BENCHMARKS (argument parsing) === + +func BenchmarkJSONv1_Unmarshal_Simple(b *testing.B) { + for b.Loop() { + var arg SimpleArg + _ = json.Unmarshal(simpleArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Simple(b *testing.B) { + for b.Loop() { + var arg SimpleArg + _ = jsonv2.Unmarshal(simpleArgJSON, &arg) + } +} + +func BenchmarkJSONv1_Unmarshal_Complex(b *testing.B) { + for b.Loop() { + var arg ComplexArg + _ = json.Unmarshal(complexArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Complex(b *testing.B) { + for b.Loop() { + var arg ComplexArg + _ = jsonv2.Unmarshal(complexArgJSON, &arg) + } +} + +func BenchmarkJSONv1_Unmarshal_Interface(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = json.Unmarshal(complexArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Interface(b *testing.B) { + for b.Loop() { + var arg interface{} + _ = jsonv2.Unmarshal(complexArgJSON, &arg) + } +} + +// === MARSHAL BENCHMARKS (result serialization) === + +func BenchmarkJSONv1_Marshal_Simple(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(simpleResult) + } +} + +func BenchmarkJSONv2_Marshal_Simple(b *testing.B) { + for b.Loop() { + _, _ = jsonv2.Marshal(simpleResult) + } +} + +func BenchmarkJSONv1_Marshal_Complex(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(complexResult) + } +} + +func BenchmarkJSONv2_Marshal_Complex(b *testing.B) { + for b.Loop() { + _, _ = jsonv2.Marshal(complexResult) + } +} + +// === RAW MESSAGE HANDLING (common in Wails bindings) === + +func BenchmarkJSONv1_RawMessage_Unmarshal(b *testing.B) { + raw := json.RawMessage(complexArgJSON) + for b.Loop() { + var arg ComplexArg + _ = json.Unmarshal(raw, &arg) + } +} + +func BenchmarkJSONv2_RawMessage_Unmarshal(b *testing.B) { + raw := jsontext.Value(complexArgJSON) + for b.Loop() { + var arg ComplexArg + _ = jsonv2.Unmarshal(raw, &arg) + } +} + +// === SLICE ARGUMENTS (common pattern) === + +var sliceArgJSON = []byte(`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`) +var largeSliceArgJSON = func() []byte { + data, _ := json.Marshal(make([]int, 100)) + return data +}() + +func BenchmarkJSONv1_Unmarshal_Slice(b *testing.B) { + for b.Loop() { + var arg []int + _ = json.Unmarshal(sliceArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_Slice(b *testing.B) { + for b.Loop() { + var arg []int + _ = jsonv2.Unmarshal(sliceArgJSON, &arg) + } +} + +func BenchmarkJSONv1_Unmarshal_LargeSlice(b *testing.B) { + for b.Loop() { + var arg []int + _ = json.Unmarshal(largeSliceArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_LargeSlice(b *testing.B) { + for b.Loop() { + var arg []int + _ = jsonv2.Unmarshal(largeSliceArgJSON, &arg) + } +} + +// === STRING ARGUMENT (most common) === + +var stringArgJSON = []byte(`"hello world this is a test string"`) + +func BenchmarkJSONv1_Unmarshal_String(b *testing.B) { + for b.Loop() { + var arg string + _ = json.Unmarshal(stringArgJSON, &arg) + } +} + +func BenchmarkJSONv2_Unmarshal_String(b *testing.B) { + for b.Loop() { + var arg string + _ = jsonv2.Unmarshal(stringArgJSON, &arg) + } +} + +// === MULTIPLE ARGUMENTS (simulating method call) === + +var multiArgJSON = [][]byte{ + []byte(`"arg1"`), + []byte(`42`), + []byte(`true`), + []byte(`{"key": "value"}`), +} + +func BenchmarkJSONv1_Unmarshal_MultiArgs(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = json.Unmarshal(multiArgJSON[0], &s) + _ = json.Unmarshal(multiArgJSON[1], &i) + _ = json.Unmarshal(multiArgJSON[2], &bl) + _ = json.Unmarshal(multiArgJSON[3], &m) + } +} + +func BenchmarkJSONv2_Unmarshal_MultiArgs(b *testing.B) { + for b.Loop() { + var s string + var i int + var bl bool + var m map[string]string + _ = jsonv2.Unmarshal(multiArgJSON[0], &s) + _ = jsonv2.Unmarshal(multiArgJSON[1], &i) + _ = jsonv2.Unmarshal(multiArgJSON[2], &bl) + _ = jsonv2.Unmarshal(multiArgJSON[3], &m) + } +} + +// === ERROR RESPONSE MARSHALING === + +type ErrorResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +var errorResp = ErrorResponse{ + Code: 500, + Message: "Internal server error", + Details: "Something went wrong while processing the request", +} + +func BenchmarkJSONv1_Marshal_Error(b *testing.B) { + for b.Loop() { + _, _ = json.Marshal(errorResp) + } +} + +func BenchmarkJSONv2_Marshal_Error(b *testing.B) { + for b.Loop() { + _, _ = jsonv2.Marshal(errorResp) + } +} diff --git a/v3/pkg/application/keys_test.go b/v3/pkg/application/keys_test.go new file mode 100644 index 000000000..3651395ed --- /dev/null +++ b/v3/pkg/application/keys_test.go @@ -0,0 +1,293 @@ +package application + +import ( + "runtime" + "strings" + "testing" +) + +func TestModifier_Constants(t *testing.T) { + // Verify modifier constants are distinct + modifiers := []modifier{CmdOrCtrlKey, OptionOrAltKey, ShiftKey, SuperKey, ControlKey} + seen := make(map[modifier]bool) + for _, m := range modifiers { + if seen[m] { + t.Errorf("Duplicate modifier value: %d", m) + } + seen[m] = true + } + + // CmdOrCtrlKey should be 0 (the base value) + if CmdOrCtrlKey != 0 { + t.Error("CmdOrCtrlKey should be 0") + } +} + +func TestParseKey_Valid(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"a", "a"}, + {"A", "a"}, + {"z", "z"}, + {"0", "0"}, + {"9", "9"}, + {"+", "+"}, // Single + is a valid printable character + {"plus", "+"}, + {"Plus", "+"}, + {"PLUS", "+"}, + {"backspace", "backspace"}, + {"Backspace", "backspace"}, + {"BACKSPACE", "backspace"}, + {"tab", "tab"}, + {"return", "return"}, + {"enter", "enter"}, + {"escape", "escape"}, + {"left", "left"}, + {"right", "right"}, + {"up", "up"}, + {"down", "down"}, + {"space", "space"}, + {"delete", "delete"}, + {"home", "home"}, + {"end", "end"}, + {"page up", "page up"}, + {"page down", "page down"}, + {"f1", "f1"}, + {"F1", "f1"}, + {"f12", "f12"}, + {"f35", "f35"}, + {"numlock", "numlock"}, + } + + for _, tt := range tests { + result, valid := parseKey(tt.input) + if tt.expected == "" { + if valid { + t.Errorf("parseKey(%q) should be invalid", tt.input) + } + } else { + if !valid { + t.Errorf("parseKey(%q) should be valid", tt.input) + } + if result != tt.expected { + t.Errorf("parseKey(%q) = %q, want %q", tt.input, result, tt.expected) + } + } + } +} + +func TestParseKey_Invalid(t *testing.T) { + tests := []string{ + "abc", // multiple chars + "", // empty + "notakey", // not a named key + "ctrl+a", // shortcut syntax + "backspac", // misspelled + } + + for _, tt := range tests { + _, valid := parseKey(tt) + if valid { + t.Errorf("parseKey(%q) should be invalid", tt) + } + } +} + +func TestParseAccelerator_Valid(t *testing.T) { + tests := []struct { + input string + key string + modCount int + }{ + {"a", "a", 0}, + {"Ctrl+A", "a", 1}, + {"ctrl+a", "a", 1}, + {"Ctrl+Shift+A", "a", 2}, + {"ctrl+shift+a", "a", 2}, + {"Cmd+A", "a", 1}, + {"Command+A", "a", 1}, + {"CmdOrCtrl+A", "a", 1}, + {"Alt+A", "a", 1}, + {"Option+A", "a", 1}, + {"OptionOrAlt+A", "a", 1}, + {"Shift+A", "a", 1}, + {"Super+A", "a", 1}, + {"Ctrl+Shift+Alt+A", "a", 3}, + {"Ctrl+plus", "+", 1}, + {"F1", "f1", 0}, + {"Ctrl+F12", "f12", 1}, + {"Ctrl+Shift+F1", "f1", 2}, + {"Ctrl+backspace", "backspace", 1}, + {"Ctrl+escape", "escape", 1}, + } + + for _, tt := range tests { + acc, err := parseAccelerator(tt.input) + if err != nil { + t.Errorf("parseAccelerator(%q) returned error: %v", tt.input, err) + continue + } + if acc.Key != tt.key { + t.Errorf("parseAccelerator(%q).Key = %q, want %q", tt.input, acc.Key, tt.key) + } + if len(acc.Modifiers) != tt.modCount { + t.Errorf("parseAccelerator(%q) has %d modifiers, want %d", tt.input, len(acc.Modifiers), tt.modCount) + } + } +} + +func TestParseAccelerator_Invalid(t *testing.T) { + tests := []struct { + input string + errMsg string + }{ + {"", "no components"}, + {"Ctrl+", "not a valid key"}, + {"Ctrl+abc", "not a valid key"}, + {"NotAModifier+A", "not a valid modifier"}, + {"Ctrl+Shift+notakey", "not a valid key"}, + } + + for _, tt := range tests { + _, err := parseAccelerator(tt.input) + if err == nil { + t.Errorf("parseAccelerator(%q) should return error", tt.input) + } + } +} + +func TestParseAccelerator_DuplicateModifiers(t *testing.T) { + // Duplicate modifiers should be deduplicated + acc, err := parseAccelerator("Ctrl+Ctrl+A") + if err != nil { + t.Errorf("parseAccelerator returned error: %v", err) + return + } + if len(acc.Modifiers) != 1 { + t.Errorf("Duplicate modifiers should be deduplicated, got %d modifiers", len(acc.Modifiers)) + } +} + +func TestAccelerator_Clone(t *testing.T) { + original := &accelerator{ + Key: "a", + Modifiers: []modifier{ControlKey, ShiftKey}, + } + + clone := original.clone() + + if clone == original { + t.Error("Clone should return a different pointer") + } + if clone.Key != original.Key { + t.Error("Clone should have same Key") + } + // Note: the slice reference is copied, so modifying the clone's slice would affect original + // This is a shallow clone +} + +func TestAccelerator_String(t *testing.T) { + // Test key-only accelerator (platform-independent) + acc := &accelerator{Key: "a", Modifiers: []modifier{}} + result := acc.String() + if result != "A" { + t.Errorf("accelerator.String() = %q, want %q", result, "A") + } + + // Test with ControlKey modifier - output varies by platform + acc = &accelerator{Key: "a", Modifiers: []modifier{ControlKey}} + result = acc.String() + // On macOS: "Ctrl+A", on Linux/Windows: "Ctrl+A" + // The representation should contain the key and be non-empty + if !strings.HasSuffix(result, "+A") && result != "A" { + t.Errorf("accelerator.String() = %q, expected to end with '+A'", result) + } + if result == "" { + t.Error("accelerator.String() should not return empty") + } + + // Test function key with modifier + acc = &accelerator{Key: "f1", Modifiers: []modifier{ControlKey}} + result = acc.String() + if !strings.HasSuffix(result, "+F1") { + t.Errorf("accelerator.String() = %q, expected to end with '+F1'", result) + } +} + +func TestAccelerator_String_PlatformSpecific(t *testing.T) { + // This test documents the expected platform-specific behavior + acc := &accelerator{Key: "a", Modifiers: []modifier{ControlKey}} + result := acc.String() + + switch runtime.GOOS { + case "darwin": + // On macOS, Ctrl key is represented as "Ctrl" (distinct from Cmd) + if !strings.Contains(result, "Ctrl") && !strings.Contains(result, "⌃") { + t.Logf("On macOS, got %q for ControlKey modifier", result) + } + case "linux", "windows": + if !strings.Contains(result, "Ctrl") { + t.Errorf("On %s, expected 'Ctrl' in result, got %q", runtime.GOOS, result) + } + } +} + +func TestAccelerator_StringWithMultipleModifiers(t *testing.T) { + acc := &accelerator{ + Key: "a", + Modifiers: []modifier{ShiftKey, ControlKey}, + } + + result := acc.String() + + // Result should contain both modifiers and the key + if result == "" { + t.Error("String() should not return empty") + } + // The modifiers are sorted, so order is deterministic +} + +func TestModifierMap_Contains(t *testing.T) { + expectedMappings := map[string]modifier{ + "cmdorctrl": CmdOrCtrlKey, + "cmd": CmdOrCtrlKey, + "command": CmdOrCtrlKey, + "ctrl": ControlKey, + "optionoralt": OptionOrAltKey, + "alt": OptionOrAltKey, + "option": OptionOrAltKey, + "shift": ShiftKey, + "super": SuperKey, + } + + for key, expected := range expectedMappings { + actual, ok := modifierMap[key] + if !ok { + t.Errorf("modifierMap should contain key %q", key) + } + if actual != expected { + t.Errorf("modifierMap[%q] = %v, want %v", key, actual, expected) + } + } +} + +func TestNamedKeys_Contains(t *testing.T) { + expectedKeys := []string{ + "backspace", "tab", "return", "enter", "escape", + "left", "right", "up", "down", "space", "delete", + "home", "end", "page up", "page down", + "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", + "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", + "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", + "f31", "f32", "f33", "f34", "f35", + "numlock", + } + + for _, key := range expectedKeys { + if _, ok := namedKeys[key]; !ok { + t.Errorf("namedKeys should contain %q", key) + } + } +} diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go index 476636ade..4d5ce4c71 100644 --- a/v3/pkg/application/linux_cgo.go +++ b/v3/pkg/application/linux_cgo.go @@ -23,11 +23,9 @@ import ( #include #include #include -#ifdef G_APPLICATION_DEFAULT_FLAGS - #define APPLICATION_DEFAULT_FLAGS G_APPLICATION_DEFAULT_FLAGS -#else - #define APPLICATION_DEFAULT_FLAGS G_APPLICATION_FLAGS_NONE -#endif +// Use NON_UNIQUE to allow multiple instances of the application to run. +// This matches the behavior of gtk_init/gtk_main used in v2. +#define APPLICATION_DEFAULT_FLAGS G_APPLICATION_NON_UNIQUE typedef struct CallbackID { @@ -87,6 +85,9 @@ void handleClick(void*); extern gboolean onButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data); extern gboolean onMenuButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data); extern void onUriList(char **extracted, gint x, gint y, gpointer data); +extern void onDragEnter(gpointer data); +extern void onDragLeave(gpointer data); +extern void onDragOver(gint x, gint y, gpointer data); extern gboolean onKeyPressEvent (GtkWidget *widget, GdkEventKey *event, uintptr_t user_data); extern void onProcessRequest(WebKitURISchemeRequest *request, uintptr_t user_data); extern void sendMessageToBackend(WebKitUserContentManager *contentManager, WebKitJavascriptResult *result, void *data); @@ -160,7 +161,16 @@ typedef struct Screen { bool isPrimary; } Screen; +// Signal handler fix for WebKit/GTK compatibility. // CREDIT: https://github.com/rainycape/magick +// +// WebKit/GTK may install signal handlers without SA_ONSTACK, which causes +// Go to crash when handling signals (e.g., during panic recovery). +// This code adds SA_ONSTACK to signal handlers after WebKit initialization. +// +// Known limitation: Due to Go issue #7227 (golang/go#7227), signals may still +// be delivered on the wrong stack in some cases when C libraries are involved. +// This is a fundamental Go runtime limitation that cannot be fully resolved here. #include #include #include @@ -224,34 +234,204 @@ static int GetNumScreens(){ return 0; } -static void on_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, +// Handle file drops from the OS - called when drag data is received +static void on_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer data) { - gint length = gtk_selection_data_get_length(selection_data); - - if (length < 0) - { - g_print("DnD failed!\n"); - gtk_drag_finish(context, FALSE, FALSE, time); + // Only process target_type 2 which is our text/uri-list + // Other target types are from internal WebKit drags + if (target_type != 2) { + return; // Don't interfere with internal drags } - gchar *uri_data = (gchar *)gtk_selection_data_get_data(selection_data); - gchar **uri_list = g_uri_list_extract_uris(uri_data); + // Check if we have valid data + if (selection_data == NULL || gtk_selection_data_get_length(selection_data) <= 0) { + gtk_drag_finish(context, FALSE, FALSE, time); + return; + } - onUriList(uri_list, x, y, data); + const gchar *uri_data = (const gchar *)gtk_selection_data_get_data(selection_data); + gchar **filenames = g_uri_list_extract_uris(uri_data); + if (filenames == NULL || filenames[0] == NULL) { + if (filenames) g_strfreev(filenames); + gtk_drag_finish(context, FALSE, FALSE, time); + return; + } - g_strfreev(uri_list); - gtk_drag_finish(context, TRUE, TRUE, time); + // Build file array for Go + GPtrArray *file_array = g_ptr_array_new(); + int iter = 0; + while (filenames[iter] != NULL) { + char *filename = g_filename_from_uri(filenames[iter], NULL, NULL); + if (filename != NULL) { + g_ptr_array_add(file_array, filename); + } + iter++; + } + g_strfreev(filenames); + + if (file_array->len > 0) { + // Get stored drop coordinates and data pointer + gint drop_x = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "drop-x")); + gint drop_y = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "drop-y")); + gpointer drop_data = g_object_get_data(G_OBJECT(widget), "drop-data"); + + // Add NULL terminator and call Go + g_ptr_array_add(file_array, NULL); + onUriList((gchar **)file_array->pdata, drop_x, drop_y, drop_data); + } + + // Cleanup + for (guint i = 0; i < file_array->len; i++) { + gpointer item = g_ptr_array_index(file_array, i); + if (item) g_free(item); + } + g_ptr_array_free(file_array, TRUE); + + // Finish the drag successfully to prevent WebKit from opening the file + gtk_drag_finish(context, TRUE, FALSE, time); } -// drag and drop tutorial: https://wiki.gnome.org/Newcomers/OldDragNDropTutorial +// Track if we've notified about drag entering +static gboolean drag_entered = FALSE; + +// Track if a drag started from within the webview (internal HTML5 drag) +static gboolean internal_drag_active = FALSE; + +// Called when a drag starts FROM this widget (internal drag) +static void on_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer data) +{ + internal_drag_active = TRUE; +} + +// Called when a drag that started from this widget ends +static void on_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer data) +{ + internal_drag_active = FALSE; +} + +// Check if a drag context contains file URIs (external drop) +// Returns TRUE only for external file manager drops, FALSE for internal HTML5 drags +static gboolean is_file_drag(GdkDragContext *context) +{ + GList *targets = gdk_drag_context_list_targets(context); + + // Internal HTML5 drags have WebKit-specific targets, external file drops have text/uri-list + for (GList *l = targets; l != NULL; l = l->next) { + GdkAtom atom = GDK_POINTER_TO_ATOM(l->data); + gchar *name = gdk_atom_name(atom); + if (name) { + gboolean is_uri = g_strcmp0(name, "text/uri-list") == 0; + g_free(name); + if (is_uri) { + return TRUE; + } + } + } + return FALSE; +} + +// Handle the actual drop - called when user releases mouse button +static gboolean on_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + guint time, gpointer data) +{ + // Only handle external file drops, let WebKit handle internal HTML5 drags + if (!is_file_drag(context)) { + return FALSE; + } + + // Reset drag entered state + drag_entered = FALSE; + + // Store coordinates for use in drag-data-received + g_object_set_data(G_OBJECT(widget), "drop-x", GINT_TO_POINTER(x)); + g_object_set_data(G_OBJECT(widget), "drop-y", GINT_TO_POINTER(y)); + g_object_set_data(G_OBJECT(widget), "drop-data", data); + + // Request the file data - this triggers drag-data-received + GdkAtom target = gdk_atom_intern("text/uri-list", FALSE); + gtk_drag_get_data(widget, context, target, time); + + return TRUE; +} + +// Handle drag-motion for hover effects on external file drags +static gboolean on_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) +{ + // Don't handle internal HTML5 drags + if (internal_drag_active || !is_file_drag(context)) { + return FALSE; + } + + gdk_drag_status(context, GDK_ACTION_COPY, time); + + // Notify JS once when drag enters + if (!drag_entered) { + drag_entered = TRUE; + onDragEnter(data); + } + + // Send position to JS for hover effects (Go side throttles this) + onDragOver(x, y, data); + + return TRUE; +} + +// Handle drag-leave - drag exited the window +static void on_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data) +{ + // Don't handle internal HTML5 drags + if (internal_drag_active || !is_file_drag(context)) { + return; + } + + if (drag_entered) { + drag_entered = FALSE; + onDragLeave(data); + } +} + +// Set up drag and drop handlers for external file drops with hover effects static void enableDND(GtkWidget *widget, gpointer data) { - GtkTargetEntry *target = gtk_target_entry_new("text/uri-list", 0, 0); - gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, target, 1, GDK_ACTION_COPY); + // Core handlers for file drop + g_signal_connect(G_OBJECT(widget), "drag-data-received", G_CALLBACK(on_drag_data_received), data); + g_signal_connect(G_OBJECT(widget), "drag-drop", G_CALLBACK(on_drag_drop), data); - signal_connect(widget, "drag-data-received", on_data_received, data); + // Hover effect handlers - return FALSE for internal drags to let WebKit handle them + g_signal_connect(G_OBJECT(widget), "drag-motion", G_CALLBACK(on_drag_motion), data); + g_signal_connect(G_OBJECT(widget), "drag-leave", G_CALLBACK(on_drag_leave), data); +} + +// Block external file drops - consume the events to prevent WebKit from navigating to files +// Returns TRUE for file drags to consume them, FALSE for internal HTML5 drags to let WebKit handle +static gboolean on_drag_drop_blocked(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + guint time, gpointer data) +{ + if (!is_file_drag(context)) { + return FALSE; // Let WebKit handle internal HTML5 drags + } + // Block external file drops by finishing with failure + gtk_drag_finish(context, FALSE, FALSE, time); + return TRUE; +} + +static gboolean on_drag_motion_blocked(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) +{ + if (internal_drag_active || !is_file_drag(context)) { + return FALSE; // Let WebKit handle internal HTML5 drags + } + // Show "no drop" cursor for external file drags + gdk_drag_status(context, 0, time); + return TRUE; +} + +// Set up handlers that block external file drops while allowing internal HTML5 drag-and-drop +static void disableDND(GtkWidget *widget, gpointer data) +{ + g_signal_connect(G_OBJECT(widget), "drag-drop", G_CALLBACK(on_drag_drop_blocked), data); + g_signal_connect(G_OBJECT(widget), "drag-motion", G_CALLBACK(on_drag_motion_blocked), data); } */ import "C" @@ -318,6 +498,7 @@ var ( ) var registerURIScheme sync.Once +var fixSignalHandlers sync.Once func init() { gtkSignalToMenuItem = map[uint]*MenuItem{} @@ -382,8 +563,6 @@ func appName() string { } func appNew(name string) pointer { - C.install_signal_handlers() - // Name is already sanitized by sanitizeAppName() in application_linux.go appId := fmt.Sprintf("org.wails.%s", name) nameC := C.CString(appId) @@ -552,6 +731,24 @@ func menuGetRadioGroup(item *linuxMenuItem) *GSList { return (*GSList)(C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(item.native))) } +func menuClear(menu *Menu) { + menuShell := (*C.GtkMenuShell)((menu.impl).(*linuxMenu).native) + children := C.gtk_container_get_children((*C.GtkContainer)(unsafe.Pointer(menuShell))) + if children != nil { + // Save the original pointer to free later + originalList := children + // Iterate through all children and remove them + for children != nil { + child := (*C.GtkWidget)(children.data) + if child != nil { + C.gtk_container_remove((*C.GtkContainer)(unsafe.Pointer(menuShell)), child) + } + children = children.next + } + C.g_list_free(originalList) + } +} + //export handleClick func handleClick(idPtr unsafe.Pointer) { ident := C.CString("id") @@ -848,10 +1045,15 @@ func (w *linuxWebviewWindow) close() { } func (w *linuxWebviewWindow) enableDND() { - C.gtk_drag_dest_unset((*C.GtkWidget)(w.webview)) + // Pass window ID as pointer value (not pointer to ID) - same pattern as other signal handlers + winID := unsafe.Pointer(uintptr(w.parent.id)) + C.enableDND((*C.GtkWidget)(w.webview), C.gpointer(winID)) +} - windowId := C.uint(w.parent.id) - C.enableDND((*C.GtkWidget)(w.vbox), C.gpointer(&windowId)) +func (w *linuxWebviewWindow) disableDND() { + // Block external file drops while allowing internal HTML5 drag-and-drop + winID := unsafe.Pointer(uintptr(w.parent.id)) + C.disableDND((*C.GtkWidget)(w.webview), C.gpointer(winID)) } func (w *linuxWebviewWindow) execJS(js string) { @@ -869,6 +1071,61 @@ func (w *linuxWebviewWindow) execJS(js string) { }) } +// Preallocated buffer for drag-over JS calls to avoid allocations +// "window._wails.handleDragOver(XXXXX,YYYYY)" is max ~45 chars +var dragOverJSBuffer = C.CString(strings.Repeat(" ", 64)) +var emptyWorldName = C.CString("") + +// execJSDragOver executes JS for drag-over events with zero Go allocations. +// It directly writes to a preallocated C buffer. Must be called from main thread. +func (w *linuxWebviewWindow) execJSDragOver(x, y int) { + // Format: "window._wails.handleDragOver(X,Y)" + // Write directly to C buffer + buf := (*[64]byte)(unsafe.Pointer(dragOverJSBuffer)) + n := copy(buf[:], "window._wails.handleDragOver(") + n += writeInt(buf[n:], x) + buf[n] = ',' + n++ + n += writeInt(buf[n:], y) + buf[n] = ')' + n++ + buf[n] = 0 // null terminate + + C.webkit_web_view_evaluate_javascript(w.webKitWebView(), + dragOverJSBuffer, + C.long(n), + nil, + emptyWorldName, + nil, + nil, + nil) +} + +// writeInt writes an integer to a byte slice and returns the number of bytes written +func writeInt(buf []byte, n int) int { + if n < 0 { + buf[0] = '-' + return 1 + writeInt(buf[1:], -n) + } + if n == 0 { + buf[0] = '0' + return 1 + } + // Count digits + tmp := n + digits := 0 + for tmp > 0 { + digits++ + tmp /= 10 + } + // Write digits in reverse + for i := digits - 1; i >= 0; i-- { + buf[i] = byte('0' + n%10) + n /= 10 + } + return digits +} + func getMousePosition() (int, int, *Screen) { var x, y C.gint var screen *C.GdkScreen @@ -1113,6 +1370,10 @@ func windowNewWebview(parentId uint, gpuPolicy WebviewGpuPolicy) pointer { C.webkit_user_content_manager_register_script_message_handler(manager, c.String("external")) webView := C.webkit_web_view_new_with_user_content_manager(manager) + fixSignalHandlers.Do(func() { + C.install_signal_handlers() + }) + C.save_webview_to_content_manager(unsafe.Pointer(manager), unsafe.Pointer(webView)) // attach window id to both the webview and contentmanager @@ -1568,6 +1829,45 @@ func onMenuButtonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data C.uintptr_t return C.gboolean(0) } +//export onDragEnter +func onDragEnter(data unsafe.Pointer) { + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + return + } + // HandleDragEnter is Linux-specific (GTK intercepts drag events) + if w, ok := targetWindow.(*WebviewWindow); ok { + w.HandleDragEnter() + } +} + +//export onDragLeave +func onDragLeave(data unsafe.Pointer) { + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + return + } + // HandleDragLeave is Linux-specific (GTK intercepts drag events) + if w, ok := targetWindow.(*WebviewWindow); ok { + w.HandleDragLeave() + } +} + +//export onDragOver +func onDragOver(x C.gint, y C.gint, data unsafe.Pointer) { + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + return + } + // HandleDragOver is Linux-specific (GTK intercepts drag events) + if w, ok := targetWindow.(*WebviewWindow); ok { + w.HandleDragOver(int(x), int(y)) + } +} + //export onUriList func onUriList(extracted **C.char, x C.gint, y C.gint, data unsafe.Pointer) { // Credit: https://groups.google.com/g/golang-nuts/c/bI17Bpck8K4/m/DVDa7EMtDAAJ @@ -1578,12 +1878,17 @@ func onUriList(extracted **C.char, x C.gint, y C.gint, data unsafe.Pointer) { extracted = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(extracted)) + offset)) } - windowDragAndDropBuffer <- &dragAndDropMessage{ - windowId: uint(*((*C.uint)(data))), - filenames: filenames, - X: int(x), - Y: int(y), + // Window ID is stored as the pointer value itself (not pointing to memory) + // Same pattern as other signal handlers in this file + windowId := uint(uintptr(data)) + targetWindow, ok := globalApplication.Window.GetByID(windowId) + if !ok || targetWindow == nil { + globalApplication.error("onUriList could not find window with ID: %d", windowId) + return } + + // Send to frontend for drop target detection and filtering + targetWindow.InitiateFrontendDropProcessing(filenames, int(x), int(y)) } var debounceTimer *time.Timer @@ -1722,7 +2027,7 @@ func messageDialogCB(button C.int) { fmt.Println("messageDialogCB", button) } -func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter) (chan string, error) { +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter, currentName string) (chan string, error) { titleStr := C.CString(title) defer C.free(unsafe.Pointer(titleStr)) cancelStr := C.CString("_Cancel") @@ -1772,6 +2077,15 @@ func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden b C.free(unsafe.Pointer(path)) } + // Set the current name for save dialogs to pre-populate the filename + if currentName != "" && action == C.GTK_FILE_CHOOSER_ACTION_SAVE { + nameStr := C.CString(currentName) + C.gtk_file_chooser_set_current_name( + (*C.GtkFileChooser)(fc), + nameStr) + C.free(unsafe.Pointer(nameStr)) + } + // FIXME: This should be consolidated - duplicate exists in linux_purego.go buildStringAndFree := func(s C.gpointer) string { bytes := []byte{} @@ -1845,7 +2159,8 @@ func runOpenFileDialog(dialog *OpenFileDialogStruct) (chan string, error) { dialog.title, action, buttonText, - dialog.filters) + dialog.filters, + "") } func runQuestionDialog(parent pointer, options *MessageDialog) int { @@ -1927,7 +2242,8 @@ func runSaveFileDialog(dialog *SaveFileDialogStruct) (chan string, error) { dialog.title, C.GTK_FILE_CHOOSER_ACTION_SAVE, buttonText, - dialog.filters) + dialog.filters, + dialog.filename) return results, err } diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go index 5d6c1cd95..d59d74ac9 100644 --- a/v3/pkg/application/linux_purego.go +++ b/v3/pkg/application/linux_purego.go @@ -5,7 +5,6 @@ package application import ( "fmt" "os" - "strings" "unsafe" "github.com/ebitengine/purego" @@ -106,6 +105,7 @@ var ( gBytesUnref func(uintptr) gFree func(pointer) gIdleAdd func(uintptr) + gListFree func(*GList) gObjectRefSink func(pointer) gObjectUnref func(pointer) gSignalConnectData func(pointer, string, uintptr, pointer, bool, int) int @@ -139,6 +139,8 @@ var ( gtkCheckMenuItemNewWithLabel func(string) pointer gtkCheckMenuItemSetActive func(pointer, int) gtkContainerAdd func(pointer, pointer) + gtkContainerGetChildren func(pointer) *GList + gtkContainerRemove func(pointer, pointer) gtkCSSProviderLoadFromData func(pointer, string, int, pointer) gtkCSSProviderNew func() pointer gtkDialogAddButton func(pointer, string, int) @@ -152,6 +154,7 @@ var ( gtkFileChooserSetAction func(pointer, int) gtkFileChooserSetCreateFolders func(pointer, bool) gtkFileChooserSetCurrentFolder func(pointer, string) + gtkFileChooserSetCurrentName func(pointer, string) gtkFileChooserSetSelectMultiple func(pointer, bool) gtkFileChooserSetShowHidden func(pointer, bool) gtkFileFilterAddPattern func(pointer, string) @@ -260,6 +263,7 @@ func init() { purego.RegisterLibFunc(&gBytesUnref, gtk, "g_bytes_unref") purego.RegisterLibFunc(&gFree, gtk, "g_free") purego.RegisterLibFunc(&gIdleAdd, gtk, "g_idle_add") + purego.RegisterLibFunc(&gListFree, gtk, "g_list_free") purego.RegisterLibFunc(&gObjectRefSink, gtk, "g_object_ref_sink") purego.RegisterLibFunc(&gObjectUnref, gtk, "g_object_unref") purego.RegisterLibFunc(&gSignalConnectData, gtk, "g_signal_connect_data") @@ -293,6 +297,8 @@ func init() { purego.RegisterLibFunc(>kCheckMenuItemNewWithLabel, gtk, "gtk_check_menu_item_new_with_label") purego.RegisterLibFunc(>kCheckMenuItemSetActive, gtk, "gtk_check_menu_item_set_active") purego.RegisterLibFunc(>kContainerAdd, gtk, "gtk_container_add") + purego.RegisterLibFunc(>kContainerGetChildren, gtk, "gtk_container_get_children") + purego.RegisterLibFunc(>kContainerRemove, gtk, "gtk_container_remove") purego.RegisterLibFunc(>kCSSProviderLoadFromData, gtk, "gtk_css_provider_load_from_data") purego.RegisterLibFunc(>kDialogAddButton, gtk, "gtk_dialog_add_button") purego.RegisterLibFunc(>kDialogGetContentArea, gtk, "gtk_dialog_get_content_area") @@ -305,6 +311,7 @@ func init() { purego.RegisterLibFunc(>kFileChooserSetAction, gtk, "gtk_file_chooser_set_action") purego.RegisterLibFunc(>kFileChooserSetCreateFolders, gtk, "gtk_file_chooser_set_create_folders") purego.RegisterLibFunc(>kFileChooserSetCurrentFolder, gtk, "gtk_file_chooser_set_current_folder") + purego.RegisterLibFunc(>kFileChooserSetCurrentName, gtk, "gtk_file_chooser_set_current_name") purego.RegisterLibFunc(>kFileChooserSetSelectMultiple, gtk, "gtk_file_chooser_set_select_multiple") purego.RegisterLibFunc(>kFileChooserSetShowHidden, gtk, "gtk_file_chooser_set_show_hidden") purego.RegisterLibFunc(>kFileFilterAddPattern, gtk, "gtk_file_filter_add_pattern") @@ -393,12 +400,15 @@ func appName() string { } func appNew(name string) pointer { - GApplicationDefaultFlags := uint(0) + // Use NON_UNIQUE to allow multiple instances of the application to run + // This matches the behavior of gtk_init/gtk_main used in v2 + // G_APPLICATION_NON_UNIQUE = (1 << 5) = 32 + GApplicationNonUnique := uint(32) // Name is already sanitized by sanitizeAppName() in application_linux.go identifier := fmt.Sprintf("org.wails.%s", name) - return pointer(gtkApplicationNew(identifier, GApplicationDefaultFlags)) + return pointer(gtkApplicationNew(identifier, GApplicationNonUnique)) } func appRun(application pointer) error { @@ -516,6 +526,24 @@ func menuGetRadioGroup(item *linuxMenuItem) *GSList { return (*GSList)(gtkRadioMenuItemGetGroup(pointer(item.native))) } +func menuClear(menu *Menu) { + menuShell := pointer((menu.impl).(*linuxMenu).native) + children := gtkContainerGetChildren(menuShell) + if children != nil { + // Save the original pointer to free later + originalList := children + // Iterate through all children and remove them + for children != nil { + child := children.data + if child != nilPointer { + gtkContainerRemove(menuShell, child) + } + children = children.next + } + gListFree(originalList) + } +} + func attachMenuHandler(item *MenuItem) { handleClick := func() { item := item @@ -1024,7 +1052,7 @@ func windowMove(window pointer, x, y int) { gtkWindowMove(window, x, y) } -func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter) ([]string, error) { +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter, currentName string) ([]string, error) { GtkResponseCancel := 0 GtkResponseAccept := 1 @@ -1056,6 +1084,12 @@ func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden b gtkFileChooserSetCurrentFolder(fc, currentFolder) } + // Set the current name for save dialogs to pre-populate the filename + const GtkFileChooserActionSave = 1 + if currentName != "" && action == GtkFileChooserActionSave { + gtkFileChooserSetCurrentName(fc, currentName) + } + buildStringAndFree := func(s pointer) string { bytes := []byte{} p := unsafe.Pointer(s) @@ -1125,7 +1159,8 @@ func runOpenFileDialog(dialog *OpenFileDialogStruct) ([]string, error) { dialog.title, GtkFileChooserActionOpen, buttonText, - dialog.filters) + dialog.filters, + "") } func runQuestionDialog(parent pointer, options *MessageDialog) int { @@ -1206,7 +1241,8 @@ func runSaveFileDialog(dialog *SaveFileDialogStruct) (string, error) { dialog.title, GtkFileChooserActionSave, buttonText, - dialog.filters) + dialog.filters, + dialog.filename) if err != nil || len(results) == 0 { return "", err diff --git a/v3/pkg/application/logger_ios.go b/v3/pkg/application/logger_ios.go index 559a033a4..584e96609 100644 --- a/v3/pkg/application/logger_ios.go +++ b/v3/pkg/application/logger_ios.go @@ -12,18 +12,19 @@ import "C" import ( "bytes" "context" - "encoding/json" "log/slog" "strings" "time" "unsafe" + + "encoding/json" ) // iosConsoleHandler implements slog.Handler and forwards records to the WKWebView console. type iosConsoleHandler struct { - level slog.Leveler - attrs []slog.Attr - group string + level slog.Leveler + attrs []slog.Attr + group string } func (h *iosConsoleHandler) Enabled(_ context.Context, lvl slog.Level) bool { diff --git a/v3/pkg/application/menu_internal_test.go b/v3/pkg/application/menu_internal_test.go new file mode 100644 index 000000000..488e9a520 --- /dev/null +++ b/v3/pkg/application/menu_internal_test.go @@ -0,0 +1,403 @@ +package application + +import ( + "testing" +) + +func TestNewMenu(t *testing.T) { + menu := NewMenu() + if menu == nil { + t.Fatal("NewMenu returned nil") + } + if menu.items != nil { + t.Error("items should be nil initially") + } +} + +func TestMenu_Add(t *testing.T) { + menu := NewMenu() + + item := menu.Add("Test Item") + if item == nil { + t.Fatal("Add returned nil") + } + if item.label != "Test Item" { + t.Errorf("label = %q, want %q", item.label, "Test Item") + } + if len(menu.items) != 1 { + t.Errorf("items count = %d, want 1", len(menu.items)) + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddSeparator(t *testing.T) { + menu := NewMenu() + + menu.AddSeparator() + if len(menu.items) != 1 { + t.Errorf("items count = %d, want 1", len(menu.items)) + } + if menu.items[0].itemType != separator { + t.Error("item should be a separator") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddCheckbox(t *testing.T) { + menu := NewMenu() + + item := menu.AddCheckbox("Check Me", true) + if item == nil { + t.Fatal("AddCheckbox returned nil") + } + if item.label != "Check Me" { + t.Errorf("label = %q, want %q", item.label, "Check Me") + } + if item.itemType != checkbox { + t.Error("item should be a checkbox") + } + if !item.checked { + t.Error("checkbox should be checked") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddRadio(t *testing.T) { + menu := NewMenu() + + item := menu.AddRadio("Radio Option", false) + if item == nil { + t.Fatal("AddRadio returned nil") + } + if item.label != "Radio Option" { + t.Errorf("label = %q, want %q", item.label, "Radio Option") + } + if item.itemType != radio { + t.Error("item should be a radio") + } + if item.checked { + t.Error("radio should not be checked") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_AddSubmenu(t *testing.T) { + menu := NewMenu() + + subMenu := menu.AddSubmenu("Submenu") + if subMenu == nil { + t.Fatal("AddSubmenu returned nil") + } + if len(menu.items) != 1 { + t.Errorf("items count = %d, want 1", len(menu.items)) + } + if menu.items[0].itemType != submenu { + t.Error("item should be a submenu") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_SetLabel(t *testing.T) { + menu := NewMenu() + + menu.SetLabel("My Menu") + if menu.label != "My Menu" { + t.Errorf("label = %q, want %q", menu.label, "My Menu") + } +} + +func TestMenu_ItemAt(t *testing.T) { + menu := NewMenu() + menu.Add("Item 0") + menu.Add("Item 1") + menu.Add("Item 2") + + item := menu.ItemAt(1) + if item == nil { + t.Fatal("ItemAt returned nil") + } + if item.label != "Item 1" { + t.Errorf("label = %q, want %q", item.label, "Item 1") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_ItemAt_OutOfBounds(t *testing.T) { + menu := NewMenu() + menu.Add("Item") + + if menu.ItemAt(-1) != nil { + t.Error("ItemAt(-1) should return nil") + } + if menu.ItemAt(1) != nil { + t.Error("ItemAt(1) should return nil for single item menu") + } + if menu.ItemAt(100) != nil { + t.Error("ItemAt(100) should return nil") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_FindByLabel(t *testing.T) { + menu := NewMenu() + menu.Add("First") + menu.Add("Second") + menu.Add("Third") + + found := menu.FindByLabel("Second") + if found == nil { + t.Fatal("FindByLabel returned nil") + } + if found.label != "Second" { + t.Errorf("label = %q, want %q", found.label, "Second") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_FindByLabel_NotFound(t *testing.T) { + menu := NewMenu() + menu.Add("First") + + found := menu.FindByLabel("NonExistent") + if found != nil { + t.Error("FindByLabel should return nil for non-existent label") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_FindByLabel_InSubmenu(t *testing.T) { + menu := NewMenu() + submenu := menu.AddSubmenu("Submenu") + submenu.Add("Nested Item") + + found := menu.FindByLabel("Nested Item") + if found == nil { + t.Fatal("FindByLabel should find item in submenu") + } + if found.label != "Nested Item" { + t.Errorf("label = %q, want %q", found.label, "Nested Item") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_RemoveMenuItem(t *testing.T) { + menu := NewMenu() + item1 := menu.Add("First") + item2 := menu.Add("Second") + menu.Add("Third") + + menu.RemoveMenuItem(item2) + if len(menu.items) != 2 { + t.Errorf("items count = %d, want 2", len(menu.items)) + } + if menu.FindByLabel("Second") != nil { + t.Error("Second item should be removed") + } + + // Clean up + removeMenuItemByID(item1.id) +} + +func TestMenu_Clear(t *testing.T) { + menu := NewMenu() + menu.Add("First") + menu.Add("Second") + menu.Add("Third") + + menu.Clear() + if menu.items != nil { + t.Error("items should be nil after Clear") + } +} + +func TestMenu_Append(t *testing.T) { + menu1 := NewMenu() + menu1.Add("Item 1") + + menu2 := NewMenu() + menu2.Add("Item 2") + menu2.Add("Item 3") + + menu1.Append(menu2) + if len(menu1.items) != 3 { + t.Errorf("items count = %d, want 3", len(menu1.items)) + } + + // Clean up + menu1.Destroy() +} + +func TestMenu_Append_Nil(t *testing.T) { + menu := NewMenu() + menu.Add("Item") + + menu.Append(nil) + if len(menu.items) != 1 { + t.Error("Append(nil) should not change menu") + } + + // Clean up + menu.Destroy() +} + +func TestMenu_Prepend(t *testing.T) { + menu1 := NewMenu() + menu1.Add("Item 3") + + menu2 := NewMenu() + menu2.Add("Item 1") + menu2.Add("Item 2") + + menu1.Prepend(menu2) + if len(menu1.items) != 3 { + t.Errorf("items count = %d, want 3", len(menu1.items)) + } + if menu1.items[0].label != "Item 1" { + t.Errorf("First item should be 'Item 1', got %q", menu1.items[0].label) + } + + // Clean up + menu1.Destroy() +} + +func TestMenu_Clone(t *testing.T) { + menu := NewMenu() + menu.SetLabel("Original Menu") + menu.Add("Item 1") + menu.Add("Item 2") + + clone := menu.Clone() + if clone == menu { + t.Error("Clone should return different pointer") + } + if clone.label != menu.label { + t.Error("Clone should have same label") + } + if len(clone.items) != len(menu.items) { + t.Error("Clone should have same number of items") + } + + // Clean up + menu.Destroy() + clone.Destroy() +} + +func TestNewMenuFromItems(t *testing.T) { + item1 := NewMenuItem("Item 1") + item2 := NewMenuItem("Item 2") + item3 := NewMenuItem("Item 3") + + menu := NewMenuFromItems(item1, item2, item3) + if menu == nil { + t.Fatal("NewMenuFromItems returned nil") + } + if len(menu.items) != 3 { + t.Errorf("items count = %d, want 3", len(menu.items)) + } + + // Clean up + menu.Destroy() +} + +func TestNewSubmenu(t *testing.T) { + items := NewMenu() + items.Add("Sub Item 1") + items.Add("Sub Item 2") + + item := NewSubmenu("My Submenu", items) + if item == nil { + t.Fatal("NewSubmenu returned nil") + } + if item.label != "My Submenu" { + t.Errorf("label = %q, want %q", item.label, "My Submenu") + } + if item.submenu != items { + t.Error("submenu should be the provided menu") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenu_ProcessRadioGroups(t *testing.T) { + menu := NewMenu() + + // Add some non-radio items + menu.Add("Regular Item") + + // Add a group of radio items + radio1 := menu.AddRadio("Radio 1", true) + radio2 := menu.AddRadio("Radio 2", false) + radio3 := menu.AddRadio("Radio 3", false) + + // Add separator to end the group + menu.AddSeparator() + + // Add another group + radio4 := menu.AddRadio("Radio A", true) + radio5 := menu.AddRadio("Radio B", false) + + // Process radio groups + menu.processRadioGroups() + + // First group should be linked + if len(radio1.radioGroupMembers) != 3 { + t.Errorf("First group should have 3 members, got %d", len(radio1.radioGroupMembers)) + } + if len(radio2.radioGroupMembers) != 3 { + t.Errorf("radio2 should have 3 members, got %d", len(radio2.radioGroupMembers)) + } + if len(radio3.radioGroupMembers) != 3 { + t.Errorf("radio3 should have 3 members, got %d", len(radio3.radioGroupMembers)) + } + + // Second group should be linked + if len(radio4.radioGroupMembers) != 2 { + t.Errorf("Second group should have 2 members, got %d", len(radio4.radioGroupMembers)) + } + if len(radio5.radioGroupMembers) != 2 { + t.Errorf("radio5 should have 2 members, got %d", len(radio5.radioGroupMembers)) + } + + // Clean up + menu.Destroy() +} + +func TestMenu_SetContextData(t *testing.T) { + menu := NewMenu() + menu.Add("Item 1") + menu.Add("Item 2") + + data := &ContextMenuData{Data: "test-data"} + menu.setContextData(data) + + // Verify data was set on items + for _, item := range menu.items { + if item.contextMenuData != data { + t.Error("Context data should be set on all items") + } + } + + // Clean up + menu.Destroy() +} diff --git a/v3/pkg/application/menu_linux.go b/v3/pkg/application/menu_linux.go index b47ad6dd0..0288000cb 100644 --- a/v3/pkg/application/menu_linux.go +++ b/v3/pkg/application/menu_linux.go @@ -29,6 +29,9 @@ func (m *linuxMenu) processMenu(menu *Menu) { menu: menu, native: menuNew(), } + } else { + // Clear existing menu items before rebuilding (prevents appending on Update()) + menuClear(menu) } var currentRadioGroup GSListPointer diff --git a/v3/pkg/application/menuitem_internal_test.go b/v3/pkg/application/menuitem_internal_test.go new file mode 100644 index 000000000..12843d4c8 --- /dev/null +++ b/v3/pkg/application/menuitem_internal_test.go @@ -0,0 +1,413 @@ +package application + +import ( + "testing" +) + +func TestMenuItemType_Constants(t *testing.T) { + // Verify menu item type constants are distinct + if text != 0 { + t.Error("text should be 0") + } + if separator != 1 { + t.Error("separator should be 1") + } + if checkbox != 2 { + t.Error("checkbox should be 2") + } + if radio != 3 { + t.Error("radio should be 3") + } + if submenu != 4 { + t.Error("submenu should be 4") + } +} + +func TestNewMenuItem(t *testing.T) { + item := NewMenuItem("Test Label") + + if item == nil { + t.Fatal("NewMenuItem returned nil") + } + if item.label != "Test Label" { + t.Errorf("label = %q, want %q", item.label, "Test Label") + } + if item.itemType != text { + t.Error("itemType should be text") + } + if item.id == 0 { + t.Error("id should be non-zero") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewMenuItemSeparator(t *testing.T) { + item := NewMenuItemSeparator() + + if item == nil { + t.Fatal("NewMenuItemSeparator returned nil") + } + if item.itemType != separator { + t.Error("itemType should be separator") + } +} + +func TestNewMenuItemCheckbox(t *testing.T) { + item := NewMenuItemCheckbox("Checkbox", true) + + if item == nil { + t.Fatal("NewMenuItemCheckbox returned nil") + } + if item.label != "Checkbox" { + t.Errorf("label = %q, want %q", item.label, "Checkbox") + } + if item.itemType != checkbox { + t.Error("itemType should be checkbox") + } + if !item.checked { + t.Error("checked should be true") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewMenuItemCheckbox_Unchecked(t *testing.T) { + item := NewMenuItemCheckbox("Unchecked", false) + + if item.checked { + t.Error("checked should be false") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewMenuItemRadio(t *testing.T) { + item := NewMenuItemRadio("Radio", true) + + if item == nil { + t.Fatal("NewMenuItemRadio returned nil") + } + if item.label != "Radio" { + t.Errorf("label = %q, want %q", item.label, "Radio") + } + if item.itemType != radio { + t.Error("itemType should be radio") + } + if !item.checked { + t.Error("checked should be true") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestNewSubMenuItem(t *testing.T) { + item := NewSubMenuItem("Submenu") + + if item == nil { + t.Fatal("NewSubMenuItem returned nil") + } + if item.label != "Submenu" { + t.Errorf("label = %q, want %q", item.label, "Submenu") + } + if item.itemType != submenu { + t.Error("itemType should be submenu") + } + if item.submenu == nil { + t.Error("submenu should not be nil") + } + if item.submenu.label != "Submenu" { + t.Errorf("submenu.label = %q, want %q", item.submenu.label, "Submenu") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItemMap_AddGetRemove(t *testing.T) { + item := NewMenuItem("Test") + id := item.id + + // Item should be in map + retrieved := getMenuItemByID(id) + if retrieved != item { + t.Error("getMenuItemByID should return the same item") + } + + // Remove item + removeMenuItemByID(id) + + // Item should be gone + retrieved = getMenuItemByID(id) + if retrieved != nil { + t.Error("getMenuItemByID should return nil after removal") + } +} + +func TestGetMenuItemByID_NotFound(t *testing.T) { + result := getMenuItemByID(999999) + if result != nil { + t.Error("getMenuItemByID should return nil for non-existent ID") + } +} + +func TestMenuItem_UniqueIDs(t *testing.T) { + item1 := NewMenuItem("Item 1") + item2 := NewMenuItem("Item 2") + item3 := NewMenuItem("Item 3") + + if item1.id == item2.id || item2.id == item3.id || item1.id == item3.id { + t.Error("Menu items should have unique IDs") + } + + // Clean up + removeMenuItemByID(item1.id) + removeMenuItemByID(item2.id) + removeMenuItemByID(item3.id) +} + +func TestMenuItem_Label(t *testing.T) { + item := NewMenuItem("Original") + + if item.Label() != "Original" { + t.Errorf("Label() = %q, want %q", item.Label(), "Original") + } + + item.SetLabel("Updated") + if item.label != "Updated" { + t.Errorf("label = %q, want %q", item.label, "Updated") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Enabled(t *testing.T) { + item := NewMenuItem("Test") + + if item.disabled { + t.Error("disabled should default to false") + } + + item.SetEnabled(false) + if !item.disabled { + t.Error("disabled should be true after SetEnabled(false)") + } + + item.SetEnabled(true) + if item.disabled { + t.Error("disabled should be false after SetEnabled(true)") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Checked(t *testing.T) { + item := NewMenuItemCheckbox("Test", false) + + if item.checked { + t.Error("checked should be false") + } + + item.SetChecked(true) + if !item.checked { + t.Error("checked should be true after SetChecked(true)") + } + + item.SetChecked(false) + if item.checked { + t.Error("checked should be false after SetChecked(false)") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Hidden(t *testing.T) { + item := NewMenuItem("Test") + + if item.hidden { + t.Error("hidden should default to false") + } + + item.SetHidden(true) + if !item.hidden { + t.Error("hidden should be true after SetHidden(true)") + } + + item.SetHidden(false) + if item.hidden { + t.Error("hidden should be false after SetHidden(false)") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Tooltip(t *testing.T) { + item := NewMenuItem("Test") + + if item.tooltip != "" { + t.Error("tooltip should default to empty string") + } + + item.SetTooltip("Tooltip text") + if item.tooltip != "Tooltip text" { + t.Errorf("tooltip = %q, want %q", item.tooltip, "Tooltip text") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Bitmap(t *testing.T) { + item := NewMenuItem("Test") + + if item.bitmap != nil { + t.Error("bitmap should default to nil") + } + + bitmap := []byte{0x89, 0x50, 0x4E, 0x47} + item.SetBitmap(bitmap) + if len(item.bitmap) != 4 { + t.Error("bitmap should be set") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_OnClick(t *testing.T) { + item := NewMenuItem("Test") + + if item.callback != nil { + t.Error("callback should default to nil") + } + + called := false + item.OnClick(func(ctx *Context) { + called = true + }) + + if item.callback == nil { + t.Error("callback should be set after OnClick") + } + + // Call the callback + item.callback(nil) + if !called { + t.Error("callback should have been called") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_SetAccelerator(t *testing.T) { + item := NewMenuItem("Test") + + if item.accelerator != nil { + t.Error("accelerator should default to nil") + } + + result := item.SetAccelerator("Ctrl+A") + if result != item { + t.Error("SetAccelerator should return the same item for chaining") + } + + if item.accelerator == nil { + t.Error("accelerator should be set after SetAccelerator") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_GetAccelerator(t *testing.T) { + item := NewMenuItem("Test") + + if item.GetAccelerator() != "" { + t.Error("GetAccelerator should return empty string when not set") + } + + item.SetAccelerator("Ctrl+B") + acc := item.GetAccelerator() + if acc == "" { + t.Error("GetAccelerator should return non-empty string when set") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_RemoveAccelerator(t *testing.T) { + item := NewMenuItem("Test") + item.SetAccelerator("Ctrl+C") + + item.RemoveAccelerator() + if item.accelerator != nil { + t.Error("accelerator should be nil after RemoveAccelerator") + } + + // Clean up + removeMenuItemByID(item.id) +} + +func TestMenuItem_Clone(t *testing.T) { + original := NewMenuItem("Original") + original.SetTooltip("Tooltip") + original.SetChecked(true) + original.SetEnabled(false) + + clone := original.Clone() + + if clone == original { + t.Error("Clone should return a different pointer") + } + if clone.label != original.label { + t.Error("Clone should have same label") + } + if clone.tooltip != original.tooltip { + t.Error("Clone should have same tooltip") + } + if clone.checked != original.checked { + t.Error("Clone should have same checked state") + } + if clone.disabled != original.disabled { + t.Error("Clone should have same disabled state") + } + // Note: Clone preserves the ID (shallow clone behavior) + + // Clean up + removeMenuItemByID(original.id) + removeMenuItemByID(clone.id) +} + +func TestMenuItem_Chaining(t *testing.T) { + item := NewMenuItem("Test"). + SetTooltip("Tooltip"). + SetEnabled(false). + SetHidden(true). + SetAccelerator("Ctrl+X") + + if item.tooltip != "Tooltip" { + t.Error("Chaining SetTooltip failed") + } + if !item.disabled { + t.Error("Chaining SetEnabled failed") + } + if !item.hidden { + t.Error("Chaining SetHidden failed") + } + if item.accelerator == nil { + t.Error("Chaining SetAccelerator failed") + } + + // Clean up + removeMenuItemByID(item.id) +} diff --git a/v3/pkg/application/messageprocessor.go b/v3/pkg/application/messageprocessor.go index 3a3802aa5..4829d3dbf 100644 --- a/v3/pkg/application/messageprocessor.go +++ b/v3/pkg/application/messageprocessor.go @@ -158,8 +158,8 @@ func (m *MessageProcessor) Error(message string, args ...any) { m.logger.Error(message, args...) } -func (m *MessageProcessor) Info(message string, args ...any) { - m.logger.Info(message, args...) +func (m *MessageProcessor) Debug(message string, args ...any) { + m.logger.Debug(message, args...) } func (m *MessageProcessor) logRuntimeCall(req *RuntimeRequest) { @@ -195,5 +195,5 @@ func (m *MessageProcessor) logRuntimeCall(req *RuntimeRequest) { methodName = androidMethodNames[req.Method] } - m.Info("Runtime call:", "method", objectName+"."+methodName, "args", req.Args.String()) + m.Debug("Runtime call:", "method", objectName+"."+methodName, "args", req.Args.String()) } diff --git a/v3/pkg/application/messageprocessor_args.go b/v3/pkg/application/messageprocessor_args.go index 838e389f5..bb3ffc6d0 100644 --- a/v3/pkg/application/messageprocessor_args.go +++ b/v3/pkg/application/messageprocessor_args.go @@ -1,8 +1,9 @@ package application import ( - "encoding/json" "fmt" + + "encoding/json" ) type Args struct { diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go index 20023a5a6..71f60a00e 100644 --- a/v3/pkg/application/messageprocessor_call.go +++ b/v3/pkg/application/messageprocessor_call.go @@ -2,9 +2,10 @@ package application import ( "context" - "encoding/json" "errors" + "encoding/json" + "github.com/wailsapp/wails/v3/pkg/errs" ) @@ -30,7 +31,7 @@ func (m *MessageProcessor) processCallCancelMethod(req *RuntimeRequest) (any, er if cancel != nil { cancel() - m.Info("Binding call cancelled:", "id", *callID) + m.Debug("Binding call cancelled:", "id", *callID) } return unit, nil } @@ -54,7 +55,7 @@ func (m *MessageProcessor) processCallMethod(ctx context.Context, req *RuntimeRe if options.MethodName == "" { methodRef = options.MethodID } - m.Info("Binding call started:", "id", *callID, "method", methodRef) + m.Debug("Binding call started:", "id", *callID, "method", methodRef) ctx, cancel := context.WithCancel(context.WithoutCancel(ctx)) @@ -107,7 +108,7 @@ func (m *MessageProcessor) processCallMethod(ctx context.Context, req *RuntimeRe defer func() { var jsonResult []byte jsonResult, _ = json.Marshal(result) - m.Info("Binding call complete:", "id", *callID, "method", boundMethod, "args", string(jsonArgs), "result", string(jsonResult)) + m.Debug("Binding call complete:", "id", *callID, "method", boundMethod, "args", string(jsonArgs), "result", string(jsonResult)) }() // Set the context values for the window diff --git a/v3/pkg/application/messageprocessor_window.go b/v3/pkg/application/messageprocessor_window.go index 1b3134c4e..1baf57606 100644 --- a/v3/pkg/application/messageprocessor_window.go +++ b/v3/pkg/application/messageprocessor_window.go @@ -2,7 +2,6 @@ package application import ( "fmt" - "log/slog" "github.com/wailsapp/wails/v3/pkg/errs" ) @@ -58,7 +57,7 @@ const ( WindowZoomOut = 47 WindowZoomReset = 48 WindowSnapAssist = 49 - WindowDropZoneDropped = 50 + WindowFilesDropped = 50 WindowPrint = 51 ) @@ -112,7 +111,7 @@ var windowMethodNames = map[int]string{ WindowZoomIn: "ZoomIn", WindowZoomOut: "ZoomOut", WindowZoomReset: "ZoomReset", - WindowDropZoneDropped: "DropZoneDropped", + WindowFilesDropped: "FilesDropped", WindowSnapAssist: "SnapAssist", WindowPrint: "Print", } @@ -354,49 +353,36 @@ func (m *MessageProcessor) processWindowMethod( case WindowZoomReset: window.ZoomReset() return unit, nil - case WindowDropZoneDropped: - m.Info( - "[DragDropDebug] processWindowMethod: Entered WindowDropZoneDropped case", - ) - - slog.Info("[DragDropDebug] Raw 'args' payload string:", "data", string(req.Args.rawData)) - + case WindowFilesDropped: var payload fileDropPayload - err := req.Args.ToStruct(&payload) if err != nil { - return nil, errs.WrapInvalidWindowCallErrorf(err, "Error decoding file drop payload from 'args' parameter") + return nil, errs.WrapInvalidWindowCallErrorf(err, "error decoding file drop payload") } - m.Info( + m.Debug( "[DragDropDebug] processWindowMethod: Decoded payload from 'args'", "payload", fmt.Sprintf("%+v", payload), ) - dropDetails := &DropZoneDetails{ + dropTarget := &DropTargetDetails{ X: payload.X, Y: payload.Y, ElementID: payload.ElementDetails.ID, ClassList: payload.ElementDetails.ClassList, - Attributes: payload.ElementDetails.Attributes, // Assumes DropZoneDetails struct is updated to include this field + Attributes: payload.ElementDetails.Attributes, } wvWindow, ok := window.(*WebviewWindow) if !ok { - return nil, errs.NewInvalidWindowCallErrorf("Error: Target window is not a WebviewWindow for FilesDroppedWithContext") + return nil, errs.NewInvalidWindowCallErrorf("target window is not a WebviewWindow") } msg := &dragAndDropMessage{ - windowId: wvWindow.id, - filenames: payload.Filenames, - DropZone: dropDetails, + windowId: wvWindow.id, + filenames: payload.Filenames, + DropTarget: dropTarget, } - - m.Info( - "[DragDropDebug] processApplicationMethod: Sending message to windowDragAndDropBuffer", - "message", - fmt.Sprintf("%+v", msg), - ) windowDragAndDropBuffer <- msg return unit, nil case WindowSnapAssist: diff --git a/v3/pkg/application/parameter_test.go b/v3/pkg/application/parameter_test.go new file mode 100644 index 000000000..3a106e48e --- /dev/null +++ b/v3/pkg/application/parameter_test.go @@ -0,0 +1,118 @@ +package application + +import ( + "reflect" + "testing" +) + +func TestParameter_IsType(t *testing.T) { + param := &Parameter{ + Name: "test", + TypeName: "string", + } + + if !param.IsType("string") { + t.Error("IsType should return true for matching type") + } + + if param.IsType("int") { + t.Error("IsType should return false for non-matching type") + } +} + +func TestParameter_IsError(t *testing.T) { + errorParam := &Parameter{ + Name: "err", + TypeName: "error", + } + + if !errorParam.IsError() { + t.Error("IsError should return true for error type") + } + + stringParam := &Parameter{ + Name: "s", + TypeName: "string", + } + + if stringParam.IsError() { + t.Error("IsError should return false for non-error type") + } +} + +func TestNewParameter(t *testing.T) { + stringType := reflect.TypeOf("") + param := newParameter("myParam", stringType) + + if param.Name != "myParam" { + t.Errorf("Name = %q, want %q", param.Name, "myParam") + } + + if param.TypeName != "string" { + t.Errorf("TypeName = %q, want %q", param.TypeName, "string") + } + + if param.ReflectType != stringType { + t.Error("ReflectType not set correctly") + } +} + +func TestCallError_Error(t *testing.T) { + err := &CallError{ + Kind: ReferenceError, + Message: "test error", + } + + if err.Error() != "test error" { + t.Errorf("Error() = %q, want %q", err.Error(), "test error") + } +} + +func TestCallError_Kinds(t *testing.T) { + tests := []struct { + kind ErrorKind + expected string + }{ + {ReferenceError, "ReferenceError"}, + {TypeError, "TypeError"}, + {RuntimeError, "RuntimeError"}, + } + + for _, tt := range tests { + if string(tt.kind) != tt.expected { + t.Errorf("ErrorKind = %q, want %q", string(tt.kind), tt.expected) + } + } +} + +func TestCallError_WithCause(t *testing.T) { + cause := map[string]string{"detail": "some detail"} + err := &CallError{ + Kind: RuntimeError, + Message: "runtime error occurred", + Cause: cause, + } + + if err.Error() != "runtime error occurred" { + t.Error("Error() should return the message") + } + + if err.Cause == nil { + t.Error("Cause should be set") + } +} + +func TestCallOptions_Fields(t *testing.T) { + opts := CallOptions{ + MethodID: 12345, + MethodName: "TestService.Method", + } + + if opts.MethodID != 12345 { + t.Error("MethodID not set correctly") + } + + if opts.MethodName != "TestService.Method" { + t.Error("MethodName not set correctly") + } +} diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go index f237b2669..ed809a130 100644 --- a/v3/pkg/application/popupmenu_windows.go +++ b/v3/pkg/application/popupmenu_windows.go @@ -1,8 +1,10 @@ package application import ( - "github.com/wailsapp/wails/v3/pkg/w32" + "sync/atomic" "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" ) const ( @@ -49,6 +51,7 @@ type Win32Menu struct { currentMenuID int onMenuClose func() onMenuOpen func() + isShowing atomic.Bool // guards against concurrent TrackPopupMenuEx calls } func (p *Win32Menu) newMenu() w32.HMENU { @@ -189,6 +192,12 @@ func NewApplicationMenu(parent *windowsWebviewWindow, inputMenu *Menu) *Win32Men } func (p *Win32Menu) ShowAt(x int, y int) { + // Prevent concurrent menu displays - TrackPopupMenuEx is blocking and + // calling it while another popup is showing causes "TrackPopupMenu failed" + if !p.isShowing.CompareAndSwap(false, true) { + return + } + defer p.isShowing.Store(false) w32.SetForegroundWindow(p.parent) @@ -216,7 +225,10 @@ func (p *Win32Menu) ShowAt(x int, y int) { } if !w32.TrackPopupMenuEx(p.menu, menuFlags, int32(x), int32(y), p.parent, nil) { - globalApplication.fatal("TrackPopupMenu failed") + // TrackPopupMenuEx can fail if called during menu transitions or rapid clicks. + // This is not fatal - just skip this menu display attempt. + globalApplication.debug("TrackPopupMenu failed - menu may already be showing") + return } if p.onMenuClose != nil { diff --git a/v3/pkg/application/screenmanager_internal_test.go b/v3/pkg/application/screenmanager_internal_test.go new file mode 100644 index 000000000..9445c1f6d --- /dev/null +++ b/v3/pkg/application/screenmanager_internal_test.go @@ -0,0 +1,435 @@ +package application + +import ( + "testing" +) + +func TestAlignment_Constants(t *testing.T) { + if TOP != 0 { + t.Error("TOP should be 0") + } + if RIGHT != 1 { + t.Error("RIGHT should be 1") + } + if BOTTOM != 2 { + t.Error("BOTTOM should be 2") + } + if LEFT != 3 { + t.Error("LEFT should be 3") + } +} + +func TestOffsetReference_Constants(t *testing.T) { + if BEGIN != 0 { + t.Error("BEGIN should be 0") + } + if END != 1 { + t.Error("END should be 1") + } +} + +func TestRect_Origin(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + origin := rect.Origin() + + if origin.X != 10 { + t.Errorf("origin.X = %d, want 10", origin.X) + } + if origin.Y != 20 { + t.Errorf("origin.Y = %d, want 20", origin.Y) + } +} + +func TestRect_Corner(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + corner := rect.Corner() + + if corner.X != 110 { // 10 + 100 + t.Errorf("corner.X = %d, want 110", corner.X) + } + if corner.Y != 220 { // 20 + 200 + t.Errorf("corner.Y = %d, want 220", corner.Y) + } +} + +func TestRect_InsideCorner(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + inside := rect.InsideCorner() + + if inside.X != 109 { // 10 + 100 - 1 + t.Errorf("inside.X = %d, want 109", inside.X) + } + if inside.Y != 219 { // 20 + 200 - 1 + t.Errorf("inside.Y = %d, want 219", inside.Y) + } +} + +func TestRect_right(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + if rect.right() != 110 { + t.Errorf("right() = %d, want 110", rect.right()) + } +} + +func TestRect_bottom(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + if rect.bottom() != 220 { + t.Errorf("bottom() = %d, want 220", rect.bottom()) + } +} + +func TestRect_Size(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + size := rect.Size() + + if size.Width != 100 { + t.Errorf("Width = %d, want 100", size.Width) + } + if size.Height != 200 { + t.Errorf("Height = %d, want 200", size.Height) + } +} + +func TestRect_IsEmpty(t *testing.T) { + tests := []struct { + rect Rect + expected bool + }{ + {Rect{X: 0, Y: 0, Width: 0, Height: 0}, true}, + {Rect{X: 0, Y: 0, Width: 100, Height: 0}, true}, + {Rect{X: 0, Y: 0, Width: 0, Height: 100}, true}, + {Rect{X: 0, Y: 0, Width: -1, Height: 100}, true}, + {Rect{X: 0, Y: 0, Width: 100, Height: -1}, true}, + {Rect{X: 0, Y: 0, Width: 100, Height: 200}, false}, + {Rect{X: 10, Y: 20, Width: 1, Height: 1}, false}, + } + + for _, tt := range tests { + result := tt.rect.IsEmpty() + if result != tt.expected { + t.Errorf("Rect%v.IsEmpty() = %v, want %v", tt.rect, result, tt.expected) + } + } +} + +func TestRect_Contains(t *testing.T) { + rect := Rect{X: 10, Y: 20, Width: 100, Height: 200} + + tests := []struct { + point Point + expected bool + }{ + {Point{X: 10, Y: 20}, true}, // top-left corner + {Point{X: 50, Y: 100}, true}, // inside + {Point{X: 109, Y: 219}, true}, // inside corner + {Point{X: 110, Y: 220}, false}, // corner (exclusive) + {Point{X: 0, Y: 0}, false}, // outside + {Point{X: 9, Y: 20}, false}, // left of rect + {Point{X: 10, Y: 19}, false}, // above rect + {Point{X: 111, Y: 100}, false}, // right of rect + {Point{X: 50, Y: 221}, false}, // below rect + } + + for _, tt := range tests { + result := rect.Contains(tt.point) + if result != tt.expected { + t.Errorf("Rect%v.Contains(%v) = %v, want %v", rect, tt.point, result, tt.expected) + } + } +} + +func TestRect_Intersect(t *testing.T) { + tests := []struct { + name string + r1 Rect + r2 Rect + expected Rect + }{ + { + name: "overlapping", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 50, Y: 50, Width: 100, Height: 100}, + expected: Rect{X: 50, Y: 50, Width: 50, Height: 50}, + }, + { + name: "no overlap - horizontal", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 200, Y: 0, Width: 100, Height: 100}, + expected: Rect{}, + }, + { + name: "no overlap - vertical", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 0, Y: 200, Width: 100, Height: 100}, + expected: Rect{}, + }, + { + name: "contained", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 25, Y: 25, Width: 50, Height: 50}, + expected: Rect{X: 25, Y: 25, Width: 50, Height: 50}, + }, + { + name: "identical", + r1: Rect{X: 10, Y: 20, Width: 100, Height: 100}, + r2: Rect{X: 10, Y: 20, Width: 100, Height: 100}, + expected: Rect{X: 10, Y: 20, Width: 100, Height: 100}, + }, + { + name: "empty rect 1", + r1: Rect{}, + r2: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + expected: Rect{}, + }, + { + name: "empty rect 2", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{}, + expected: Rect{}, + }, + { + name: "touching edges - no intersection", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 100, Y: 0, Width: 100, Height: 100}, + expected: Rect{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.r1.Intersect(tt.r2) + if result != tt.expected { + t.Errorf("Intersect: got %v, want %v", result, tt.expected) + } + }) + } +} + +func TestRect_distanceFromRectSquared(t *testing.T) { + tests := []struct { + name string + r1 Rect + r2 Rect + expected int + }{ + { + name: "overlapping - negative area", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 50, Y: 50, Width: 100, Height: 100}, + expected: -(50 * 50), // intersection area + }, + { + name: "horizontal gap", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 110, Y: 0, Width: 100, Height: 100}, + expected: 100, // gap of 10, squared + }, + { + name: "vertical gap", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 0, Y: 120, Width: 100, Height: 100}, + expected: 400, // gap of 20, squared + }, + { + name: "diagonal gap", + r1: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + r2: Rect{X: 110, Y: 110, Width: 100, Height: 100}, + expected: 200, // dX=10, dY=10, 10^2 + 10^2 = 200 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.r1.distanceFromRectSquared(tt.r2) + if result != tt.expected { + t.Errorf("distanceFromRectSquared: got %d, want %d", result, tt.expected) + } + }) + } +} + +func TestScreen_Origin(t *testing.T) { + screen := Screen{X: 100, Y: 200} + origin := screen.Origin() + + if origin.X != 100 { + t.Errorf("origin.X = %d, want 100", origin.X) + } + if origin.Y != 200 { + t.Errorf("origin.Y = %d, want 200", origin.Y) + } +} + +func TestScreen_scale(t *testing.T) { + screen := Screen{ScaleFactor: 2.0} + + tests := []struct { + value int + toDip bool + expected int + }{ + {100, true, 50}, // to DIP: 100 / 2 = 50 + {100, false, 200}, // to physical: 100 * 2 = 200 + {101, true, 51}, // to DIP: ceil(101 / 2) = 51 + {101, false, 202}, // to physical: floor(101 * 2) = 202 + {0, true, 0}, + {0, false, 0}, + } + + for _, tt := range tests { + result := screen.scale(tt.value, tt.toDip) + if result != tt.expected { + t.Errorf("scale(%d, %v) = %d, want %d", tt.value, tt.toDip, result, tt.expected) + } + } +} + +func TestScreen_scale_1_5(t *testing.T) { + screen := Screen{ScaleFactor: 1.5} + + tests := []struct { + value int + toDip bool + expected int + }{ + {150, true, 100}, // to DIP: ceil(150 / 1.5) = 100 + {100, false, 150}, // to physical: floor(100 * 1.5) = 150 + {100, true, 67}, // to DIP: ceil(100 / 1.5) = 67 + {67, false, 100}, // to physical: floor(67 * 1.5) = 100 + } + + for _, tt := range tests { + result := screen.scale(tt.value, tt.toDip) + if result != tt.expected { + t.Errorf("scale(%d, %v) with factor 1.5 = %d, want %d", tt.value, tt.toDip, result, tt.expected) + } + } +} + +func TestScreen_right(t *testing.T) { + screen := Screen{ + Bounds: Rect{X: 100, Y: 0, Width: 200, Height: 100}, + } + if screen.right() != 300 { + t.Errorf("right() = %d, want 300", screen.right()) + } +} + +func TestScreen_bottom(t *testing.T) { + screen := Screen{ + Bounds: Rect{X: 0, Y: 100, Width: 100, Height: 200}, + } + if screen.bottom() != 300 { + t.Errorf("bottom() = %d, want 300", screen.bottom()) + } +} + +func TestScreen_intersects(t *testing.T) { + screen1 := &Screen{ + X: 0, + Y: 0, + Bounds: Rect{X: 0, Y: 0, Width: 100, Height: 100}, + } + + screen2 := &Screen{ + X: 50, + Y: 50, + Bounds: Rect{X: 50, Y: 50, Width: 100, Height: 100}, + } + + screen3 := &Screen{ + X: 200, + Y: 0, + Bounds: Rect{X: 200, Y: 0, Width: 100, Height: 100}, + } + + if !screen1.intersects(screen2) { + t.Error("screen1 and screen2 should intersect") + } + if screen1.intersects(screen3) { + t.Error("screen1 and screen3 should not intersect") + } +} + +func TestPoint_Fields(t *testing.T) { + pt := Point{X: 10, Y: 20} + if pt.X != 10 || pt.Y != 20 { + t.Error("Point fields not set correctly") + } +} + +func TestSize_Fields(t *testing.T) { + size := Size{Width: 100, Height: 200} + if size.Width != 100 || size.Height != 200 { + t.Error("Size fields not set correctly") + } +} + +func TestScreen_Fields(t *testing.T) { + screen := Screen{ + ID: "display-1", + Name: "Primary Display", + ScaleFactor: 2.0, + X: 0, + Y: 0, + Size: Size{Width: 1920, Height: 1080}, + Bounds: Rect{X: 0, Y: 0, Width: 1920, Height: 1080}, + IsPrimary: true, + Rotation: 0, + } + + if screen.ID != "display-1" { + t.Error("ID not set correctly") + } + if screen.Name != "Primary Display" { + t.Error("Name not set correctly") + } + if screen.ScaleFactor != 2.0 { + t.Error("ScaleFactor not set correctly") + } + if !screen.IsPrimary { + t.Error("IsPrimary not set correctly") + } +} + +func TestScreenPlacement_Fields(t *testing.T) { + parent := &Screen{ID: "parent"} + child := &Screen{ID: "child"} + + placement := ScreenPlacement{ + screen: child, + parent: parent, + alignment: RIGHT, + offset: 100, + offsetReference: BEGIN, + } + + if placement.screen != child { + t.Error("screen not set correctly") + } + if placement.parent != parent { + t.Error("parent not set correctly") + } + if placement.alignment != RIGHT { + t.Error("alignment not set correctly") + } + if placement.offset != 100 { + t.Error("offset not set correctly") + } + if placement.offsetReference != BEGIN { + t.Error("offsetReference not set correctly") + } +} + +func TestNewScreenManager(t *testing.T) { + sm := newScreenManager(nil) + if sm == nil { + t.Fatal("newScreenManager returned nil") + } + if sm.screens != nil { + t.Error("screens should be nil initially") + } + if sm.primaryScreen != nil { + t.Error("primaryScreen should be nil initially") + } +} diff --git a/v3/pkg/application/services_test.go b/v3/pkg/application/services_test.go new file mode 100644 index 000000000..79fbb64ea --- /dev/null +++ b/v3/pkg/application/services_test.go @@ -0,0 +1,203 @@ +package application + +import ( + "context" + "testing" +) + +// Test service implementations +type testService struct { + name string +} + +func (s *testService) ServiceName() string { + return s.name +} + +type testServiceWithStartup struct { + started bool +} + +func (s *testServiceWithStartup) ServiceStartup(ctx context.Context, options ServiceOptions) error { + s.started = true + return nil +} + +type testServiceWithShutdown struct { + shutdown bool +} + +func (s *testServiceWithShutdown) ServiceShutdown() error { + s.shutdown = true + return nil +} + +type testServiceNoInterface struct { + value int +} + +func TestNewService(t *testing.T) { + svc := &testService{name: "test"} + service := NewService(svc) + + if service.instance == nil { + t.Error("NewService should set instance") + } + if service.Instance() != svc { + t.Error("Instance() should return the original service") + } +} + +func TestNewServiceWithOptions(t *testing.T) { + svc := &testService{name: "original"} + opts := ServiceOptions{ + Name: "custom-name", + Route: "/api", + } + service := NewServiceWithOptions(svc, opts) + + if service.Instance() != svc { + t.Error("Instance() should return the original service") + } + if service.options.Name != "custom-name" { + t.Errorf("options.Name = %q, want %q", service.options.Name, "custom-name") + } + if service.options.Route != "/api" { + t.Errorf("options.Route = %q, want %q", service.options.Route, "/api") + } +} + +func TestGetServiceName_FromOptions(t *testing.T) { + svc := &testService{name: "service-name"} + opts := ServiceOptions{Name: "options-name"} + service := NewServiceWithOptions(svc, opts) + + name := getServiceName(service) + if name != "options-name" { + t.Errorf("getServiceName() = %q, want %q (options takes precedence)", name, "options-name") + } +} + +func TestGetServiceName_FromInterface(t *testing.T) { + svc := &testService{name: "interface-name"} + service := NewService(svc) + + name := getServiceName(service) + if name != "interface-name" { + t.Errorf("getServiceName() = %q, want %q (from interface)", name, "interface-name") + } +} + +func TestGetServiceName_FromType(t *testing.T) { + svc := &testServiceNoInterface{value: 42} + service := NewService(svc) + + name := getServiceName(service) + // Should contain the type name + if name == "" { + t.Error("getServiceName() should return type name for services without ServiceName interface") + } + // The name should contain "testServiceNoInterface" + expected := "application.testServiceNoInterface" + if name != expected { + t.Errorf("getServiceName() = %q, want %q", name, expected) + } +} + +func TestService_Instance(t *testing.T) { + svc := &testService{name: "test"} + service := NewService(svc) + + instance := service.Instance() + if instance == nil { + t.Error("Instance() should not return nil") + } + + // Type assertion to verify it's the correct type + if _, ok := instance.(*testService); !ok { + t.Error("Instance() should return the correct type") + } +} + +func TestDefaultServiceOptions(t *testing.T) { + // Verify DefaultServiceOptions is zero-valued + if DefaultServiceOptions.Name != "" { + t.Errorf("DefaultServiceOptions.Name should be empty, got %q", DefaultServiceOptions.Name) + } + if DefaultServiceOptions.Route != "" { + t.Errorf("DefaultServiceOptions.Route should be empty, got %q", DefaultServiceOptions.Route) + } + if DefaultServiceOptions.MarshalError != nil { + t.Error("DefaultServiceOptions.MarshalError should be nil") + } +} + +func TestNewService_UsesDefaultOptions(t *testing.T) { + svc := &testService{name: "test"} + service := NewService(svc) + + // Service created with NewService should use DefaultServiceOptions + if service.options.Name != DefaultServiceOptions.Name { + t.Error("NewService should use DefaultServiceOptions") + } +} + +func TestServiceOptions_WithMarshalError(t *testing.T) { + customMarshal := func(err error) []byte { + return []byte(`{"error": "custom"}`) + } + + svc := &testService{name: "test"} + opts := ServiceOptions{ + MarshalError: customMarshal, + } + service := NewServiceWithOptions(svc, opts) + + if service.options.MarshalError == nil { + t.Error("MarshalError should be set") + } + + result := service.options.MarshalError(nil) + expected := `{"error": "custom"}` + if string(result) != expected { + t.Errorf("MarshalError result = %q, want %q", string(result), expected) + } +} + +func TestServiceStartupInterface(t *testing.T) { + svc := &testServiceWithStartup{} + service := NewService(svc) + + // Verify the service implements ServiceStartup + instance := service.Instance() + if startup, ok := instance.(ServiceStartup); ok { + err := startup.ServiceStartup(context.Background(), ServiceOptions{}) + if err != nil { + t.Errorf("ServiceStartup returned error: %v", err) + } + if !svc.started { + t.Error("ServiceStartup should have been called") + } + } else { + t.Error("testServiceWithStartup should implement ServiceStartup") + } +} + +func TestServiceShutdownInterface(t *testing.T) { + svc := &testServiceWithShutdown{} + service := NewService(svc) + + // Verify the service implements ServiceShutdown + instance := service.Instance() + if shutdown, ok := instance.(ServiceShutdown); ok { + err := shutdown.ServiceShutdown() + if err != nil { + t.Errorf("ServiceShutdown returned error: %v", err) + } + if !svc.shutdown { + t.Error("ServiceShutdown should have been called") + } + } else { + t.Error("testServiceWithShutdown should implement ServiceShutdown") + } +} diff --git a/v3/pkg/application/single_instance.go b/v3/pkg/application/single_instance.go index 24bbf5c31..7f04320eb 100644 --- a/v3/pkg/application/single_instance.go +++ b/v3/pkg/application/single_instance.go @@ -5,11 +5,12 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" - "encoding/json" "errors" "os" "path/filepath" "sync" + + "encoding/json" ) var alreadyRunningError = errors.New("application is already running") diff --git a/v3/pkg/application/single_instance_test.go b/v3/pkg/application/single_instance_test.go new file mode 100644 index 000000000..230f8e07d --- /dev/null +++ b/v3/pkg/application/single_instance_test.go @@ -0,0 +1,229 @@ +package application + +import ( + "os" + "path/filepath" + "testing" +) + +func TestEncryptDecrypt(t *testing.T) { + key := [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + } + + plaintext := []byte("Hello, World! This is a test message.") + + encrypted, err := encrypt(key, plaintext) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + + if encrypted == "" { + t.Error("encrypted should not be empty") + } + + if encrypted == string(plaintext) { + t.Error("encrypted should be different from plaintext") + } + + decrypted, err := decrypt(key, encrypted) + if err != nil { + t.Fatalf("decrypt failed: %v", err) + } + + if string(decrypted) != string(plaintext) { + t.Errorf("decrypted = %q, want %q", string(decrypted), string(plaintext)) + } +} + +func TestEncryptDecrypt_EmptyData(t *testing.T) { + key := [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + } + + plaintext := []byte{} + + encrypted, err := encrypt(key, plaintext) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + + decrypted, err := decrypt(key, encrypted) + if err != nil { + t.Fatalf("decrypt failed: %v", err) + } + + if len(decrypted) != 0 { + t.Errorf("decrypted should be empty, got %d bytes", len(decrypted)) + } +} + +func TestDecrypt_InvalidData(t *testing.T) { + key := [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + } + + tests := []struct { + name string + data string + }{ + {"invalid base64", "not-valid-base64!!!"}, + {"too short", "YWJj"}, // "abc" base64 encoded (3 bytes) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := decrypt(key, tt.data) + if err == nil { + t.Error("decrypt should return error for invalid data") + } + }) + } +} + +func TestDecrypt_WrongKey(t *testing.T) { + key1 := [32]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} + + key2 := [32]byte{0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} + + plaintext := []byte("Secret message") + + encrypted, err := encrypt(key1, plaintext) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + + _, err = decrypt(key2, encrypted) + if err == nil { + t.Error("decrypt with wrong key should return error") + } +} + +func TestGetLockPath(t *testing.T) { + uniqueID := "com.example.myapp" + path := getLockPath(uniqueID) + + if path == "" { + t.Error("getLockPath should return non-empty path") + } + + expectedFileName := uniqueID + ".lock" + actualFileName := filepath.Base(path) + if actualFileName != expectedFileName { + t.Errorf("filename = %q, want %q", actualFileName, expectedFileName) + } + + // Path should be in temp directory + // Use filepath.Clean to normalize paths (os.TempDir may have trailing slash on macOS) + tmpDir := filepath.Clean(os.TempDir()) + if filepath.Dir(path) != tmpDir { + t.Errorf("path should be in temp directory %q, got %q", tmpDir, filepath.Dir(path)) + } +} + +func TestGetCurrentWorkingDir(t *testing.T) { + dir := getCurrentWorkingDir() + + // Should return a non-empty path + if dir == "" { + t.Error("getCurrentWorkingDir should return non-empty path") + } + + // Should match os.Getwd() + expected, err := os.Getwd() + if err != nil { + t.Skipf("os.Getwd failed: %v", err) + } + + if dir != expected { + t.Errorf("getCurrentWorkingDir() = %q, want %q", dir, expected) + } +} + +func TestSecondInstanceData_Fields(t *testing.T) { + data := SecondInstanceData{ + Args: []string{"arg1", "arg2"}, + WorkingDir: "/home/user", + AdditionalData: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + } + + if len(data.Args) != 2 { + t.Error("Args not set correctly") + } + if data.WorkingDir != "/home/user" { + t.Error("WorkingDir not set correctly") + } + if len(data.AdditionalData) != 2 { + t.Error("AdditionalData not set correctly") + } +} + +func TestSingleInstanceOptions_Defaults(t *testing.T) { + opts := SingleInstanceOptions{} + + if opts.UniqueID != "" { + t.Error("UniqueID should default to empty string") + } + if opts.OnSecondInstanceLaunch != nil { + t.Error("OnSecondInstanceLaunch should default to nil") + } + if opts.AdditionalData != nil { + t.Error("AdditionalData should default to nil") + } + if opts.ExitCode != 0 { + t.Error("ExitCode should default to 0") + } + var zeroKey [32]byte + if opts.EncryptionKey != zeroKey { + t.Error("EncryptionKey should default to zero array") + } +} + +func TestSingleInstanceManager_Cleanup_Nil(t *testing.T) { + // Calling cleanup on nil manager should not panic + var m *singleInstanceManager + m.cleanup() // Should not panic +} + +func TestSingleInstanceManager_Cleanup_NilLock(t *testing.T) { + // Calling cleanup with nil lock should not panic + m := &singleInstanceManager{} + m.cleanup() // Should not panic +} + +func TestNewSingleInstanceManager_NilOptions(t *testing.T) { + manager, err := newSingleInstanceManager(nil, nil) + if err != nil { + t.Errorf("newSingleInstanceManager(nil, nil) should not return error: %v", err) + } + if manager != nil { + t.Error("newSingleInstanceManager(nil, nil) should return nil manager") + } +} + +func TestAlreadyRunningError(t *testing.T) { + if alreadyRunningError == nil { + t.Error("alreadyRunningError should not be nil") + } + if alreadyRunningError.Error() != "application is already running" { + t.Errorf("alreadyRunningError.Error() = %q", alreadyRunningError.Error()) + } +} diff --git a/v3/pkg/application/systemtray.go b/v3/pkg/application/systemtray.go index ef0525e54..0735b52e2 100644 --- a/v3/pkg/application/systemtray.go +++ b/v3/pkg/application/systemtray.go @@ -96,6 +96,13 @@ func (s *SystemTray) Label() string { } func (s *SystemTray) Run() { + + // exit early if application isn't running. + // app.Run() will call this + if globalApplication == nil || globalApplication.running == false { + return + } + s.impl = newSystemTrayImpl(s) if s.attachedWindow.Window != nil { diff --git a/v3/pkg/application/systemtray_bench_test.go b/v3/pkg/application/systemtray_bench_test.go new file mode 100644 index 000000000..3691f0f59 --- /dev/null +++ b/v3/pkg/application/systemtray_bench_test.go @@ -0,0 +1,374 @@ +//go:build bench + +package application + +import ( + "testing" + "time" +) + +// Note: SystemTray benchmarks are limited since actual system tray operations +// require platform-specific GUI initialization. These benchmarks focus on +// the Go-side logic that can be tested without a running GUI. + +// BenchmarkSystemTrayCreation measures the cost of creating SystemTray instances +func BenchmarkSystemTrayCreation(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + _ = tray + } +} + +// BenchmarkSystemTrayConfiguration measures configuration operations +func BenchmarkSystemTrayConfiguration(b *testing.B) { + b.Run("SetLabel", func(b *testing.B) { + tray := newSystemTray(1) + // impl is nil, so this just sets the field + for b.Loop() { + tray.SetLabel("Test Label") + } + }) + + b.Run("SetTooltip", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetTooltip("Test Tooltip") + } + }) + + b.Run("SetIcon", func(b *testing.B) { + tray := newSystemTray(1) + icon := make([]byte, 1024) // 1KB icon data + for b.Loop() { + tray.SetIcon(icon) + } + }) + + b.Run("SetDarkModeIcon", func(b *testing.B) { + tray := newSystemTray(1) + icon := make([]byte, 1024) + for b.Loop() { + tray.SetDarkModeIcon(icon) + } + }) + + b.Run("SetTemplateIcon", func(b *testing.B) { + tray := newSystemTray(1) + icon := make([]byte, 1024) + for b.Loop() { + tray.SetTemplateIcon(icon) + } + }) + + b.Run("SetIconPosition", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetIconPosition(NSImageLeading) + } + }) + + b.Run("ChainedConfiguration", func(b *testing.B) { + icon := make([]byte, 1024) + for b.Loop() { + tray := newSystemTray(1) + tray.SetIcon(icon). + SetDarkModeIcon(icon). + SetIconPosition(NSImageLeading) + } + }) +} + +// BenchmarkClickHandlerExecution measures handler registration and invocation +func BenchmarkClickHandlerExecution(b *testing.B) { + b.Run("RegisterClickHandler", func(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + tray.OnClick(func() {}) + } + }) + + b.Run("RegisterAllHandlers", func(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + tray.OnClick(func() {}) + tray.OnRightClick(func() {}) + tray.OnDoubleClick(func() {}) + tray.OnRightDoubleClick(func() {}) + tray.OnMouseEnter(func() {}) + tray.OnMouseLeave(func() {}) + } + }) + + b.Run("InvokeClickHandler", func(b *testing.B) { + tray := newSystemTray(1) + counter := 0 + tray.OnClick(func() { + counter++ + }) + + b.ResetTimer() + for b.Loop() { + if tray.clickHandler != nil { + tray.clickHandler() + } + } + }) + + b.Run("InvokeAllHandlers", func(b *testing.B) { + tray := newSystemTray(1) + counter := 0 + handler := func() { counter++ } + tray.OnClick(handler) + tray.OnRightClick(handler) + tray.OnDoubleClick(handler) + tray.OnRightDoubleClick(handler) + tray.OnMouseEnter(handler) + tray.OnMouseLeave(handler) + + b.ResetTimer() + for b.Loop() { + if tray.clickHandler != nil { + tray.clickHandler() + } + if tray.rightClickHandler != nil { + tray.rightClickHandler() + } + if tray.doubleClickHandler != nil { + tray.doubleClickHandler() + } + if tray.rightDoubleClickHandler != nil { + tray.rightDoubleClickHandler() + } + if tray.mouseEnterHandler != nil { + tray.mouseEnterHandler() + } + if tray.mouseLeaveHandler != nil { + tray.mouseLeaveHandler() + } + } + }) +} + +// BenchmarkWindowAttachment measures window attachment configuration +func BenchmarkWindowAttachment(b *testing.B) { + b.Run("AttachWindow", func(b *testing.B) { + // We can't create real windows, but we can test the attachment logic + for b.Loop() { + tray := newSystemTray(1) + // AttachWindow accepts nil gracefully + tray.AttachWindow(nil) + } + }) + + b.Run("WindowOffset", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.WindowOffset(10) + } + }) + + b.Run("WindowDebounce", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.WindowDebounce(200 * time.Millisecond) + } + }) + + b.Run("ChainedAttachment", func(b *testing.B) { + for b.Loop() { + tray := newSystemTray(1) + tray.AttachWindow(nil). + WindowOffset(10). + WindowDebounce(200 * time.Millisecond) + } + }) +} + +// BenchmarkMenuConfiguration measures menu setup operations +func BenchmarkMenuConfiguration(b *testing.B) { + b.Run("SetNilMenu", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetMenu(nil) + } + }) + + b.Run("SetSimpleMenu", func(b *testing.B) { + menu := NewMenu() + menu.Add("Item 1") + menu.Add("Item 2") + menu.Add("Item 3") + + tray := newSystemTray(1) + b.ResetTimer() + for b.Loop() { + tray.SetMenu(menu) + } + }) + + b.Run("SetComplexMenu", func(b *testing.B) { + menu := NewMenu() + for i := 0; i < 20; i++ { + menu.Add("Item") + } + submenu := NewMenu() + for i := 0; i < 10; i++ { + submenu.Add("Subitem") + } + + tray := newSystemTray(1) + b.ResetTimer() + for b.Loop() { + tray.SetMenu(menu) + } + }) +} + +// BenchmarkIconSizes measures icon handling with different sizes +func BenchmarkIconSizes(b *testing.B) { + sizes := []struct { + name string + size int + }{ + {"16x16", 16 * 16 * 4}, // 1KB - small icon + {"32x32", 32 * 32 * 4}, // 4KB - medium icon + {"64x64", 64 * 64 * 4}, // 16KB - large icon + {"128x128", 128 * 128 * 4}, // 64KB - retina icon + {"256x256", 256 * 256 * 4}, // 256KB - high-res icon + } + + for _, size := range sizes { + b.Run(size.name, func(b *testing.B) { + icon := make([]byte, size.size) + tray := newSystemTray(1) + + b.ResetTimer() + for b.Loop() { + tray.SetIcon(icon) + } + }) + } +} + +// BenchmarkWindowAttachConfigInit measures WindowAttachConfig initialization +func BenchmarkWindowAttachConfigInit(b *testing.B) { + b.Run("DefaultConfig", func(b *testing.B) { + for b.Loop() { + config := WindowAttachConfig{ + Window: nil, + Offset: 0, + Debounce: 200 * time.Millisecond, + } + _ = config + } + }) + + b.Run("FullConfig", func(b *testing.B) { + for b.Loop() { + config := WindowAttachConfig{ + Window: nil, + Offset: 10, + Debounce: 300 * time.Millisecond, + justClosed: false, + hasBeenShown: true, + } + _ = config + } + }) +} + +// BenchmarkSystemTrayShowHide measures show/hide state changes +// Note: These operations are no-ops when impl is nil, but we measure the check overhead +func BenchmarkSystemTrayShowHide(b *testing.B) { + b.Run("Show", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.Show() + } + }) + + b.Run("Hide", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.Hide() + } + }) + + b.Run("ToggleShowHide", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.Show() + tray.Hide() + } + }) +} + +// BenchmarkIconPositionConstants measures icon position constant access +func BenchmarkIconPositionConstants(b *testing.B) { + positions := []IconPosition{ + NSImageNone, + NSImageOnly, + NSImageLeft, + NSImageRight, + NSImageBelow, + NSImageAbove, + NSImageOverlaps, + NSImageLeading, + NSImageTrailing, + } + + b.Run("SetAllPositions", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + for _, pos := range positions { + tray.SetIconPosition(pos) + } + } + }) +} + +// BenchmarkLabelOperations measures label getter/setter performance +func BenchmarkLabelOperations(b *testing.B) { + b.Run("SetLabel", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetLabel("System Tray Label") + } + }) + + b.Run("GetLabel", func(b *testing.B) { + tray := newSystemTray(1) + tray.SetLabel("System Tray Label") + b.ResetTimer() + for b.Loop() { + _ = tray.Label() + } + }) + + b.Run("SetGetLabel", func(b *testing.B) { + tray := newSystemTray(1) + for b.Loop() { + tray.SetLabel("Label") + _ = tray.Label() + } + }) +} + +// BenchmarkDefaultClickHandler measures the default click handler logic +func BenchmarkDefaultClickHandler(b *testing.B) { + b.Run("NoAttachedWindow", func(b *testing.B) { + tray := newSystemTray(1) + // With no menu and no attached window, defaultClickHandler returns early + for b.Loop() { + tray.defaultClickHandler() + } + }) + + b.Run("WithNilWindow", func(b *testing.B) { + tray := newSystemTray(1) + tray.attachedWindow.Window = nil + for b.Loop() { + tray.defaultClickHandler() + } + }) +} diff --git a/v3/pkg/application/transport_http.go b/v3/pkg/application/transport_http.go index 971628518..0c2b0fd45 100644 --- a/v3/pkg/application/transport_http.go +++ b/v3/pkg/application/transport_http.go @@ -1,17 +1,31 @@ package application import ( + "bytes" "context" - "encoding/json" "errors" "io" "log/slog" "net/http" "strconv" + "sync" + + "encoding/json" "github.com/wailsapp/wails/v3/pkg/errs" ) +// bufferPool reduces allocations for reading request bodies. +// Buffers larger than maxPooledBufferSize are not returned to the pool +// to prevent memory bloat from occasional large requests (e.g., images). +const maxPooledBufferSize = 512 * 1024 // 512KB + +var bufferPool = sync.Pool{ + New: func() any { + return bytes.NewBuffer(make([]byte, 0, 4096)) + }, +} + type HTTPTransport struct { messageProcessor *MessageProcessor logger *slog.Logger @@ -75,14 +89,24 @@ func (t *HTTPTransport) Handler() func(next http.Handler) http.Handler { } func (t *HTTPTransport) handleRuntimeRequest(rw http.ResponseWriter, r *http.Request) { - bodyBytes, err := io.ReadAll(r.Body) + // Use pooled buffer to reduce allocations + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer func() { + // Don't return large buffers to pool to prevent memory bloat + if buf.Cap() <= maxPooledBufferSize { + bufferPool.Put(buf) + } + }() + + _, err := io.Copy(buf, r.Body) if err != nil { t.httpError(rw, errs.WrapInvalidRuntimeCallErrorf(err, "Unable to read request body")) return } var body request - err = json.Unmarshal(bodyBytes, &body) + err = json.Unmarshal(buf.Bytes(), &body) if err != nil { t.httpError(rw, errs.WrapInvalidRuntimeCallErrorf(err, "Unable to parse request body as JSON")) return diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 2193a236b..dbf7eebcc 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -1,7 +1,6 @@ package application import ( - "encoding/json" "fmt" "runtime" "slices" @@ -10,9 +9,9 @@ import ( "sync/atomic" "unsafe" - "github.com/leaanthony/u" + "encoding/json" - "github.com/samber/lo" + "github.com/leaanthony/u" "github.com/wailsapp/wails/v3/internal/assetserver" "github.com/wailsapp/wails/v3/pkg/events" ) @@ -772,7 +771,9 @@ func (w *WebviewWindow) OnWindowEvent( return func() { // Check if eventListener is already locked w.eventListenersLock.Lock() - w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener) + w.eventListeners[eventID] = slices.DeleteFunc(w.eventListeners[eventID], func(l *WindowEventListener) bool { + return l == windowEventListener + }) w.eventListenersLock.Unlock() } } @@ -793,7 +794,9 @@ func (w *WebviewWindow) RegisterHook( return func() { w.eventHooksLock.Lock() defer w.eventHooksLock.Unlock() - w.eventHooks[eventID] = lo.Without(w.eventHooks[eventID], windowEventHook) + w.eventHooks[eventID] = slices.DeleteFunc(w.eventHooks[eventID], func(l *WindowEventListener) bool { + return l == windowEventHook + }) } } @@ -1177,7 +1180,7 @@ func (w *WebviewWindow) SetFrameless(frameless bool) Window { } func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) { - msg := fmt.Sprintf("_wails.dispatchWailsEvent(%s);", event.ToJSON()) + msg := fmt.Sprintf("window._wails.dispatchWailsEvent(%s);", event.ToJSON()) w.ExecJS(msg) } @@ -1202,37 +1205,16 @@ func (w *WebviewWindow) Error(message string, args ...any) { globalApplication.error("in window '%s': "+message, args...) } -func (w *WebviewWindow) HandleDragAndDropMessage(filenames []string, dropZone *DropZoneDetails) { - globalApplication.debug( - "[DragDropDebug] HandleDragAndDropMessage called", - "files", filenames, - "dropZone", dropZone, - ) +func (w *WebviewWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) { thisEvent := NewWindowEvent() - globalApplication.debug( - "[DragDropDebug] HandleDragAndDropMessage: thisEvent created", - "ctx", thisEvent.ctx, - ) ctx := newWindowEventContext() ctx.setDroppedFiles(filenames) - if dropZone != nil { // Check if dropZone details are available - ctx.setDropZoneDetails(dropZone) + if dropTarget != nil { + ctx.setDropTargetDetails(dropTarget) } thisEvent.ctx = ctx - globalApplication.debug( - "[DragDropDebug] HandleDragAndDropMessage: thisEvent.ctx assigned", - "thisEvent.ctx", thisEvent.ctx, - "ctx", ctx, - ) - listeners := w.eventListeners[uint(events.Common.WindowDropZoneFilesDropped)] - globalApplication.debug( - "[DragDropDebug] HandleDragAndDropMessage: Found listeners for WindowDropZoneFilesDropped", - "count", len(listeners), - ) - globalApplication.debug( - "[DragDropDebug] HandleDragAndDropMessage: Before calling listeners", - "thisEvent.ctx", thisEvent.ctx, - ) + + listeners := w.eventListeners[uint(events.Common.WindowFilesDropped)] for _, listener := range listeners { if listener == nil { continue @@ -1451,11 +1433,6 @@ func (w *WebviewWindow) ToggleMenuBar() { } func (w *WebviewWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) { - globalApplication.debug( - "[DragDropDebug] InitiateFrontendDropProcessing called", - "x", x, - "y", y, - ) if w.impl == nil || w.isDestroyed() { return } @@ -1467,7 +1444,7 @@ func (w *WebviewWindow) InitiateFrontendDropProcessing(filenames []string, x int } jsCall := fmt.Sprintf( - "window._wails.handlePlatformFileDrop(%s, %d, %d);", + "window.wails.Window.HandlePlatformFileDrop(%s, %d, %d);", string(filenamesJSON), x, y, @@ -1484,6 +1461,65 @@ func (w *WebviewWindow) InitiateFrontendDropProcessing(filenames []string, x int }) } +// HandleDragEnter is called when drag enters the window (Linux only, since GTK intercepts drag events) +func (w *WebviewWindow) HandleDragEnter() { + if w.impl == nil || w.isDestroyed() || !w.runtimeLoaded { + return + } + + // Reset drag hover state for new drag session + dragHover.lastSentX = 0 + dragHover.lastSentY = 0 + + w.impl.execJS("window._wails.handleDragEnter();") +} + +// Drag hover throttle state +var dragHover struct { + lastSentX int + lastSentY int +} + +// HandleDragOver is called during drag-motion to update hover state in JS +// This is called from the GTK main thread, so we can call execJS directly +func (w *WebviewWindow) HandleDragOver(x int, y int) { + if w.impl == nil || w.isDestroyed() || !w.runtimeLoaded { + return + } + + // Throttle: only send if moved at least 5 pixels + dx := x - dragHover.lastSentX + dy := y - dragHover.lastSentY + if dx < 0 { + dx = -dx + } + if dy < 0 { + dy = -dy + } + if dx < 5 && dy < 5 { + return + } + dragHover.lastSentX = x + dragHover.lastSentY = y + + // Use platform-specific zero-alloc implementation if available + if impl, ok := w.impl.(interface{ execJSDragOver(x, y int) }); ok { + impl.execJSDragOver(x, y) + } else { + w.impl.execJS(fmt.Sprintf("window._wails.handleDragOver(%d,%d)", x, y)) + } +} + +// HandleDragLeave is called when drag leaves the window +func (w *WebviewWindow) HandleDragLeave() { + if w.impl == nil || w.isDestroyed() || !w.runtimeLoaded { + return + } + + // Don't use InvokeSync - execJS already handles main thread dispatch internally + w.impl.execJS("window._wails.handleDragLeave();") +} + // SnapAssist triggers the Windows Snap Assist feature by simulating Win+Z key combination. // On Windows, this opens the snap layout options. On Linux and macOS, this is a no-op. func (w *WebviewWindow) SnapAssist() { diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index b8d3736d3..d4534b501 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -34,8 +34,8 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa backing:NSBackingStoreBuffered defer:NO]; - // Allow fullscreen. Needed for frameless windows - window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary; + // Note: collectionBehavior is set later via windowSetCollectionBehavior() + // to allow user configuration of Space and fullscreen behavior // Create delegate WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; @@ -234,6 +234,19 @@ void setModalPanelWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLev void setScreenSaverWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSScreenSaverWindowLevel]; } void setTornOffMenuWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSTornOffMenuWindowLevel]; } +// Set NSWindow collection behavior for Spaces and fullscreen +// The behavior parameter is a bitmask that can combine multiple NSWindowCollectionBehavior values +void windowSetCollectionBehavior(void* nsWindow, int behavior) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + if (behavior == 0) { + // Default: use FullScreenPrimary for backwards compatibility + window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary; + } else { + // Pass through the combined bitmask directly + window.collectionBehavior = (NSWindowCollectionBehavior)behavior; + } +} + // Load URL in NSWindow void navigationLoadURL(void* nsWindow, char* url) { // Load URL on main thread @@ -339,6 +352,12 @@ void windowExecJS(void* nsWindow, const char* js) { free((void*)js); } +// Execute JS without allocation - buffer is NOT freed +void windowExecJSNoAlloc(void* nsWindow, const char* js) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView evaluateJavaScript:[NSString stringWithUTF8String:js] completionHandler:nil]; +} + // Make NSWindow backdrop translucent void windowSetTranslucent(void* nsWindow) { // Get window @@ -571,11 +590,19 @@ void windowGetRelativePosition(void* nsWindow, int* x, int* y) { *y = screenFrame.size.height - frame.origin.y - frame.size.height; } -// Get absolute window position +// Get absolute window position (in screen coordinates with Y=0 at top, scaled for DPI) void windowGetPosition(void* nsWindow, int* x, int* y) { - NSRect frame = [(WebviewWindow*)nsWindow frame]; - *x = frame.origin.x; - *y = frame.origin.y; + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSScreen* screen = [window screen]; + if (screen == NULL) { + screen = [NSScreen mainScreen]; + } + CGFloat scale = [screen backingScaleFactor]; + NSRect frame = [window frame]; + NSRect screenFrame = [screen frame]; + // Convert to top-origin coordinates and apply scale (matching windowSetPosition) + *x = frame.origin.x * scale; + *y = (screenFrame.size.height - frame.origin.y - frame.size.height) * scale; } void windowSetPosition(void* nsWindow, int x, int y) { @@ -834,6 +861,7 @@ static void setContentProtection(void *nsWindow, bool enabled) { */ import "C" import ( + "fmt" "sync" "sync/atomic" "unsafe" @@ -1081,6 +1109,17 @@ func (w *macosWebviewWindow) execJS(js string) { }) } +// execJSDragOver executes JS for drag-over events with zero allocations +// Must be called from main thread +func (w *macosWebviewWindow) execJSDragOver(buffer []byte) { + if w.nsWindow == nil { + return + } + // Pass buffer directly to C without allocation + // Buffer must be null-terminated + C.windowExecJSNoAlloc(w.nsWindow, (*C.char)(unsafe.Pointer(&buffer[0]))) +} + func (w *macosWebviewWindow) setURL(uri string) { C.navigationLoadURL(w.nsWindow, C.CString(uri)) } @@ -1094,7 +1133,10 @@ func newWindowImpl(parent *WebviewWindow) *macosWebviewWindow { parent: parent, } result.parent.RegisterHook(events.Mac.WebViewDidFinishNavigation, func(event *WindowEvent) { - result.execJS(runtime.Core(globalApplication.impl.GetFlags(globalApplication.options))) + // Inject runtime core + js := runtime.Core(globalApplication.impl.GetFlags(globalApplication.options)) + js += fmt.Sprintf("window._wails.flags.enableFileDrop=%v;", result.parent.options.EnableFileDrop) + result.execJS(js) }) return result } @@ -1162,6 +1204,10 @@ func (w *macosWebviewWindow) setWindowLevel(level MacWindowLevel) { } } +func (w *macosWebviewWindow) setCollectionBehavior(behavior MacWindowCollectionBehavior) { + C.windowSetCollectionBehavior(w.nsWindow, C.int(behavior)) +} + func (w *macosWebviewWindow) width() int { var width C.int var wg sync.WaitGroup @@ -1224,7 +1270,7 @@ func (w *macosWebviewWindow) run() { C.int(options.Height), C.bool(macOptions.EnableFraudulentWebsiteWarnings), C.bool(options.Frameless), - C.bool(options.EnableDragAndDrop), + C.bool(options.EnableFileDrop), w.getWebviewPreferences(), ) w.setTitle(options.Title) @@ -1260,6 +1306,9 @@ func (w *macosWebviewWindow) run() { } w.setWindowLevel(macOptions.WindowLevel) + // Set collection behavior (defaults to FullScreenPrimary for backwards compatibility) + w.setCollectionBehavior(macOptions.CollectionBehavior) + // Initialise the window buttons w.setMinimiseButtonState(options.MinimiseButtonState) w.setMaximiseButtonState(options.MaximiseButtonState) @@ -1457,6 +1506,8 @@ func (w *macosWebviewWindow) setPhysicalBounds(physicalBounds Rect) { func (w *macosWebviewWindow) destroy() { w.parent.markAsDestroyed() + // Clear caches for this window + clearWindowDragCache(w.parent.id) C.windowDestroy(w.nsWindow) } diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 34c0ef019..7b47ffbbd 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -196,7 +196,6 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { [super setDelegate: delegate]; // If the delegate is our WebviewWindowDelegate (which handles NSDraggingDestination) if ([delegate isKindOfClass:[WebviewWindowDelegate class]]) { - NSLog(@"WebviewWindow: setDelegate - Registering window for dragged types (NSFilenamesPboardType) because WebviewWindowDelegate is being set."); [self registerForDraggedTypes:@[NSFilenamesPboardType]]; // 'self' is the WebviewWindow instance } } @@ -246,53 +245,40 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { @end @implementation WebviewWindowDelegate - (NSDragOperation)draggingEntered:(id)sender { - NSLog(@"WebviewWindowDelegate: draggingEntered called. WindowID: %u", self.windowId); NSPasteboard *pasteboard = [sender draggingPasteboard]; if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { - NSLog(@"WebviewWindowDelegate: draggingEntered - Found NSFilenamesPboardType. Firing EventWindowFileDraggingEntered."); - // We need to ensure processWindowEvent is available or adapt this part - // For now, let's assume it's available globally or via an import if (hasListeners(EventWindowFileDraggingEntered)) { processWindowEvent(self.windowId, EventWindowFileDraggingEntered); } return NSDragOperationCopy; } - NSLog(@"WebviewWindowDelegate: draggingEntered - NSFilenamesPboardType NOT found."); return NSDragOperationNone; } - (void)draggingExited:(id)sender { - NSLog(@"WebviewWindowDelegate: draggingExited called. WindowID: %u", self.windowId); if (hasListeners(EventWindowFileDraggingExited)) { processWindowEvent(self.windowId, EventWindowFileDraggingExited); } } - (BOOL)prepareForDragOperation:(id)sender { - NSLog(@"WebviewWindowDelegate: prepareForDragOperation called. WindowID: %u", self.windowId); return YES; } - (BOOL)performDragOperation:(id)sender { - NSLog(@"WebviewWindowDelegate: performDragOperation called. WindowID: %u", self.windowId); NSPasteboard *pasteboard = [sender draggingPasteboard]; if (hasListeners(EventWindowFileDraggingPerformed)) { processWindowEvent(self.windowId, EventWindowFileDraggingPerformed); } if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { - NSLog(@"WebviewWindowDelegate: performDragOperation - Found NSFilenamesPboardType."); NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType]; NSUInteger count = [files count]; - NSLog(@"WebviewWindowDelegate: performDragOperation - File count: %lu", (unsigned long)count); if (count == 0) { - NSLog(@"WebviewWindowDelegate: performDragOperation - No files found in pasteboard, though type was present."); return NO; } char** cArray = (char**)malloc(count * sizeof(char*)); if (cArray == NULL) { - NSLog(@"WebviewWindowDelegate: performDragOperation - Failed to allocate memory for file array."); return NO; } for (NSUInteger i = 0; i < count; i++) { NSString* str = files[i]; - NSLog(@"WebviewWindowDelegate: performDragOperation - File %lu: %@", (unsigned long)i, str); cArray[i] = (char*)[str UTF8String]; } // Get the WebviewWindow instance, which is the dragging destination @@ -303,14 +289,10 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { CGFloat viewHeight = webView.frame.size.height; int x = (int)dropPointInView.x; int y = (int)(viewHeight - dropPointInView.y); // Flip Y for web coordinate system - NSLog(@"WebviewWindowDelegate: performDragOperation - Coords: x=%d, y=%d. ViewHeight: %f", x, y, viewHeight); - NSLog(@"WebviewWindowDelegate: performDragOperation - Calling processDragItems for windowId %u.", self.windowId); processDragItems(self.windowId, cArray, (int)count, x, y); // self.windowId is from the delegate free(cArray); - NSLog(@"WebviewWindowDelegate: performDragOperation - Returned from processDragItems."); return NO; } - NSLog(@"WebviewWindowDelegate: performDragOperation - NSFilenamesPboardType NOT found. Returning NO."); return NO; } // Original WebviewWindowDelegate methods continue here... @@ -373,14 +355,8 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { } - (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask { NSURL *url = urlSchemeTask.request.URL; - printf("🎨🎨🎨 [DARWIN] URL SCHEME HANDLER: %s\n", [url.absoluteString UTF8String]); fflush(stdout); if ([url.path hasSuffix:@".css"] || [url.path containsString:@"style"]) { - printf("🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨\n"); - printf("🎨 [DARWIN] CSS REQUEST INTERCEPTED!\n"); - printf("🎨 URL: %s\n", [url.absoluteString UTF8String]); - printf("🎨 Path: %s\n", [url.path UTF8String]); - printf("🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨🎨\n"); fflush(stdout); } processURLRequest(self.windowId, urlSchemeTask); diff --git a/v3/pkg/application/webview_window_darwin_drag.m b/v3/pkg/application/webview_window_darwin_drag.m index e5fcdc579..fb185ef98 100644 --- a/v3/pkg/application/webview_window_darwin_drag.m +++ b/v3/pkg/application/webview_window_darwin_drag.m @@ -7,6 +7,9 @@ #import "../events/events_darwin.h" extern void processDragItems(unsigned int windowId, char** arr, int length, int x, int y); +extern void macosOnDragEnter(unsigned int windowId); +extern void macosOnDragExit(unsigned int windowId); +extern void macosOnDragOver(unsigned int windowId, int x, int y); @implementation WebviewDrag @@ -14,7 +17,6 @@ extern void processDragItems(unsigned int windowId, char** arr, int length, int - (instancetype)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; if (self) { - NSLog(@"WebviewDrag: initWithFrame - Registering for dragged types. WindowID (at init if available, might be set later): %u", self.windowId); // self.windowId might not be set here yet. [self registerForDraggedTypes:@[NSFilenamesPboardType]]; } return self; @@ -22,47 +24,65 @@ extern void processDragItems(unsigned int windowId, char** arr, int length, int // draggingEntered: - (NSDragOperation)draggingEntered:(id)sender { - NSLog(@"WebviewDrag: draggingEntered called. WindowID: %u", self.windowId); NSPasteboard *pasteboard = [sender draggingPasteboard]; if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { - NSLog(@"WebviewDrag: draggingEntered - Found NSFilenamesPboardType. Firing EventWindowFileDraggingEntered."); processWindowEvent(self.windowId, EventWindowFileDraggingEntered); + // Notify JS for hover effects + macosOnDragEnter(self.windowId); + return NSDragOperationCopy; + } + return NSDragOperationNone; +} + +// draggingUpdated: +- (NSDragOperation)draggingUpdated:(id)sender { + NSPasteboard *pasteboard = [sender draggingPasteboard]; + if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { + // Get the current mouse position + NSPoint dropPointInWindow = [sender draggingLocation]; + NSPoint dropPointInView = [self convertPoint:dropPointInWindow fromView:nil]; + + // Get the window's content view height for coordinate conversion + NSView *contentView = [self.window contentView]; + CGFloat contentHeight = contentView.frame.size.height; + + int x = (int)dropPointInView.x; + int y = (int)(contentHeight - dropPointInView.y); + + // Notify JS for hover effects + macosOnDragOver(self.windowId, x, y); + return NSDragOperationCopy; } - NSLog(@"WebviewDrag: draggingEntered - NSFilenamesPboardType NOT found."); return NSDragOperationNone; } // draggingExited: - (void)draggingExited:(id)sender { - NSLog(@"WebviewDrag: draggingExited called. WindowID: %u", self.windowId); // Added log processWindowEvent(self.windowId, EventWindowFileDraggingExited); + // Notify JS to clean up hover effects + macosOnDragExit(self.windowId); } // prepareForDragOperation: - (BOOL)prepareForDragOperation:(id)sender { - NSLog(@"WebviewDrag: prepareForDragOperation called. WindowID: %u", self.windowId); // Added log return YES; } // performDragOperation: - (BOOL)performDragOperation:(id)sender { - NSLog(@"WebviewDrag: performDragOperation called. WindowID: %u", self.windowId); NSPasteboard *pasteboard = [sender draggingPasteboard]; processWindowEvent(self.windowId, EventWindowFileDraggingPerformed); if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType]; NSUInteger count = [files count]; - NSLog(@"WebviewDrag: performDragOperation - File count: %lu", (unsigned long)count); if (count == 0) { - NSLog(@"WebviewDrag: performDragOperation - No files found in pasteboard, though type was present."); return NO; } char** cArray = (char**)malloc(count * sizeof(char*)); for (NSUInteger i = 0; i < count; i++) { NSString* str = files[i]; - NSLog(@"WebviewDrag: performDragOperation - File %lu: %@", (unsigned long)i, str); cArray[i] = (char*)[str UTF8String]; } @@ -73,18 +93,14 @@ extern void processDragItems(unsigned int windowId, char** arr, int length, int NSView *contentView = [self.window contentView]; CGFloat contentHeight = contentView.frame.size.height; - NSLog(@"WebviewDrag: Self height: %.2f, Content view height: %.2f", self.frame.size.height, contentHeight); - int x = (int)dropPointInView.x; // Use the content view height for conversion int y = (int)(contentHeight - dropPointInView.y); processDragItems(self.windowId, cArray, (int)count, x, y); free(cArray); - NSLog(@"WebviewDrag: performDragOperation - Returned from processDragItems."); return YES; } - NSLog(@"WebviewDrag: performDragOperation - NSFilenamesPboardType NOT found. Returning NO."); return NO; } diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go index 859306d05..0400bc726 100644 --- a/v3/pkg/application/webview_window_linux.go +++ b/v3/pkg/application/webview_window_linux.go @@ -282,8 +282,10 @@ func (w *linuxWebviewWindow) run() { w.window, w.webview, w.vbox = windowNew(app.application, w.gtkmenu, w.parent.id, w.parent.options.Linux.WebviewGpuPolicy) app.registerWindow(w.window, w.parent.id) // record our mapping w.connectSignals() - if w.parent.options.EnableDragAndDrop { + if w.parent.options.EnableFileDrop { w.enableDND() + } else { + w.disableDND() } w.setTitle(w.parent.options.Title) w.setIcon(app.icon) @@ -352,7 +354,10 @@ func (w *linuxWebviewWindow) run() { }) w.parent.RegisterHook(events.Linux.WindowLoadFinished, func(e *WindowEvent) { - w.execJS(runtime.Core(globalApplication.impl.GetFlags(globalApplication.options))) + // Inject runtime core and EnableFileDrop flag together + js := runtime.Core(globalApplication.impl.GetFlags(globalApplication.options)) + js += fmt.Sprintf("window._wails.flags.enableFileDrop=%v;", w.parent.options.EnableFileDrop) + w.execJS(js) }) if w.parent.options.HTML != "" { w.setHTML(w.parent.options.HTML) diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 4c9c0f57a..3ae49157d 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -104,8 +104,10 @@ type WebviewWindowOptions struct { // ZoomControlEnabled will enable the zoom control. ZoomControlEnabled bool - // EnableDragAndDrop will enable drag and drop. - EnableDragAndDrop bool + // EnableFileDrop enables drag and drop of files onto the window. + // When enabled, files dragged from the OS onto elements with the + // `data-file-drop-target` attribute will trigger a FilesDropped event. + EnableFileDrop bool // OpenInspectorOnStartup will open the inspector when the window is first shown. OpenInspectorOnStartup bool @@ -182,20 +184,6 @@ const ( /******* Windows Options *******/ type BackdropType int32 -type DragEffect int32 - -const ( - // DragEffectNone is used to indicate that the drop target cannot accept the data. - DragEffectNone DragEffect = 1 - // DragEffectCopy is used to indicate that the data is copied to the drop target. - DragEffectCopy DragEffect = 2 - // DragEffectMove is used to indicate that the data is removed from the drag source. - DragEffectMove DragEffect = 3 - // DragEffectLink is used to indicate that a link to the original data is established. - DragEffectLink DragEffect = 4 - // DragEffectScroll is used to indicate that the target can be scrolled while dragging to locate a drop position that is not currently visible in the target. - -) const ( Auto BackdropType = 0 @@ -281,10 +269,6 @@ type WindowsWindow struct { // Menu is the menu to use for the window. Menu *Menu - // Drag Cursor Effects - OnEnterEffect DragEffect - OnOverEffect DragEffect - // Permissions map for WebView2. If empty, default permissions will be granted. Permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState @@ -489,6 +473,9 @@ type MacWindow struct { // WindowLevel sets the window level to control the order of windows in the screen WindowLevel MacWindowLevel + // CollectionBehavior controls how the window behaves across macOS Spaces and fullscreen + CollectionBehavior MacWindowCollectionBehavior + // LiquidGlass contains configuration for the Liquid Glass effect LiquidGlass MacLiquidGlass } @@ -506,6 +493,40 @@ const ( MacWindowLevelScreenSaver MacWindowLevel = "screenSaver" ) +// MacWindowCollectionBehavior controls window behavior across macOS Spaces and fullscreen. +// These correspond to NSWindowCollectionBehavior bitmask values and can be combined using bitwise OR. +// For example: MacWindowCollectionBehaviorCanJoinAllSpaces | MacWindowCollectionBehaviorFullScreenAuxiliary +type MacWindowCollectionBehavior int + +const ( + // MacWindowCollectionBehaviorDefault is zero value - when set, FullScreenPrimary is used for backwards compatibility + MacWindowCollectionBehaviorDefault MacWindowCollectionBehavior = 0 + // MacWindowCollectionBehaviorCanJoinAllSpaces allows window to appear on all Spaces + MacWindowCollectionBehaviorCanJoinAllSpaces MacWindowCollectionBehavior = 1 << 0 // 1 + // MacWindowCollectionBehaviorMoveToActiveSpace moves window to active Space when shown + MacWindowCollectionBehaviorMoveToActiveSpace MacWindowCollectionBehavior = 1 << 1 // 2 + // MacWindowCollectionBehaviorManaged is the default managed window behavior + MacWindowCollectionBehaviorManaged MacWindowCollectionBehavior = 1 << 2 // 4 + // MacWindowCollectionBehaviorTransient marks window as temporary/transient + MacWindowCollectionBehaviorTransient MacWindowCollectionBehavior = 1 << 3 // 8 + // MacWindowCollectionBehaviorStationary keeps window stationary during Space switches + MacWindowCollectionBehaviorStationary MacWindowCollectionBehavior = 1 << 4 // 16 + // MacWindowCollectionBehaviorParticipatesInCycle includes window in Cmd+` cycling (default for normal windows) + MacWindowCollectionBehaviorParticipatesInCycle MacWindowCollectionBehavior = 1 << 5 // 32 + // MacWindowCollectionBehaviorIgnoresCycle excludes window from Cmd+` cycling + MacWindowCollectionBehaviorIgnoresCycle MacWindowCollectionBehavior = 1 << 6 // 64 + // MacWindowCollectionBehaviorFullScreenPrimary allows the window to enter fullscreen + MacWindowCollectionBehaviorFullScreenPrimary MacWindowCollectionBehavior = 1 << 7 // 128 + // MacWindowCollectionBehaviorFullScreenAuxiliary allows window to overlay fullscreen apps + MacWindowCollectionBehaviorFullScreenAuxiliary MacWindowCollectionBehavior = 1 << 8 // 256 + // MacWindowCollectionBehaviorFullScreenNone prevents window from entering fullscreen (macOS 10.7+) + MacWindowCollectionBehaviorFullScreenNone MacWindowCollectionBehavior = 1 << 9 // 512 + // MacWindowCollectionBehaviorFullScreenAllowsTiling allows side-by-side tiling in fullscreen (macOS 10.11+) + MacWindowCollectionBehaviorFullScreenAllowsTiling MacWindowCollectionBehavior = 1 << 11 // 2048 + // MacWindowCollectionBehaviorFullScreenDisallowsTiling prevents tiling in fullscreen (macOS 10.11+) + MacWindowCollectionBehaviorFullScreenDisallowsTiling MacWindowCollectionBehavior = 1 << 12 // 4096 +) + // MacWebviewPreferences contains preferences for the Mac webview type MacWebviewPreferences struct { // TabFocusesLinks will enable tabbing to links diff --git a/v3/pkg/application/webview_window_options_test.go b/v3/pkg/application/webview_window_options_test.go new file mode 100644 index 000000000..4d43e123e --- /dev/null +++ b/v3/pkg/application/webview_window_options_test.go @@ -0,0 +1,373 @@ +package application + +import ( + "testing" +) + +func TestNewRGBA(t *testing.T) { + rgba := NewRGBA(100, 150, 200, 255) + + if rgba.Red != 100 { + t.Errorf("Red = %d, want 100", rgba.Red) + } + if rgba.Green != 150 { + t.Errorf("Green = %d, want 150", rgba.Green) + } + if rgba.Blue != 200 { + t.Errorf("Blue = %d, want 200", rgba.Blue) + } + if rgba.Alpha != 255 { + t.Errorf("Alpha = %d, want 255", rgba.Alpha) + } +} + +func TestNewRGB(t *testing.T) { + rgba := NewRGB(100, 150, 200) + + if rgba.Red != 100 { + t.Errorf("Red = %d, want 100", rgba.Red) + } + if rgba.Green != 150 { + t.Errorf("Green = %d, want 150", rgba.Green) + } + if rgba.Blue != 200 { + t.Errorf("Blue = %d, want 200", rgba.Blue) + } + if rgba.Alpha != 255 { + t.Errorf("Alpha = %d, want 255 (default)", rgba.Alpha) + } +} + +func TestNewRGBPtr(t *testing.T) { + ptr := NewRGBPtr(0x12, 0x34, 0x56) + + if ptr == nil { + t.Fatal("NewRGBPtr returned nil") + } + + // RGB is packed as 0x00BBGGRR + expected := uint32(0x12) | (uint32(0x34) << 8) | (uint32(0x56) << 16) + if *ptr != expected { + t.Errorf("*ptr = 0x%X, want 0x%X", *ptr, expected) + } +} + +func TestBackgroundType_Constants(t *testing.T) { + if BackgroundTypeSolid != 0 { + t.Error("BackgroundTypeSolid should be 0") + } + if BackgroundTypeTransparent != 1 { + t.Error("BackgroundTypeTransparent should be 1") + } + if BackgroundTypeTranslucent != 2 { + t.Error("BackgroundTypeTranslucent should be 2") + } +} + +func TestBackdropType_Constants(t *testing.T) { + if Auto != 0 { + t.Error("Auto should be 0") + } + if None != 1 { + t.Error("None should be 1") + } + if Mica != 2 { + t.Error("Mica should be 2") + } + if Acrylic != 3 { + t.Error("Acrylic should be 3") + } + if Tabbed != 4 { + t.Error("Tabbed should be 4") + } +} + +func TestTheme_Constants(t *testing.T) { + if SystemDefault != 0 { + t.Error("SystemDefault should be 0") + } + if Dark != 1 { + t.Error("Dark should be 1") + } + if Light != 2 { + t.Error("Light should be 2") + } +} + +func TestMacBackdrop_Constants(t *testing.T) { + if MacBackdropNormal != 0 { + t.Error("MacBackdropNormal should be 0") + } + if MacBackdropTransparent != 1 { + t.Error("MacBackdropTransparent should be 1") + } + if MacBackdropTranslucent != 2 { + t.Error("MacBackdropTranslucent should be 2") + } + if MacBackdropLiquidGlass != 3 { + t.Error("MacBackdropLiquidGlass should be 3") + } +} + +func TestMacToolbarStyle_Constants(t *testing.T) { + if MacToolbarStyleAutomatic != 0 { + t.Error("MacToolbarStyleAutomatic should be 0") + } + if MacToolbarStyleExpanded != 1 { + t.Error("MacToolbarStyleExpanded should be 1") + } + if MacToolbarStylePreference != 2 { + t.Error("MacToolbarStylePreference should be 2") + } + if MacToolbarStyleUnified != 3 { + t.Error("MacToolbarStyleUnified should be 3") + } + if MacToolbarStyleUnifiedCompact != 4 { + t.Error("MacToolbarStyleUnifiedCompact should be 4") + } +} + +func TestWebviewGpuPolicy_Constants(t *testing.T) { + if WebviewGpuPolicyAlways != 0 { + t.Error("WebviewGpuPolicyAlways should be 0") + } + if WebviewGpuPolicyOnDemand != 1 { + t.Error("WebviewGpuPolicyOnDemand should be 1") + } + if WebviewGpuPolicyNever != 2 { + t.Error("WebviewGpuPolicyNever should be 2") + } +} + +func TestMacTitleBarDefault(t *testing.T) { + titleBar := MacTitleBarDefault + + if titleBar.AppearsTransparent != false { + t.Error("MacTitleBarDefault.AppearsTransparent should be false") + } + if titleBar.Hide != false { + t.Error("MacTitleBarDefault.Hide should be false") + } + if titleBar.HideTitle != false { + t.Error("MacTitleBarDefault.HideTitle should be false") + } + if titleBar.FullSizeContent != false { + t.Error("MacTitleBarDefault.FullSizeContent should be false") + } + if titleBar.UseToolbar != false { + t.Error("MacTitleBarDefault.UseToolbar should be false") + } + if titleBar.HideToolbarSeparator != false { + t.Error("MacTitleBarDefault.HideToolbarSeparator should be false") + } +} + +func TestMacTitleBarHidden(t *testing.T) { + titleBar := MacTitleBarHidden + + if titleBar.AppearsTransparent != true { + t.Error("MacTitleBarHidden.AppearsTransparent should be true") + } + if titleBar.Hide != false { + t.Error("MacTitleBarHidden.Hide should be false") + } + if titleBar.HideTitle != true { + t.Error("MacTitleBarHidden.HideTitle should be true") + } + if titleBar.FullSizeContent != true { + t.Error("MacTitleBarHidden.FullSizeContent should be true") + } + if titleBar.UseToolbar != false { + t.Error("MacTitleBarHidden.UseToolbar should be false") + } + if titleBar.HideToolbarSeparator != false { + t.Error("MacTitleBarHidden.HideToolbarSeparator should be false") + } +} + +func TestMacTitleBarHiddenInset(t *testing.T) { + titleBar := MacTitleBarHiddenInset + + if titleBar.AppearsTransparent != true { + t.Error("MacTitleBarHiddenInset.AppearsTransparent should be true") + } + if titleBar.Hide != false { + t.Error("MacTitleBarHiddenInset.Hide should be false") + } + if titleBar.HideTitle != true { + t.Error("MacTitleBarHiddenInset.HideTitle should be true") + } + if titleBar.FullSizeContent != true { + t.Error("MacTitleBarHiddenInset.FullSizeContent should be true") + } + if titleBar.UseToolbar != true { + t.Error("MacTitleBarHiddenInset.UseToolbar should be true") + } + if titleBar.HideToolbarSeparator != true { + t.Error("MacTitleBarHiddenInset.HideToolbarSeparator should be true") + } +} + +func TestMacTitleBarHiddenInsetUnified(t *testing.T) { + titleBar := MacTitleBarHiddenInsetUnified + + if titleBar.AppearsTransparent != true { + t.Error("MacTitleBarHiddenInsetUnified.AppearsTransparent should be true") + } + if titleBar.ToolbarStyle != MacToolbarStyleUnified { + t.Error("MacTitleBarHiddenInsetUnified.ToolbarStyle should be MacToolbarStyleUnified") + } +} + +func TestMacAppearanceType_Constants(t *testing.T) { + tests := []struct { + name string + value MacAppearanceType + expected string + }{ + {"DefaultAppearance", DefaultAppearance, ""}, + {"NSAppearanceNameAqua", NSAppearanceNameAqua, "NSAppearanceNameAqua"}, + {"NSAppearanceNameDarkAqua", NSAppearanceNameDarkAqua, "NSAppearanceNameDarkAqua"}, + {"NSAppearanceNameVibrantLight", NSAppearanceNameVibrantLight, "NSAppearanceNameVibrantLight"}, + } + + for _, tt := range tests { + if string(tt.value) != tt.expected { + t.Errorf("%s = %q, want %q", tt.name, string(tt.value), tt.expected) + } + } +} + +func TestMacWindowLevel_Constants(t *testing.T) { + tests := []struct { + name string + value MacWindowLevel + expected string + }{ + {"MacWindowLevelNormal", MacWindowLevelNormal, "normal"}, + {"MacWindowLevelFloating", MacWindowLevelFloating, "floating"}, + {"MacWindowLevelTornOffMenu", MacWindowLevelTornOffMenu, "tornOffMenu"}, + {"MacWindowLevelModalPanel", MacWindowLevelModalPanel, "modalPanel"}, + {"MacWindowLevelMainMenu", MacWindowLevelMainMenu, "mainMenu"}, + {"MacWindowLevelStatus", MacWindowLevelStatus, "status"}, + {"MacWindowLevelPopUpMenu", MacWindowLevelPopUpMenu, "popUpMenu"}, + {"MacWindowLevelScreenSaver", MacWindowLevelScreenSaver, "screenSaver"}, + } + + for _, tt := range tests { + if string(tt.value) != tt.expected { + t.Errorf("%s = %q, want %q", tt.name, string(tt.value), tt.expected) + } + } +} + +func TestWebviewWindowOptions_Defaults(t *testing.T) { + opts := WebviewWindowOptions{} + + // Verify zero values + if opts.Name != "" { + t.Error("Name should default to empty string") + } + if opts.Title != "" { + t.Error("Title should default to empty string") + } + if opts.Width != 0 { + t.Error("Width should default to 0") + } + if opts.Height != 0 { + t.Error("Height should default to 0") + } + if opts.AlwaysOnTop != false { + t.Error("AlwaysOnTop should default to false") + } + if opts.Frameless != false { + t.Error("Frameless should default to false") + } +} + +func TestWindowsWindow_Defaults(t *testing.T) { + opts := WindowsWindow{} + + if opts.BackdropType != Auto { + t.Error("BackdropType should default to Auto") + } + if opts.DisableIcon != false { + t.Error("DisableIcon should default to false") + } + if opts.Theme != SystemDefault { + t.Error("Theme should default to SystemDefault") + } +} + +func TestMacWindow_Defaults(t *testing.T) { + opts := MacWindow{} + + if opts.Backdrop != MacBackdropNormal { + t.Error("Backdrop should default to MacBackdropNormal") + } + if opts.DisableShadow != false { + t.Error("DisableShadow should default to false") + } +} + +func TestLinuxWindow_Defaults(t *testing.T) { + opts := LinuxWindow{} + + if opts.WindowIsTranslucent != false { + t.Error("WindowIsTranslucent should default to false") + } + if opts.WebviewGpuPolicy != WebviewGpuPolicyAlways { + t.Error("WebviewGpuPolicy should default to WebviewGpuPolicyAlways") + } +} + +func TestCoreWebView2PermissionKind_Constants(t *testing.T) { + if CoreWebView2PermissionKindUnknownPermission != 0 { + t.Error("CoreWebView2PermissionKindUnknownPermission should be 0") + } + if CoreWebView2PermissionKindMicrophone != 1 { + t.Error("CoreWebView2PermissionKindMicrophone should be 1") + } + if CoreWebView2PermissionKindCamera != 2 { + t.Error("CoreWebView2PermissionKindCamera should be 2") + } +} + +func TestCoreWebView2PermissionState_Constants(t *testing.T) { + if CoreWebView2PermissionStateDefault != 0 { + t.Error("CoreWebView2PermissionStateDefault should be 0") + } + if CoreWebView2PermissionStateAllow != 1 { + t.Error("CoreWebView2PermissionStateAllow should be 1") + } + if CoreWebView2PermissionStateDeny != 2 { + t.Error("CoreWebView2PermissionStateDeny should be 2") + } +} + +func TestMacLiquidGlassStyle_Constants(t *testing.T) { + if LiquidGlassStyleAutomatic != 0 { + t.Error("LiquidGlassStyleAutomatic should be 0") + } + if LiquidGlassStyleLight != 1 { + t.Error("LiquidGlassStyleLight should be 1") + } + if LiquidGlassStyleDark != 2 { + t.Error("LiquidGlassStyleDark should be 2") + } + if LiquidGlassStyleVibrant != 3 { + t.Error("LiquidGlassStyleVibrant should be 3") + } +} + +func TestNSVisualEffectMaterial_Constants(t *testing.T) { + if NSVisualEffectMaterialAppearanceBased != 0 { + t.Error("NSVisualEffectMaterialAppearanceBased should be 0") + } + if NSVisualEffectMaterialLight != 1 { + t.Error("NSVisualEffectMaterialLight should be 1") + } + if NSVisualEffectMaterialAuto != -1 { + t.Error("NSVisualEffectMaterialAuto should be -1") + } +} diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 5a0b9a30f..b2bb3c04d 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -10,7 +10,6 @@ import ( "strings" "sync" "sync/atomic" - "syscall" "time" "unsafe" @@ -20,8 +19,7 @@ import ( "github.com/wailsapp/wails/v3/internal/assetserver/webview" "github.com/wailsapp/wails/v3/internal/capabilities" "github.com/wailsapp/wails/v3/internal/runtime" - - "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/sliceutil" "github.com/wailsapp/go-webview2/pkg/edge" "github.com/wailsapp/wails/v3/pkg/events" @@ -68,7 +66,6 @@ type windowsWebviewWindow struct { resizeBorderWidth int32 resizeBorderHeight int32 focusingChromium bool - dropTarget *w32.DropTarget onceDo sync.Once // Window move debouncer @@ -245,8 +242,14 @@ func (w *windowsWebviewWindow) setTitle(title string) { } func (w *windowsWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + var hwndInsertAfter uintptr + if alwaysOnTop { + hwndInsertAfter = w32.HWND_TOPMOST + } else { + hwndInsertAfter = w32.HWND_NOTOPMOST + } w32.SetWindowPos(w.hwnd, - lo.Ternary(alwaysOnTop, w32.HWND_TOPMOST, w32.HWND_NOTOPMOST), + hwndInsertAfter, 0, 0, 0, @@ -716,9 +719,6 @@ func (w *windowsWebviewWindow) setRelativePosition(x int, y int) { func (w *windowsWebviewWindow) destroy() { w.parent.markAsDestroyed() - if w.dropTarget != nil { - w.dropTarget.Release() - } // destroy the window w32.DestroyWindow(w.hwnd) } @@ -1186,14 +1186,22 @@ func (w *windowsWebviewWindow) openContextMenu(menu *Menu, _ *ContextMenuData) { func (w *windowsWebviewWindow) setStyle(b bool, style int) { currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE)) if currentStyle != 0 { - currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style) + if b { + currentStyle = currentStyle | style + } else { + currentStyle = currentStyle &^ style + } w32.SetWindowLongPtr(w.hwnd, w32.GWL_STYLE, uintptr(currentStyle)) } } func (w *windowsWebviewWindow) setExStyle(b bool, style int) { currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE)) if currentStyle != 0 { - currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style) + if b { + currentStyle = currentStyle | style + } else { + currentStyle = currentStyle &^ style + } w32.SetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE, uintptr(currentStyle)) } } @@ -1482,7 +1490,7 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp } } case w32.WM_SYSKEYDOWN: - globalApplication.info("w32.WM_SYSKEYDOWN", "wparam", uint(wparam)) + globalApplication.debug("w32.WM_SYSKEYDOWN", "wparam", uint(wparam)) w.parent.emit(events.Windows.WindowKeyDown) if w.processKeyBinding(uint(wparam)) { return 0 @@ -1914,13 +1922,13 @@ func (w *windowsWebviewWindow) setupChromium() { opts.DisabledFeatures = append(opts.DisabledFeatures, "msSmartScreenProtection") if len(opts.DisabledFeatures) > 0 { - opts.DisabledFeatures = lo.Uniq(opts.DisabledFeatures) + opts.DisabledFeatures = sliceutil.Unique(opts.DisabledFeatures) arg := fmt.Sprintf("--disable-features=%s", strings.Join(opts.DisabledFeatures, ",")) chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) } if len(opts.EnabledFeatures) > 0 { - opts.EnabledFeatures = lo.Uniq(opts.EnabledFeatures) + opts.EnabledFeatures = sliceutil.Unique(opts.EnabledFeatures) arg := fmt.Sprintf("--enable-features=%s", strings.Join(opts.EnabledFeatures, ",")) chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) } @@ -1964,66 +1972,20 @@ func (w *windowsWebviewWindow) setupChromium() { } } - if w.parent.options.EnableDragAndDrop { - if chromium.HasCapability(edge.AllowExternalDrop) { - err := chromium.AllowExternalDrag(false) - if err != nil { - globalApplication.handleFatalError(err) - } - } - - // Initialize OLE for drag-and-drop operations - w32.OleInitialise() - - w.dropTarget = w32.NewDropTarget() - w.dropTarget.OnDrop = func(files []string, x int, y int) { - w.parent.emit(events.Windows.WindowDragDrop) - globalApplication.debug("[DragDropDebug] Windows DropTarget OnDrop: Raw screen coordinates", "x", x, "y", y) - - // Convert screen coordinates to window-relative coordinates first - // Windows DropTarget gives us screen coordinates, but we need window-relative coordinates - windowRect := w32.GetWindowRect(w.hwnd) - windowRelativeX := x - int(windowRect.Left) - windowRelativeY := y - int(windowRect.Top) - - globalApplication.debug("[DragDropDebug] Windows DropTarget OnDrop: After screen-to-window conversion", "windowRelativeX", windowRelativeX, "windowRelativeY", windowRelativeY) - - // Convert window-relative coordinates to webview-relative coordinates - webviewX, webviewY := w.convertWindowToWebviewCoordinates(windowRelativeX, windowRelativeY) - globalApplication.debug("[DragDropDebug] Windows DropTarget OnDrop: Final webview coordinates", "webviewX", webviewX, "webviewY", webviewY) - w.parent.InitiateFrontendDropProcessing(files, webviewX, webviewY) - } - if opts.OnEnterEffect != 0 { - w.dropTarget.OnEnterEffect = convertEffect(opts.OnEnterEffect) - } - if opts.OnOverEffect != 0 { - w.dropTarget.OnOverEffect = convertEffect(opts.OnOverEffect) - } - w.dropTarget.OnEnter = func() { - w.parent.emit(events.Windows.WindowDragEnter) - } - w.dropTarget.OnLeave = func() { - w.parent.emit(events.Windows.WindowDragLeave) - } - w.dropTarget.OnOver = func() { - w.parent.emit(events.Windows.WindowDragOver) - } - // Enumerate all the child windows for this window and register them as drop targets - w32.EnumChildWindows(w.hwnd, func(hwnd w32.HWND, lparam w32.LPARAM) w32.LRESULT { - // Check if the window class is "Chrome_RenderWidgetHostHWND" - // If it is, then we register it as a drop target - //windowName := w32.GetClassName(hwnd) - //println(windowName) - //if windowName == "Chrome_RenderWidgetHostHWND" { - err := w32.RegisterDragDrop(hwnd, w.dropTarget) - if err != nil && !errors.Is(err, syscall.Errno(w32.DRAGDROP_E_ALREADYREGISTERED)) { - globalApplication.error("error registering drag and drop: %w", err) - } - //} - return 1 - }) - - } + // File drop handling on Windows: + // WebView2's AllowExternalDrop controls ALL drag-and-drop (both external file drops + // AND internal HTML5 drag-and-drop). We cannot disable it without breaking HTML5 DnD. + // + // When EnableFileDrop is true: + // - JS dragenter/dragover/drop events fire for external file drags + // - JS calls preventDefault() to stop the browser from navigating to the file + // - JS uses chrome.webview.postMessageWithAdditionalObjects to send file paths to Go + // - Go receives paths via processMessageWithAdditionalObjects + // + // When EnableFileDrop is false: + // - We cannot use AllowExternalDrag(false) as it breaks HTML5 internal drag-and-drop + // - JS runtime checks window._wails.flags.enableFileDrop and shows "no drop" cursor + // - The enableFileDrop flag is injected in navigationCompleted callback err = chromium.PutIsGeneralAutofillEnabled(opts.GeneralAutofillEnabled) if err != nil { @@ -2138,19 +2100,6 @@ func (w *windowsWebviewWindow) fullscreenChanged( } } -func convertEffect(effect DragEffect) w32.DWORD { - switch effect { - case DragEffectCopy: - return w32.DROPEFFECT_COPY - case DragEffectMove: - return w32.DROPEFFECT_MOVE - case DragEffectLink: - return w32.DROPEFFECT_LINK - default: - return w32.DROPEFFECT_NONE - } -} - func (w *windowsWebviewWindow) flash(enabled bool) { w32.FlashWindow(w.hwnd, enabled) } @@ -2163,6 +2112,10 @@ func (w *windowsWebviewWindow) navigationCompleted( // Install the runtime core w.execJS(runtime.Core(globalApplication.impl.GetFlags(globalApplication.options))) + // Set the EnableFileDrop flag for this window (Windows-specific) + // The JS runtime checks this before processing file drops + w.execJS(fmt.Sprintf("window._wails.flags.enableFileDrop = %v;", w.parent.options.EnableFileDrop)) + // EmitEvent DomReady ApplicationEvent windowEvents <- &windowEvent{EventID: uint(events.Windows.WebViewNavigationCompleted), WindowID: w.parent.id} @@ -2264,7 +2217,7 @@ func (w *windowsWebviewWindow) processMessageWithAdditionalObjects( sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs, ) { - if strings.HasPrefix(message, "FilesDropped") { + if strings.HasPrefix(message, "file:drop:") { objs, err := args.GetAdditionalObjects() if err != nil { globalApplication.handleError(err) @@ -2306,14 +2259,14 @@ func (w *windowsWebviewWindow) processMessageWithAdditionalObjects( filenames = append(filenames, filepath) } - // Extract X/Y coordinates from message - format should be "FilesDropped:x:y" + // Extract X/Y coordinates from message - format is "file:drop:x:y" var x, y int parts := strings.Split(message, ":") - if len(parts) >= 3 { - if parsedX, err := strconv.Atoi(parts[1]); err == nil { + if len(parts) >= 4 { + if parsedX, err := strconv.Atoi(parts[2]); err == nil { x = parsedX } - if parsedY, err := strconv.Atoi(parts[2]); err == nil { + if parsedY, err := strconv.Atoi(parts[3]); err == nil { y = parsedY } } diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go index 0e7407d8c..3f4949b16 100644 --- a/v3/pkg/application/window.go +++ b/v3/pkg/application/window.go @@ -21,7 +21,7 @@ type Window interface { GetBorderSizes() *LRTB GetScreen() (*Screen, error) GetZoom() float64 - HandleDragAndDropMessage(filenames []string, dropZone *DropZoneDetails) + handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) InitiateFrontendDropProcessing(filenames []string, x int, y int) HandleMessage(message string) HandleWindowEvent(id uint) diff --git a/v3/pkg/application/window_bench_test.go b/v3/pkg/application/window_bench_test.go new file mode 100644 index 000000000..31418053d --- /dev/null +++ b/v3/pkg/application/window_bench_test.go @@ -0,0 +1,442 @@ +//go:build bench + +package application + +import ( + "fmt" + "sync" + "testing" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Note: This file uses internal package access to benchmark window internals +// without requiring GUI initialization. + +// BenchmarkWindowEventRegistration measures the cost of registering window event listeners +func BenchmarkWindowEventRegistration(b *testing.B) { + b.Run("SingleListener", func(b *testing.B) { + for b.Loop() { + w := &WebviewWindow{ + eventListeners: make(map[uint][]*WindowEventListener), + } + w.OnWindowEvent(events.Common.WindowFocus, func(event *WindowEvent) {}) + } + }) + + b.Run("MultipleListenersSameEvent", func(b *testing.B) { + for b.Loop() { + w := &WebviewWindow{ + eventListeners: make(map[uint][]*WindowEventListener), + } + for i := 0; i < 10; i++ { + w.OnWindowEvent(events.Common.WindowFocus, func(event *WindowEvent) {}) + } + } + }) + + b.Run("MultipleListenersDifferentEvents", func(b *testing.B) { + eventTypes := []events.WindowEventType{ + events.Common.WindowFocus, + events.Common.WindowLostFocus, + events.Common.WindowShow, + events.Common.WindowHide, + events.Common.WindowDidMove, + } + for b.Loop() { + w := &WebviewWindow{ + eventListeners: make(map[uint][]*WindowEventListener), + } + for _, evt := range eventTypes { + w.OnWindowEvent(evt, func(event *WindowEvent) {}) + } + } + }) +} + +// BenchmarkWindowHookRegistration measures the cost of registering window event hooks +func BenchmarkWindowHookRegistration(b *testing.B) { + b.Run("SingleHook", func(b *testing.B) { + eventID := uint(events.Common.WindowClosing) + for b.Loop() { + w := &WebviewWindow{ + eventHooks: make(map[uint][]*WindowEventListener), + } + w.eventHooksLock.Lock() + w.eventHooks[eventID] = append(w.eventHooks[eventID], &WindowEventListener{ + callback: func(event *WindowEvent) {}, + }) + w.eventHooksLock.Unlock() + } + }) + + b.Run("MultipleHooks", func(b *testing.B) { + eventID := uint(events.Common.WindowClosing) + for b.Loop() { + w := &WebviewWindow{ + eventHooks: make(map[uint][]*WindowEventListener), + } + for i := 0; i < 5; i++ { + w.eventHooksLock.Lock() + w.eventHooks[eventID] = append(w.eventHooks[eventID], &WindowEventListener{ + callback: func(event *WindowEvent) {}, + }) + w.eventHooksLock.Unlock() + } + } + }) +} + +// BenchmarkWindowEventDispatch measures the internal event dispatch mechanism +func BenchmarkWindowEventDispatch(b *testing.B) { + listenerCounts := []int{0, 1, 5, 10, 50} + + for _, count := range listenerCounts { + b.Run(fmt.Sprintf("Listeners%d", count), func(b *testing.B) { + w := &WebviewWindow{ + eventListeners: make(map[uint][]*WindowEventListener), + } + + eventID := uint(events.Common.WindowFocus) + + // Register listeners + for i := 0; i < count; i++ { + w.eventListeners[eventID] = append(w.eventListeners[eventID], &WindowEventListener{ + callback: func(event *WindowEvent) { + _ = event.IsCancelled() + }, + }) + } + + b.ResetTimer() + for b.Loop() { + w.eventListenersLock.RLock() + listeners := w.eventListeners[eventID] + w.eventListenersLock.RUnlock() + _ = listeners + } + }) + } +} + +// BenchmarkKeyBindingLookup measures key binding lookup performance +func BenchmarkKeyBindingLookup(b *testing.B) { + bindingCounts := []int{1, 10, 50, 100} + + for _, count := range bindingCounts { + b.Run(fmt.Sprintf("Bindings%d", count), func(b *testing.B) { + w := &WebviewWindow{ + keyBindings: make(map[string]func(Window)), + } + + // Register bindings + for i := 0; i < count; i++ { + key := fmt.Sprintf("ctrl+shift+%c", 'a'+i%26) + w.keyBindings[key] = func(Window) {} + } + + // Lookup key that exists + lookupKey := "ctrl+shift+m" + w.keyBindings[lookupKey] = func(Window) {} + + b.ResetTimer() + for b.Loop() { + w.keyBindingsLock.RLock() + _ = w.keyBindings[lookupKey] + w.keyBindingsLock.RUnlock() + } + }) + } + + b.Run("MissLookup", func(b *testing.B) { + w := &WebviewWindow{ + keyBindings: make(map[string]func(Window)), + } + + // Register some bindings + for i := 0; i < 50; i++ { + key := fmt.Sprintf("ctrl+shift+%c", 'a'+i%26) + w.keyBindings[key] = func(Window) {} + } + + lookupKey := "ctrl+alt+nonexistent" + + b.ResetTimer() + for b.Loop() { + w.keyBindingsLock.RLock() + _ = w.keyBindings[lookupKey] + w.keyBindingsLock.RUnlock() + } + }) +} + +// BenchmarkConcurrentWindowOps measures concurrent access patterns +func BenchmarkConcurrentWindowOps(b *testing.B) { + b.Run("ConcurrentEventLookup", func(b *testing.B) { + w := &WebviewWindow{ + eventListeners: make(map[uint][]*WindowEventListener), + } + + eventID := uint(events.Common.WindowFocus) + for i := 0; i < 10; i++ { + w.eventListeners[eventID] = append(w.eventListeners[eventID], &WindowEventListener{ + callback: func(event *WindowEvent) {}, + }) + } + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + w.eventListenersLock.RLock() + _ = w.eventListeners[eventID] + w.eventListenersLock.RUnlock() + } + }) + }) + + b.Run("ConcurrentKeyBindingLookup", func(b *testing.B) { + w := &WebviewWindow{ + keyBindings: make(map[string]func(Window)), + } + + for i := 0; i < 50; i++ { + key := fmt.Sprintf("ctrl+shift+%c", 'a'+i%26) + w.keyBindings[key] = func(Window) {} + } + + keys := []string{"ctrl+shift+a", "ctrl+shift+m", "ctrl+shift+z"} + + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + w.keyBindingsLock.RLock() + _ = w.keyBindings[keys[i%len(keys)]] + w.keyBindingsLock.RUnlock() + i++ + } + }) + }) + + b.Run("MixedReadWrite", func(b *testing.B) { + w := &WebviewWindow{ + eventListeners: make(map[uint][]*WindowEventListener), + } + + eventID := uint(events.Common.WindowFocus) + + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + if i%10 == 0 { + // Write operation (10% of ops) + w.eventListenersLock.Lock() + w.eventListeners[eventID] = append(w.eventListeners[eventID], &WindowEventListener{ + callback: func(event *WindowEvent) {}, + }) + w.eventListenersLock.Unlock() + } else { + // Read operation (90% of ops) + w.eventListenersLock.RLock() + _ = w.eventListeners[eventID] + w.eventListenersLock.RUnlock() + } + i++ + } + }) + }) +} + +// BenchmarkWindowEventCreation measures WindowEvent allocation +func BenchmarkWindowEventCreation(b *testing.B) { + for b.Loop() { + event := NewWindowEvent() + _ = event + } +} + +// BenchmarkWindowEventCancellation measures cancel/check operations +func BenchmarkWindowEventCancellation(b *testing.B) { + b.Run("Cancel", func(b *testing.B) { + for b.Loop() { + event := NewWindowEvent() + event.Cancel() + } + }) + + b.Run("IsCancelled", func(b *testing.B) { + event := NewWindowEvent() + for b.Loop() { + _ = event.IsCancelled() + } + }) + + b.Run("CancelledCheck", func(b *testing.B) { + event := NewWindowEvent() + event.Cancel() + for b.Loop() { + _ = event.IsCancelled() + } + }) +} + +// BenchmarkWindowOptionsInit measures window options initialization patterns +func BenchmarkWindowOptionsInit(b *testing.B) { + b.Run("DefaultOptions", func(b *testing.B) { + for b.Loop() { + opts := WebviewWindowOptions{} + _ = opts + } + }) + + b.Run("CommonOptions", func(b *testing.B) { + for b.Loop() { + opts := WebviewWindowOptions{ + Title: "Test Window", + Width: 800, + Height: 600, + MinWidth: 400, + MinHeight: 300, + } + _ = opts + } + }) + + b.Run("FullOptions", func(b *testing.B) { + for b.Loop() { + opts := WebviewWindowOptions{ + Title: "Full Test Window", + Width: 1024, + Height: 768, + MinWidth: 400, + MinHeight: 300, + MaxWidth: 1920, + MaxHeight: 1080, + URL: "http://localhost:8080", + Frameless: false, + DisableResize: false, + AlwaysOnTop: false, + Hidden: false, + EnableDragAndDrop: true, + BackgroundColour: RGBA{Red: 255, Green: 255, Blue: 255, Alpha: 255}, + } + _ = opts + } + }) +} + +// BenchmarkMenuBindingLookup measures menu binding lookups +func BenchmarkMenuBindingLookup(b *testing.B) { + w := &WebviewWindow{ + menuBindings: make(map[string]*MenuItem), + } + + // Register some menu bindings + for i := 0; i < 50; i++ { + id := fmt.Sprintf("menu-item-%d", i) + w.menuBindings[id] = &MenuItem{id: uint(i)} + } + + lookupID := "menu-item-25" + + b.Run("Hit", func(b *testing.B) { + for b.Loop() { + w.menuBindingsLock.RLock() + _ = w.menuBindings[lookupID] + w.menuBindingsLock.RUnlock() + } + }) + + b.Run("Miss", func(b *testing.B) { + missID := "nonexistent-menu-item" + for b.Loop() { + w.menuBindingsLock.RLock() + _ = w.menuBindings[missID] + w.menuBindingsLock.RUnlock() + } + }) +} + +// BenchmarkWindowDestroyedCheck measures the destroyed flag check pattern +func BenchmarkWindowDestroyedCheck(b *testing.B) { + w := &WebviewWindow{} + + b.Run("NotDestroyed", func(b *testing.B) { + for b.Loop() { + w.destroyedLock.RLock() + _ = w.destroyed + w.destroyedLock.RUnlock() + } + }) + + b.Run("Destroyed", func(b *testing.B) { + w.destroyed = true + for b.Loop() { + w.destroyedLock.RLock() + _ = w.destroyed + w.destroyedLock.RUnlock() + } + }) +} + +// BenchmarkCancellerManagement measures canceller function management +func BenchmarkCancellerManagement(b *testing.B) { + b.Run("AddCanceller", func(b *testing.B) { + for b.Loop() { + w := &WebviewWindow{ + cancellers: make([]func(), 0), + } + for i := 0; i < 10; i++ { + w.cancellersLock.Lock() + w.cancellers = append(w.cancellers, func() {}) + w.cancellersLock.Unlock() + } + } + }) + + b.Run("ExecuteCancellers", func(b *testing.B) { + w := &WebviewWindow{ + cancellers: make([]func(), 100), + } + for i := 0; i < 100; i++ { + w.cancellers[i] = func() {} + } + + b.ResetTimer() + for b.Loop() { + w.cancellersLock.RLock() + cancellers := w.cancellers + w.cancellersLock.RUnlock() + for _, cancel := range cancellers { + cancel() + } + } + }) +} + +// BenchmarkRWMutexPatterns compares different locking patterns +func BenchmarkRWMutexPatterns(b *testing.B) { + b.Run("RLockUnlock", func(b *testing.B) { + var mu sync.RWMutex + for b.Loop() { + mu.RLock() + mu.RUnlock() + } + }) + + b.Run("LockUnlock", func(b *testing.B) { + var mu sync.RWMutex + for b.Loop() { + mu.Lock() + mu.Unlock() + } + }) + + b.Run("DeferredRLock", func(b *testing.B) { + var mu sync.RWMutex + for b.Loop() { + func() { + mu.RLock() + defer mu.RUnlock() + }() + } + }) +} diff --git a/v3/pkg/events/events.go b/v3/pkg/events/events.go index 3641b69c7..20bfcbeaa 100644 --- a/v3/pkg/events/events.go +++ b/v3/pkg/events/events.go @@ -32,7 +32,6 @@ type commonEvents struct { WindowZoomIn WindowEventType WindowZoomOut WindowEventType WindowZoomReset WindowEventType - WindowDropZoneFilesDropped WindowEventType } func newCommonEvents() commonEvents { @@ -63,7 +62,6 @@ func newCommonEvents() commonEvents { WindowZoomIn: 1047, WindowZoomOut: 1048, WindowZoomReset: 1049, - WindowDropZoneFilesDropped: 1050, } } @@ -85,17 +83,17 @@ type linuxEvents struct { func newLinuxEvents() linuxEvents { return linuxEvents{ - ApplicationStartup: 1051, - SystemThemeChanged: 1052, - WindowDeleteEvent: 1053, - WindowDidMove: 1054, - WindowDidResize: 1055, - WindowFocusIn: 1056, - WindowFocusOut: 1057, - WindowLoadStarted: 1058, - WindowLoadRedirected: 1059, - WindowLoadCommitted: 1060, - WindowLoadFinished: 1061, + ApplicationStartup: 1050, + SystemThemeChanged: 1051, + WindowDeleteEvent: 1052, + WindowDidMove: 1053, + WindowDidResize: 1054, + WindowFocusIn: 1055, + WindowFocusOut: 1056, + WindowLoadStarted: 1057, + WindowLoadRedirected: 1058, + WindowLoadCommitted: 1059, + WindowLoadFinished: 1060, } } @@ -238,138 +236,138 @@ type macEvents struct { func newMacEvents() macEvents { return macEvents{ - ApplicationDidBecomeActive: 1062, - ApplicationDidChangeBackingProperties: 1063, - ApplicationDidChangeEffectiveAppearance: 1064, - ApplicationDidChangeIcon: 1065, - ApplicationDidChangeOcclusionState: 1066, - ApplicationDidChangeScreenParameters: 1067, - ApplicationDidChangeStatusBarFrame: 1068, - ApplicationDidChangeStatusBarOrientation: 1069, - ApplicationDidChangeTheme: 1070, - ApplicationDidFinishLaunching: 1071, - ApplicationDidHide: 1072, - ApplicationDidResignActive: 1073, - ApplicationDidUnhide: 1074, - ApplicationDidUpdate: 1075, - ApplicationShouldHandleReopen: 1076, - ApplicationWillBecomeActive: 1077, - ApplicationWillFinishLaunching: 1078, - ApplicationWillHide: 1079, - ApplicationWillResignActive: 1080, - ApplicationWillTerminate: 1081, - ApplicationWillUnhide: 1082, - ApplicationWillUpdate: 1083, - MenuDidAddItem: 1084, - MenuDidBeginTracking: 1085, - MenuDidClose: 1086, - MenuDidDisplayItem: 1087, - MenuDidEndTracking: 1088, - MenuDidHighlightItem: 1089, - MenuDidOpen: 1090, - MenuDidPopUp: 1091, - MenuDidRemoveItem: 1092, - MenuDidSendAction: 1093, - MenuDidSendActionToItem: 1094, - MenuDidUpdate: 1095, - MenuWillAddItem: 1096, - MenuWillBeginTracking: 1097, - MenuWillDisplayItem: 1098, - MenuWillEndTracking: 1099, - MenuWillHighlightItem: 1100, - MenuWillOpen: 1101, - MenuWillPopUp: 1102, - MenuWillRemoveItem: 1103, - MenuWillSendAction: 1104, - MenuWillSendActionToItem: 1105, - MenuWillUpdate: 1106, - WebViewDidCommitNavigation: 1107, - WebViewDidFinishNavigation: 1108, - WebViewDidReceiveServerRedirectForProvisionalNavigation: 1109, - WebViewDidStartProvisionalNavigation: 1110, - WindowDidBecomeKey: 1111, - WindowDidBecomeMain: 1112, - WindowDidBeginSheet: 1113, - WindowDidChangeAlpha: 1114, - WindowDidChangeBackingLocation: 1115, - WindowDidChangeBackingProperties: 1116, - WindowDidChangeCollectionBehavior: 1117, - WindowDidChangeEffectiveAppearance: 1118, - WindowDidChangeOcclusionState: 1119, - WindowDidChangeOrderingMode: 1120, - WindowDidChangeScreen: 1121, - WindowDidChangeScreenParameters: 1122, - WindowDidChangeScreenProfile: 1123, - WindowDidChangeScreenSpace: 1124, - WindowDidChangeScreenSpaceProperties: 1125, - WindowDidChangeSharingType: 1126, - WindowDidChangeSpace: 1127, - WindowDidChangeSpaceOrderingMode: 1128, - WindowDidChangeTitle: 1129, - WindowDidChangeToolbar: 1130, - WindowDidDeminiaturize: 1131, - WindowDidEndSheet: 1132, - WindowDidEnterFullScreen: 1133, - WindowDidEnterVersionBrowser: 1134, - WindowDidExitFullScreen: 1135, - WindowDidExitVersionBrowser: 1136, - WindowDidExpose: 1137, - WindowDidFocus: 1138, - WindowDidMiniaturize: 1139, - WindowDidMove: 1140, - WindowDidOrderOffScreen: 1141, - WindowDidOrderOnScreen: 1142, - WindowDidResignKey: 1143, - WindowDidResignMain: 1144, - WindowDidResize: 1145, - WindowDidUpdate: 1146, - WindowDidUpdateAlpha: 1147, - WindowDidUpdateCollectionBehavior: 1148, - WindowDidUpdateCollectionProperties: 1149, - WindowDidUpdateShadow: 1150, - WindowDidUpdateTitle: 1151, - WindowDidUpdateToolbar: 1152, - WindowDidZoom: 1153, - WindowFileDraggingEntered: 1154, - WindowFileDraggingExited: 1155, - WindowFileDraggingPerformed: 1156, - WindowHide: 1157, - WindowMaximise: 1158, - WindowUnMaximise: 1159, - WindowMinimise: 1160, - WindowUnMinimise: 1161, - WindowShouldClose: 1162, - WindowShow: 1163, - WindowWillBecomeKey: 1164, - WindowWillBecomeMain: 1165, - WindowWillBeginSheet: 1166, - WindowWillChangeOrderingMode: 1167, - WindowWillClose: 1168, - WindowWillDeminiaturize: 1169, - WindowWillEnterFullScreen: 1170, - WindowWillEnterVersionBrowser: 1171, - WindowWillExitFullScreen: 1172, - WindowWillExitVersionBrowser: 1173, - WindowWillFocus: 1174, - WindowWillMiniaturize: 1175, - WindowWillMove: 1176, - WindowWillOrderOffScreen: 1177, - WindowWillOrderOnScreen: 1178, - WindowWillResignMain: 1179, - WindowWillResize: 1180, - WindowWillUnfocus: 1181, - WindowWillUpdate: 1182, - WindowWillUpdateAlpha: 1183, - WindowWillUpdateCollectionBehavior: 1184, - WindowWillUpdateCollectionProperties: 1185, - WindowWillUpdateShadow: 1186, - WindowWillUpdateTitle: 1187, - WindowWillUpdateToolbar: 1188, - WindowWillUpdateVisibility: 1189, - WindowWillUseStandardFrame: 1190, - WindowZoomIn: 1191, - WindowZoomOut: 1192, - WindowZoomReset: 1193, + ApplicationDidBecomeActive: 1061, + ApplicationDidChangeBackingProperties: 1062, + ApplicationDidChangeEffectiveAppearance: 1063, + ApplicationDidChangeIcon: 1064, + ApplicationDidChangeOcclusionState: 1065, + ApplicationDidChangeScreenParameters: 1066, + ApplicationDidChangeStatusBarFrame: 1067, + ApplicationDidChangeStatusBarOrientation: 1068, + ApplicationDidChangeTheme: 1069, + ApplicationDidFinishLaunching: 1070, + ApplicationDidHide: 1071, + ApplicationDidResignActive: 1072, + ApplicationDidUnhide: 1073, + ApplicationDidUpdate: 1074, + ApplicationShouldHandleReopen: 1075, + ApplicationWillBecomeActive: 1076, + ApplicationWillFinishLaunching: 1077, + ApplicationWillHide: 1078, + ApplicationWillResignActive: 1079, + ApplicationWillTerminate: 1080, + ApplicationWillUnhide: 1081, + ApplicationWillUpdate: 1082, + MenuDidAddItem: 1083, + MenuDidBeginTracking: 1084, + MenuDidClose: 1085, + MenuDidDisplayItem: 1086, + MenuDidEndTracking: 1087, + MenuDidHighlightItem: 1088, + MenuDidOpen: 1089, + MenuDidPopUp: 1090, + MenuDidRemoveItem: 1091, + MenuDidSendAction: 1092, + MenuDidSendActionToItem: 1093, + MenuDidUpdate: 1094, + MenuWillAddItem: 1095, + MenuWillBeginTracking: 1096, + MenuWillDisplayItem: 1097, + MenuWillEndTracking: 1098, + MenuWillHighlightItem: 1099, + MenuWillOpen: 1100, + MenuWillPopUp: 1101, + MenuWillRemoveItem: 1102, + MenuWillSendAction: 1103, + MenuWillSendActionToItem: 1104, + MenuWillUpdate: 1105, + WebViewDidCommitNavigation: 1106, + WebViewDidFinishNavigation: 1107, + WebViewDidReceiveServerRedirectForProvisionalNavigation: 1108, + WebViewDidStartProvisionalNavigation: 1109, + WindowDidBecomeKey: 1110, + WindowDidBecomeMain: 1111, + WindowDidBeginSheet: 1112, + WindowDidChangeAlpha: 1113, + WindowDidChangeBackingLocation: 1114, + WindowDidChangeBackingProperties: 1115, + WindowDidChangeCollectionBehavior: 1116, + WindowDidChangeEffectiveAppearance: 1117, + WindowDidChangeOcclusionState: 1118, + WindowDidChangeOrderingMode: 1119, + WindowDidChangeScreen: 1120, + WindowDidChangeScreenParameters: 1121, + WindowDidChangeScreenProfile: 1122, + WindowDidChangeScreenSpace: 1123, + WindowDidChangeScreenSpaceProperties: 1124, + WindowDidChangeSharingType: 1125, + WindowDidChangeSpace: 1126, + WindowDidChangeSpaceOrderingMode: 1127, + WindowDidChangeTitle: 1128, + WindowDidChangeToolbar: 1129, + WindowDidDeminiaturize: 1130, + WindowDidEndSheet: 1131, + WindowDidEnterFullScreen: 1132, + WindowDidEnterVersionBrowser: 1133, + WindowDidExitFullScreen: 1134, + WindowDidExitVersionBrowser: 1135, + WindowDidExpose: 1136, + WindowDidFocus: 1137, + WindowDidMiniaturize: 1138, + WindowDidMove: 1139, + WindowDidOrderOffScreen: 1140, + WindowDidOrderOnScreen: 1141, + WindowDidResignKey: 1142, + WindowDidResignMain: 1143, + WindowDidResize: 1144, + WindowDidUpdate: 1145, + WindowDidUpdateAlpha: 1146, + WindowDidUpdateCollectionBehavior: 1147, + WindowDidUpdateCollectionProperties: 1148, + WindowDidUpdateShadow: 1149, + WindowDidUpdateTitle: 1150, + WindowDidUpdateToolbar: 1151, + WindowDidZoom: 1152, + WindowFileDraggingEntered: 1153, + WindowFileDraggingExited: 1154, + WindowFileDraggingPerformed: 1155, + WindowHide: 1156, + WindowMaximise: 1157, + WindowUnMaximise: 1158, + WindowMinimise: 1159, + WindowUnMinimise: 1160, + WindowShouldClose: 1161, + WindowShow: 1162, + WindowWillBecomeKey: 1163, + WindowWillBecomeMain: 1164, + WindowWillBeginSheet: 1165, + WindowWillChangeOrderingMode: 1166, + WindowWillClose: 1167, + WindowWillDeminiaturize: 1168, + WindowWillEnterFullScreen: 1169, + WindowWillEnterVersionBrowser: 1170, + WindowWillExitFullScreen: 1171, + WindowWillExitVersionBrowser: 1172, + WindowWillFocus: 1173, + WindowWillMiniaturize: 1174, + WindowWillMove: 1175, + WindowWillOrderOffScreen: 1176, + WindowWillOrderOnScreen: 1177, + WindowWillResignMain: 1178, + WindowWillResize: 1179, + WindowWillUnfocus: 1180, + WindowWillUpdate: 1181, + WindowWillUpdateAlpha: 1182, + WindowWillUpdateCollectionBehavior: 1183, + WindowWillUpdateCollectionProperties: 1184, + WindowWillUpdateShadow: 1185, + WindowWillUpdateTitle: 1186, + WindowWillUpdateToolbar: 1187, + WindowWillUpdateVisibility: 1188, + WindowWillUseStandardFrame: 1189, + WindowZoomIn: 1190, + WindowZoomOut: 1191, + WindowZoomReset: 1192, } } @@ -424,50 +422,50 @@ type windowsEvents struct { func newWindowsEvents() windowsEvents { return windowsEvents{ - APMPowerSettingChange: 1194, - APMPowerStatusChange: 1195, - APMResumeAutomatic: 1196, - APMResumeSuspend: 1197, - APMSuspend: 1198, - ApplicationStarted: 1199, - SystemThemeChanged: 1200, - WebViewNavigationCompleted: 1201, - WindowActive: 1202, - WindowBackgroundErase: 1203, - WindowClickActive: 1204, - WindowClosing: 1205, - WindowDidMove: 1206, - WindowDidResize: 1207, - WindowDPIChanged: 1208, - WindowDragDrop: 1209, - WindowDragEnter: 1210, - WindowDragLeave: 1211, - WindowDragOver: 1212, - WindowEndMove: 1213, - WindowEndResize: 1214, - WindowFullscreen: 1215, - WindowHide: 1216, - WindowInactive: 1217, - WindowKeyDown: 1218, - WindowKeyUp: 1219, - WindowKillFocus: 1220, - WindowNonClientHit: 1221, - WindowNonClientMouseDown: 1222, - WindowNonClientMouseLeave: 1223, - WindowNonClientMouseMove: 1224, - WindowNonClientMouseUp: 1225, - WindowPaint: 1226, - WindowRestore: 1227, - WindowSetFocus: 1228, - WindowShow: 1229, - WindowStartMove: 1230, - WindowStartResize: 1231, - WindowUnFullscreen: 1232, - WindowZOrderChanged: 1233, - WindowMinimise: 1234, - WindowUnMinimise: 1235, - WindowMaximise: 1236, - WindowUnMaximise: 1237, + APMPowerSettingChange: 1193, + APMPowerStatusChange: 1194, + APMResumeAutomatic: 1195, + APMResumeSuspend: 1196, + APMSuspend: 1197, + ApplicationStarted: 1198, + SystemThemeChanged: 1199, + WebViewNavigationCompleted: 1200, + WindowActive: 1201, + WindowBackgroundErase: 1202, + WindowClickActive: 1203, + WindowClosing: 1204, + WindowDidMove: 1205, + WindowDidResize: 1206, + WindowDPIChanged: 1207, + WindowDragDrop: 1208, + WindowDragEnter: 1209, + WindowDragLeave: 1210, + WindowDragOver: 1211, + WindowEndMove: 1212, + WindowEndResize: 1213, + WindowFullscreen: 1214, + WindowHide: 1215, + WindowInactive: 1216, + WindowKeyDown: 1217, + WindowKeyUp: 1218, + WindowKillFocus: 1219, + WindowNonClientHit: 1220, + WindowNonClientMouseDown: 1221, + WindowNonClientMouseLeave: 1222, + WindowNonClientMouseMove: 1223, + WindowNonClientMouseUp: 1224, + WindowPaint: 1225, + WindowRestore: 1226, + WindowSetFocus: 1227, + WindowShow: 1228, + WindowStartMove: 1229, + WindowStartResize: 1230, + WindowUnFullscreen: 1231, + WindowZOrderChanged: 1232, + WindowMinimise: 1233, + WindowUnMinimise: 1234, + WindowMaximise: 1235, + WindowUnMaximise: 1236, } } @@ -500,28 +498,28 @@ type iosEvents struct { func newIOSEvents() iosEvents { return iosEvents{ - ApplicationDidBecomeActive: 1238, - ApplicationDidEnterBackground: 1239, - ApplicationDidFinishLaunching: 1240, - ApplicationDidReceiveMemoryWarning: 1241, - ApplicationWillEnterForeground: 1242, - ApplicationWillResignActive: 1243, - ApplicationWillTerminate: 1244, - WindowDidLoad: 1245, - WindowWillAppear: 1246, - WindowDidAppear: 1247, - WindowWillDisappear: 1248, - WindowDidDisappear: 1249, - WindowSafeAreaInsetsChanged: 1250, - WindowOrientationChanged: 1251, - WindowTouchBegan: 1252, - WindowTouchMoved: 1253, - WindowTouchEnded: 1254, - WindowTouchCancelled: 1255, - WebViewDidStartNavigation: 1256, - WebViewDidFinishNavigation: 1257, - WebViewDidFailNavigation: 1258, - WebViewDecidePolicyForNavigationAction: 1259, + ApplicationDidBecomeActive: 1237, + ApplicationDidEnterBackground: 1238, + ApplicationDidFinishLaunching: 1239, + ApplicationDidReceiveMemoryWarning: 1240, + ApplicationWillEnterForeground: 1241, + ApplicationWillResignActive: 1242, + ApplicationWillTerminate: 1243, + WindowDidLoad: 1244, + WindowWillAppear: 1245, + WindowDidAppear: 1246, + WindowWillDisappear: 1247, + WindowDidDisappear: 1248, + WindowSafeAreaInsetsChanged: 1249, + WindowOrientationChanged: 1250, + WindowTouchBegan: 1251, + WindowTouchMoved: 1252, + WindowTouchEnded: 1253, + WindowTouchCancelled: 1254, + WebViewDidStartNavigation: 1255, + WebViewDidFinishNavigation: 1256, + WebViewDidFailNavigation: 1257, + WebViewDecidePolicyForNavigationAction: 1258, } } @@ -556,214 +554,213 @@ var eventToJS = map[uint]string{ 1047: "common:WindowZoomIn", 1048: "common:WindowZoomOut", 1049: "common:WindowZoomReset", - 1050: "common:WindowDropZoneFilesDropped", - 1051: "linux:ApplicationStartup", - 1052: "linux:SystemThemeChanged", - 1053: "linux:WindowDeleteEvent", - 1054: "linux:WindowDidMove", - 1055: "linux:WindowDidResize", - 1056: "linux:WindowFocusIn", - 1057: "linux:WindowFocusOut", - 1058: "linux:WindowLoadStarted", - 1059: "linux:WindowLoadRedirected", - 1060: "linux:WindowLoadCommitted", - 1061: "linux:WindowLoadFinished", - 1062: "mac:ApplicationDidBecomeActive", - 1063: "mac:ApplicationDidChangeBackingProperties", - 1064: "mac:ApplicationDidChangeEffectiveAppearance", - 1065: "mac:ApplicationDidChangeIcon", - 1066: "mac:ApplicationDidChangeOcclusionState", - 1067: "mac:ApplicationDidChangeScreenParameters", - 1068: "mac:ApplicationDidChangeStatusBarFrame", - 1069: "mac:ApplicationDidChangeStatusBarOrientation", - 1070: "mac:ApplicationDidChangeTheme", - 1071: "mac:ApplicationDidFinishLaunching", - 1072: "mac:ApplicationDidHide", - 1073: "mac:ApplicationDidResignActive", - 1074: "mac:ApplicationDidUnhide", - 1075: "mac:ApplicationDidUpdate", - 1076: "mac:ApplicationShouldHandleReopen", - 1077: "mac:ApplicationWillBecomeActive", - 1078: "mac:ApplicationWillFinishLaunching", - 1079: "mac:ApplicationWillHide", - 1080: "mac:ApplicationWillResignActive", - 1081: "mac:ApplicationWillTerminate", - 1082: "mac:ApplicationWillUnhide", - 1083: "mac:ApplicationWillUpdate", - 1084: "mac:MenuDidAddItem", - 1085: "mac:MenuDidBeginTracking", - 1086: "mac:MenuDidClose", - 1087: "mac:MenuDidDisplayItem", - 1088: "mac:MenuDidEndTracking", - 1089: "mac:MenuDidHighlightItem", - 1090: "mac:MenuDidOpen", - 1091: "mac:MenuDidPopUp", - 1092: "mac:MenuDidRemoveItem", - 1093: "mac:MenuDidSendAction", - 1094: "mac:MenuDidSendActionToItem", - 1095: "mac:MenuDidUpdate", - 1096: "mac:MenuWillAddItem", - 1097: "mac:MenuWillBeginTracking", - 1098: "mac:MenuWillDisplayItem", - 1099: "mac:MenuWillEndTracking", - 1100: "mac:MenuWillHighlightItem", - 1101: "mac:MenuWillOpen", - 1102: "mac:MenuWillPopUp", - 1103: "mac:MenuWillRemoveItem", - 1104: "mac:MenuWillSendAction", - 1105: "mac:MenuWillSendActionToItem", - 1106: "mac:MenuWillUpdate", - 1107: "mac:WebViewDidCommitNavigation", - 1108: "mac:WebViewDidFinishNavigation", - 1109: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", - 1110: "mac:WebViewDidStartProvisionalNavigation", - 1111: "mac:WindowDidBecomeKey", - 1112: "mac:WindowDidBecomeMain", - 1113: "mac:WindowDidBeginSheet", - 1114: "mac:WindowDidChangeAlpha", - 1115: "mac:WindowDidChangeBackingLocation", - 1116: "mac:WindowDidChangeBackingProperties", - 1117: "mac:WindowDidChangeCollectionBehavior", - 1118: "mac:WindowDidChangeEffectiveAppearance", - 1119: "mac:WindowDidChangeOcclusionState", - 1120: "mac:WindowDidChangeOrderingMode", - 1121: "mac:WindowDidChangeScreen", - 1122: "mac:WindowDidChangeScreenParameters", - 1123: "mac:WindowDidChangeScreenProfile", - 1124: "mac:WindowDidChangeScreenSpace", - 1125: "mac:WindowDidChangeScreenSpaceProperties", - 1126: "mac:WindowDidChangeSharingType", - 1127: "mac:WindowDidChangeSpace", - 1128: "mac:WindowDidChangeSpaceOrderingMode", - 1129: "mac:WindowDidChangeTitle", - 1130: "mac:WindowDidChangeToolbar", - 1131: "mac:WindowDidDeminiaturize", - 1132: "mac:WindowDidEndSheet", - 1133: "mac:WindowDidEnterFullScreen", - 1134: "mac:WindowDidEnterVersionBrowser", - 1135: "mac:WindowDidExitFullScreen", - 1136: "mac:WindowDidExitVersionBrowser", - 1137: "mac:WindowDidExpose", - 1138: "mac:WindowDidFocus", - 1139: "mac:WindowDidMiniaturize", - 1140: "mac:WindowDidMove", - 1141: "mac:WindowDidOrderOffScreen", - 1142: "mac:WindowDidOrderOnScreen", - 1143: "mac:WindowDidResignKey", - 1144: "mac:WindowDidResignMain", - 1145: "mac:WindowDidResize", - 1146: "mac:WindowDidUpdate", - 1147: "mac:WindowDidUpdateAlpha", - 1148: "mac:WindowDidUpdateCollectionBehavior", - 1149: "mac:WindowDidUpdateCollectionProperties", - 1150: "mac:WindowDidUpdateShadow", - 1151: "mac:WindowDidUpdateTitle", - 1152: "mac:WindowDidUpdateToolbar", - 1153: "mac:WindowDidZoom", - 1154: "mac:WindowFileDraggingEntered", - 1155: "mac:WindowFileDraggingExited", - 1156: "mac:WindowFileDraggingPerformed", - 1157: "mac:WindowHide", - 1158: "mac:WindowMaximise", - 1159: "mac:WindowUnMaximise", - 1160: "mac:WindowMinimise", - 1161: "mac:WindowUnMinimise", - 1162: "mac:WindowShouldClose", - 1163: "mac:WindowShow", - 1164: "mac:WindowWillBecomeKey", - 1165: "mac:WindowWillBecomeMain", - 1166: "mac:WindowWillBeginSheet", - 1167: "mac:WindowWillChangeOrderingMode", - 1168: "mac:WindowWillClose", - 1169: "mac:WindowWillDeminiaturize", - 1170: "mac:WindowWillEnterFullScreen", - 1171: "mac:WindowWillEnterVersionBrowser", - 1172: "mac:WindowWillExitFullScreen", - 1173: "mac:WindowWillExitVersionBrowser", - 1174: "mac:WindowWillFocus", - 1175: "mac:WindowWillMiniaturize", - 1176: "mac:WindowWillMove", - 1177: "mac:WindowWillOrderOffScreen", - 1178: "mac:WindowWillOrderOnScreen", - 1179: "mac:WindowWillResignMain", - 1180: "mac:WindowWillResize", - 1181: "mac:WindowWillUnfocus", - 1182: "mac:WindowWillUpdate", - 1183: "mac:WindowWillUpdateAlpha", - 1184: "mac:WindowWillUpdateCollectionBehavior", - 1185: "mac:WindowWillUpdateCollectionProperties", - 1186: "mac:WindowWillUpdateShadow", - 1187: "mac:WindowWillUpdateTitle", - 1188: "mac:WindowWillUpdateToolbar", - 1189: "mac:WindowWillUpdateVisibility", - 1190: "mac:WindowWillUseStandardFrame", - 1191: "mac:WindowZoomIn", - 1192: "mac:WindowZoomOut", - 1193: "mac:WindowZoomReset", - 1194: "windows:APMPowerSettingChange", - 1195: "windows:APMPowerStatusChange", - 1196: "windows:APMResumeAutomatic", - 1197: "windows:APMResumeSuspend", - 1198: "windows:APMSuspend", - 1199: "windows:ApplicationStarted", - 1200: "windows:SystemThemeChanged", - 1201: "windows:WebViewNavigationCompleted", - 1202: "windows:WindowActive", - 1203: "windows:WindowBackgroundErase", - 1204: "windows:WindowClickActive", - 1205: "windows:WindowClosing", - 1206: "windows:WindowDidMove", - 1207: "windows:WindowDidResize", - 1208: "windows:WindowDPIChanged", - 1209: "windows:WindowDragDrop", - 1210: "windows:WindowDragEnter", - 1211: "windows:WindowDragLeave", - 1212: "windows:WindowDragOver", - 1213: "windows:WindowEndMove", - 1214: "windows:WindowEndResize", - 1215: "windows:WindowFullscreen", - 1216: "windows:WindowHide", - 1217: "windows:WindowInactive", - 1218: "windows:WindowKeyDown", - 1219: "windows:WindowKeyUp", - 1220: "windows:WindowKillFocus", - 1221: "windows:WindowNonClientHit", - 1222: "windows:WindowNonClientMouseDown", - 1223: "windows:WindowNonClientMouseLeave", - 1224: "windows:WindowNonClientMouseMove", - 1225: "windows:WindowNonClientMouseUp", - 1226: "windows:WindowPaint", - 1227: "windows:WindowRestore", - 1228: "windows:WindowSetFocus", - 1229: "windows:WindowShow", - 1230: "windows:WindowStartMove", - 1231: "windows:WindowStartResize", - 1232: "windows:WindowUnFullscreen", - 1233: "windows:WindowZOrderChanged", - 1234: "windows:WindowMinimise", - 1235: "windows:WindowUnMinimise", - 1236: "windows:WindowMaximise", - 1237: "windows:WindowUnMaximise", - 1238: "ios:ApplicationDidBecomeActive", - 1239: "ios:ApplicationDidEnterBackground", - 1240: "ios:ApplicationDidFinishLaunching", - 1241: "ios:ApplicationDidReceiveMemoryWarning", - 1242: "ios:ApplicationWillEnterForeground", - 1243: "ios:ApplicationWillResignActive", - 1244: "ios:ApplicationWillTerminate", - 1245: "ios:WindowDidLoad", - 1246: "ios:WindowWillAppear", - 1247: "ios:WindowDidAppear", - 1248: "ios:WindowWillDisappear", - 1249: "ios:WindowDidDisappear", - 1250: "ios:WindowSafeAreaInsetsChanged", - 1251: "ios:WindowOrientationChanged", - 1252: "ios:WindowTouchBegan", - 1253: "ios:WindowTouchMoved", - 1254: "ios:WindowTouchEnded", - 1255: "ios:WindowTouchCancelled", - 1256: "ios:WebViewDidStartNavigation", - 1257: "ios:WebViewDidFinishNavigation", - 1258: "ios:WebViewDidFailNavigation", - 1259: "ios:WebViewDecidePolicyForNavigationAction", + 1050: "linux:ApplicationStartup", + 1051: "linux:SystemThemeChanged", + 1052: "linux:WindowDeleteEvent", + 1053: "linux:WindowDidMove", + 1054: "linux:WindowDidResize", + 1055: "linux:WindowFocusIn", + 1056: "linux:WindowFocusOut", + 1057: "linux:WindowLoadStarted", + 1058: "linux:WindowLoadRedirected", + 1059: "linux:WindowLoadCommitted", + 1060: "linux:WindowLoadFinished", + 1061: "mac:ApplicationDidBecomeActive", + 1062: "mac:ApplicationDidChangeBackingProperties", + 1063: "mac:ApplicationDidChangeEffectiveAppearance", + 1064: "mac:ApplicationDidChangeIcon", + 1065: "mac:ApplicationDidChangeOcclusionState", + 1066: "mac:ApplicationDidChangeScreenParameters", + 1067: "mac:ApplicationDidChangeStatusBarFrame", + 1068: "mac:ApplicationDidChangeStatusBarOrientation", + 1069: "mac:ApplicationDidChangeTheme", + 1070: "mac:ApplicationDidFinishLaunching", + 1071: "mac:ApplicationDidHide", + 1072: "mac:ApplicationDidResignActive", + 1073: "mac:ApplicationDidUnhide", + 1074: "mac:ApplicationDidUpdate", + 1075: "mac:ApplicationShouldHandleReopen", + 1076: "mac:ApplicationWillBecomeActive", + 1077: "mac:ApplicationWillFinishLaunching", + 1078: "mac:ApplicationWillHide", + 1079: "mac:ApplicationWillResignActive", + 1080: "mac:ApplicationWillTerminate", + 1081: "mac:ApplicationWillUnhide", + 1082: "mac:ApplicationWillUpdate", + 1083: "mac:MenuDidAddItem", + 1084: "mac:MenuDidBeginTracking", + 1085: "mac:MenuDidClose", + 1086: "mac:MenuDidDisplayItem", + 1087: "mac:MenuDidEndTracking", + 1088: "mac:MenuDidHighlightItem", + 1089: "mac:MenuDidOpen", + 1090: "mac:MenuDidPopUp", + 1091: "mac:MenuDidRemoveItem", + 1092: "mac:MenuDidSendAction", + 1093: "mac:MenuDidSendActionToItem", + 1094: "mac:MenuDidUpdate", + 1095: "mac:MenuWillAddItem", + 1096: "mac:MenuWillBeginTracking", + 1097: "mac:MenuWillDisplayItem", + 1098: "mac:MenuWillEndTracking", + 1099: "mac:MenuWillHighlightItem", + 1100: "mac:MenuWillOpen", + 1101: "mac:MenuWillPopUp", + 1102: "mac:MenuWillRemoveItem", + 1103: "mac:MenuWillSendAction", + 1104: "mac:MenuWillSendActionToItem", + 1105: "mac:MenuWillUpdate", + 1106: "mac:WebViewDidCommitNavigation", + 1107: "mac:WebViewDidFinishNavigation", + 1108: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + 1109: "mac:WebViewDidStartProvisionalNavigation", + 1110: "mac:WindowDidBecomeKey", + 1111: "mac:WindowDidBecomeMain", + 1112: "mac:WindowDidBeginSheet", + 1113: "mac:WindowDidChangeAlpha", + 1114: "mac:WindowDidChangeBackingLocation", + 1115: "mac:WindowDidChangeBackingProperties", + 1116: "mac:WindowDidChangeCollectionBehavior", + 1117: "mac:WindowDidChangeEffectiveAppearance", + 1118: "mac:WindowDidChangeOcclusionState", + 1119: "mac:WindowDidChangeOrderingMode", + 1120: "mac:WindowDidChangeScreen", + 1121: "mac:WindowDidChangeScreenParameters", + 1122: "mac:WindowDidChangeScreenProfile", + 1123: "mac:WindowDidChangeScreenSpace", + 1124: "mac:WindowDidChangeScreenSpaceProperties", + 1125: "mac:WindowDidChangeSharingType", + 1126: "mac:WindowDidChangeSpace", + 1127: "mac:WindowDidChangeSpaceOrderingMode", + 1128: "mac:WindowDidChangeTitle", + 1129: "mac:WindowDidChangeToolbar", + 1130: "mac:WindowDidDeminiaturize", + 1131: "mac:WindowDidEndSheet", + 1132: "mac:WindowDidEnterFullScreen", + 1133: "mac:WindowDidEnterVersionBrowser", + 1134: "mac:WindowDidExitFullScreen", + 1135: "mac:WindowDidExitVersionBrowser", + 1136: "mac:WindowDidExpose", + 1137: "mac:WindowDidFocus", + 1138: "mac:WindowDidMiniaturize", + 1139: "mac:WindowDidMove", + 1140: "mac:WindowDidOrderOffScreen", + 1141: "mac:WindowDidOrderOnScreen", + 1142: "mac:WindowDidResignKey", + 1143: "mac:WindowDidResignMain", + 1144: "mac:WindowDidResize", + 1145: "mac:WindowDidUpdate", + 1146: "mac:WindowDidUpdateAlpha", + 1147: "mac:WindowDidUpdateCollectionBehavior", + 1148: "mac:WindowDidUpdateCollectionProperties", + 1149: "mac:WindowDidUpdateShadow", + 1150: "mac:WindowDidUpdateTitle", + 1151: "mac:WindowDidUpdateToolbar", + 1152: "mac:WindowDidZoom", + 1153: "mac:WindowFileDraggingEntered", + 1154: "mac:WindowFileDraggingExited", + 1155: "mac:WindowFileDraggingPerformed", + 1156: "mac:WindowHide", + 1157: "mac:WindowMaximise", + 1158: "mac:WindowUnMaximise", + 1159: "mac:WindowMinimise", + 1160: "mac:WindowUnMinimise", + 1161: "mac:WindowShouldClose", + 1162: "mac:WindowShow", + 1163: "mac:WindowWillBecomeKey", + 1164: "mac:WindowWillBecomeMain", + 1165: "mac:WindowWillBeginSheet", + 1166: "mac:WindowWillChangeOrderingMode", + 1167: "mac:WindowWillClose", + 1168: "mac:WindowWillDeminiaturize", + 1169: "mac:WindowWillEnterFullScreen", + 1170: "mac:WindowWillEnterVersionBrowser", + 1171: "mac:WindowWillExitFullScreen", + 1172: "mac:WindowWillExitVersionBrowser", + 1173: "mac:WindowWillFocus", + 1174: "mac:WindowWillMiniaturize", + 1175: "mac:WindowWillMove", + 1176: "mac:WindowWillOrderOffScreen", + 1177: "mac:WindowWillOrderOnScreen", + 1178: "mac:WindowWillResignMain", + 1179: "mac:WindowWillResize", + 1180: "mac:WindowWillUnfocus", + 1181: "mac:WindowWillUpdate", + 1182: "mac:WindowWillUpdateAlpha", + 1183: "mac:WindowWillUpdateCollectionBehavior", + 1184: "mac:WindowWillUpdateCollectionProperties", + 1185: "mac:WindowWillUpdateShadow", + 1186: "mac:WindowWillUpdateTitle", + 1187: "mac:WindowWillUpdateToolbar", + 1188: "mac:WindowWillUpdateVisibility", + 1189: "mac:WindowWillUseStandardFrame", + 1190: "mac:WindowZoomIn", + 1191: "mac:WindowZoomOut", + 1192: "mac:WindowZoomReset", + 1193: "windows:APMPowerSettingChange", + 1194: "windows:APMPowerStatusChange", + 1195: "windows:APMResumeAutomatic", + 1196: "windows:APMResumeSuspend", + 1197: "windows:APMSuspend", + 1198: "windows:ApplicationStarted", + 1199: "windows:SystemThemeChanged", + 1200: "windows:WebViewNavigationCompleted", + 1201: "windows:WindowActive", + 1202: "windows:WindowBackgroundErase", + 1203: "windows:WindowClickActive", + 1204: "windows:WindowClosing", + 1205: "windows:WindowDidMove", + 1206: "windows:WindowDidResize", + 1207: "windows:WindowDPIChanged", + 1208: "windows:WindowDragDrop", + 1209: "windows:WindowDragEnter", + 1210: "windows:WindowDragLeave", + 1211: "windows:WindowDragOver", + 1212: "windows:WindowEndMove", + 1213: "windows:WindowEndResize", + 1214: "windows:WindowFullscreen", + 1215: "windows:WindowHide", + 1216: "windows:WindowInactive", + 1217: "windows:WindowKeyDown", + 1218: "windows:WindowKeyUp", + 1219: "windows:WindowKillFocus", + 1220: "windows:WindowNonClientHit", + 1221: "windows:WindowNonClientMouseDown", + 1222: "windows:WindowNonClientMouseLeave", + 1223: "windows:WindowNonClientMouseMove", + 1224: "windows:WindowNonClientMouseUp", + 1225: "windows:WindowPaint", + 1226: "windows:WindowRestore", + 1227: "windows:WindowSetFocus", + 1228: "windows:WindowShow", + 1229: "windows:WindowStartMove", + 1230: "windows:WindowStartResize", + 1231: "windows:WindowUnFullscreen", + 1232: "windows:WindowZOrderChanged", + 1233: "windows:WindowMinimise", + 1234: "windows:WindowUnMinimise", + 1235: "windows:WindowMaximise", + 1236: "windows:WindowUnMaximise", + 1237: "ios:ApplicationDidBecomeActive", + 1238: "ios:ApplicationDidEnterBackground", + 1239: "ios:ApplicationDidFinishLaunching", + 1240: "ios:ApplicationDidReceiveMemoryWarning", + 1241: "ios:ApplicationWillEnterForeground", + 1242: "ios:ApplicationWillResignActive", + 1243: "ios:ApplicationWillTerminate", + 1244: "ios:WindowDidLoad", + 1245: "ios:WindowWillAppear", + 1246: "ios:WindowDidAppear", + 1247: "ios:WindowWillDisappear", + 1248: "ios:WindowDidDisappear", + 1249: "ios:WindowSafeAreaInsetsChanged", + 1250: "ios:WindowOrientationChanged", + 1251: "ios:WindowTouchBegan", + 1252: "ios:WindowTouchMoved", + 1253: "ios:WindowTouchEnded", + 1254: "ios:WindowTouchCancelled", + 1255: "ios:WebViewDidStartNavigation", + 1256: "ios:WebViewDidFinishNavigation", + 1257: "ios:WebViewDidFailNavigation", + 1258: "ios:WebViewDecidePolicyForNavigationAction", } diff --git a/v3/pkg/events/events.txt b/v3/pkg/events/events.txt index e4d99f5b4..9a6c5fd6d 100644 --- a/v3/pkg/events/events.txt +++ b/v3/pkg/events/events.txt @@ -24,7 +24,6 @@ common:WindowZoom common:WindowZoomIn common:WindowZoomOut common:WindowZoomReset -common:WindowDropZoneFilesDropped linux:ApplicationStartup linux:SystemThemeChanged linux:WindowDeleteEvent diff --git a/v3/pkg/events/events_darwin.h b/v3/pkg/events/events_darwin.h index 1ab2cc300..1547779ce 100644 --- a/v3/pkg/events/events_darwin.h +++ b/v3/pkg/events/events_darwin.h @@ -6,140 +6,140 @@ extern void processApplicationEvent(unsigned int, void* data); extern void processWindowEvent(unsigned int, unsigned int); -#define EventApplicationDidBecomeActive 1062 -#define EventApplicationDidChangeBackingProperties 1063 -#define EventApplicationDidChangeEffectiveAppearance 1064 -#define EventApplicationDidChangeIcon 1065 -#define EventApplicationDidChangeOcclusionState 1066 -#define EventApplicationDidChangeScreenParameters 1067 -#define EventApplicationDidChangeStatusBarFrame 1068 -#define EventApplicationDidChangeStatusBarOrientation 1069 -#define EventApplicationDidChangeTheme 1070 -#define EventApplicationDidFinishLaunching 1071 -#define EventApplicationDidHide 1072 -#define EventApplicationDidResignActive 1073 -#define EventApplicationDidUnhide 1074 -#define EventApplicationDidUpdate 1075 -#define EventApplicationShouldHandleReopen 1076 -#define EventApplicationWillBecomeActive 1077 -#define EventApplicationWillFinishLaunching 1078 -#define EventApplicationWillHide 1079 -#define EventApplicationWillResignActive 1080 -#define EventApplicationWillTerminate 1081 -#define EventApplicationWillUnhide 1082 -#define EventApplicationWillUpdate 1083 -#define EventMenuDidAddItem 1084 -#define EventMenuDidBeginTracking 1085 -#define EventMenuDidClose 1086 -#define EventMenuDidDisplayItem 1087 -#define EventMenuDidEndTracking 1088 -#define EventMenuDidHighlightItem 1089 -#define EventMenuDidOpen 1090 -#define EventMenuDidPopUp 1091 -#define EventMenuDidRemoveItem 1092 -#define EventMenuDidSendAction 1093 -#define EventMenuDidSendActionToItem 1094 -#define EventMenuDidUpdate 1095 -#define EventMenuWillAddItem 1096 -#define EventMenuWillBeginTracking 1097 -#define EventMenuWillDisplayItem 1098 -#define EventMenuWillEndTracking 1099 -#define EventMenuWillHighlightItem 1100 -#define EventMenuWillOpen 1101 -#define EventMenuWillPopUp 1102 -#define EventMenuWillRemoveItem 1103 -#define EventMenuWillSendAction 1104 -#define EventMenuWillSendActionToItem 1105 -#define EventMenuWillUpdate 1106 -#define EventWebViewDidCommitNavigation 1107 -#define EventWebViewDidFinishNavigation 1108 -#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1109 -#define EventWebViewDidStartProvisionalNavigation 1110 -#define EventWindowDidBecomeKey 1111 -#define EventWindowDidBecomeMain 1112 -#define EventWindowDidBeginSheet 1113 -#define EventWindowDidChangeAlpha 1114 -#define EventWindowDidChangeBackingLocation 1115 -#define EventWindowDidChangeBackingProperties 1116 -#define EventWindowDidChangeCollectionBehavior 1117 -#define EventWindowDidChangeEffectiveAppearance 1118 -#define EventWindowDidChangeOcclusionState 1119 -#define EventWindowDidChangeOrderingMode 1120 -#define EventWindowDidChangeScreen 1121 -#define EventWindowDidChangeScreenParameters 1122 -#define EventWindowDidChangeScreenProfile 1123 -#define EventWindowDidChangeScreenSpace 1124 -#define EventWindowDidChangeScreenSpaceProperties 1125 -#define EventWindowDidChangeSharingType 1126 -#define EventWindowDidChangeSpace 1127 -#define EventWindowDidChangeSpaceOrderingMode 1128 -#define EventWindowDidChangeTitle 1129 -#define EventWindowDidChangeToolbar 1130 -#define EventWindowDidDeminiaturize 1131 -#define EventWindowDidEndSheet 1132 -#define EventWindowDidEnterFullScreen 1133 -#define EventWindowDidEnterVersionBrowser 1134 -#define EventWindowDidExitFullScreen 1135 -#define EventWindowDidExitVersionBrowser 1136 -#define EventWindowDidExpose 1137 -#define EventWindowDidFocus 1138 -#define EventWindowDidMiniaturize 1139 -#define EventWindowDidMove 1140 -#define EventWindowDidOrderOffScreen 1141 -#define EventWindowDidOrderOnScreen 1142 -#define EventWindowDidResignKey 1143 -#define EventWindowDidResignMain 1144 -#define EventWindowDidResize 1145 -#define EventWindowDidUpdate 1146 -#define EventWindowDidUpdateAlpha 1147 -#define EventWindowDidUpdateCollectionBehavior 1148 -#define EventWindowDidUpdateCollectionProperties 1149 -#define EventWindowDidUpdateShadow 1150 -#define EventWindowDidUpdateTitle 1151 -#define EventWindowDidUpdateToolbar 1152 -#define EventWindowDidZoom 1153 -#define EventWindowFileDraggingEntered 1154 -#define EventWindowFileDraggingExited 1155 -#define EventWindowFileDraggingPerformed 1156 -#define EventWindowHide 1157 -#define EventWindowMaximise 1158 -#define EventWindowUnMaximise 1159 -#define EventWindowMinimise 1160 -#define EventWindowUnMinimise 1161 -#define EventWindowShouldClose 1162 -#define EventWindowShow 1163 -#define EventWindowWillBecomeKey 1164 -#define EventWindowWillBecomeMain 1165 -#define EventWindowWillBeginSheet 1166 -#define EventWindowWillChangeOrderingMode 1167 -#define EventWindowWillClose 1168 -#define EventWindowWillDeminiaturize 1169 -#define EventWindowWillEnterFullScreen 1170 -#define EventWindowWillEnterVersionBrowser 1171 -#define EventWindowWillExitFullScreen 1172 -#define EventWindowWillExitVersionBrowser 1173 -#define EventWindowWillFocus 1174 -#define EventWindowWillMiniaturize 1175 -#define EventWindowWillMove 1176 -#define EventWindowWillOrderOffScreen 1177 -#define EventWindowWillOrderOnScreen 1178 -#define EventWindowWillResignMain 1179 -#define EventWindowWillResize 1180 -#define EventWindowWillUnfocus 1181 -#define EventWindowWillUpdate 1182 -#define EventWindowWillUpdateAlpha 1183 -#define EventWindowWillUpdateCollectionBehavior 1184 -#define EventWindowWillUpdateCollectionProperties 1185 -#define EventWindowWillUpdateShadow 1186 -#define EventWindowWillUpdateTitle 1187 -#define EventWindowWillUpdateToolbar 1188 -#define EventWindowWillUpdateVisibility 1189 -#define EventWindowWillUseStandardFrame 1190 -#define EventWindowZoomIn 1191 -#define EventWindowZoomOut 1192 -#define EventWindowZoomReset 1193 +#define EventApplicationDidBecomeActive 1061 +#define EventApplicationDidChangeBackingProperties 1062 +#define EventApplicationDidChangeEffectiveAppearance 1063 +#define EventApplicationDidChangeIcon 1064 +#define EventApplicationDidChangeOcclusionState 1065 +#define EventApplicationDidChangeScreenParameters 1066 +#define EventApplicationDidChangeStatusBarFrame 1067 +#define EventApplicationDidChangeStatusBarOrientation 1068 +#define EventApplicationDidChangeTheme 1069 +#define EventApplicationDidFinishLaunching 1070 +#define EventApplicationDidHide 1071 +#define EventApplicationDidResignActive 1072 +#define EventApplicationDidUnhide 1073 +#define EventApplicationDidUpdate 1074 +#define EventApplicationShouldHandleReopen 1075 +#define EventApplicationWillBecomeActive 1076 +#define EventApplicationWillFinishLaunching 1077 +#define EventApplicationWillHide 1078 +#define EventApplicationWillResignActive 1079 +#define EventApplicationWillTerminate 1080 +#define EventApplicationWillUnhide 1081 +#define EventApplicationWillUpdate 1082 +#define EventMenuDidAddItem 1083 +#define EventMenuDidBeginTracking 1084 +#define EventMenuDidClose 1085 +#define EventMenuDidDisplayItem 1086 +#define EventMenuDidEndTracking 1087 +#define EventMenuDidHighlightItem 1088 +#define EventMenuDidOpen 1089 +#define EventMenuDidPopUp 1090 +#define EventMenuDidRemoveItem 1091 +#define EventMenuDidSendAction 1092 +#define EventMenuDidSendActionToItem 1093 +#define EventMenuDidUpdate 1094 +#define EventMenuWillAddItem 1095 +#define EventMenuWillBeginTracking 1096 +#define EventMenuWillDisplayItem 1097 +#define EventMenuWillEndTracking 1098 +#define EventMenuWillHighlightItem 1099 +#define EventMenuWillOpen 1100 +#define EventMenuWillPopUp 1101 +#define EventMenuWillRemoveItem 1102 +#define EventMenuWillSendAction 1103 +#define EventMenuWillSendActionToItem 1104 +#define EventMenuWillUpdate 1105 +#define EventWebViewDidCommitNavigation 1106 +#define EventWebViewDidFinishNavigation 1107 +#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1108 +#define EventWebViewDidStartProvisionalNavigation 1109 +#define EventWindowDidBecomeKey 1110 +#define EventWindowDidBecomeMain 1111 +#define EventWindowDidBeginSheet 1112 +#define EventWindowDidChangeAlpha 1113 +#define EventWindowDidChangeBackingLocation 1114 +#define EventWindowDidChangeBackingProperties 1115 +#define EventWindowDidChangeCollectionBehavior 1116 +#define EventWindowDidChangeEffectiveAppearance 1117 +#define EventWindowDidChangeOcclusionState 1118 +#define EventWindowDidChangeOrderingMode 1119 +#define EventWindowDidChangeScreen 1120 +#define EventWindowDidChangeScreenParameters 1121 +#define EventWindowDidChangeScreenProfile 1122 +#define EventWindowDidChangeScreenSpace 1123 +#define EventWindowDidChangeScreenSpaceProperties 1124 +#define EventWindowDidChangeSharingType 1125 +#define EventWindowDidChangeSpace 1126 +#define EventWindowDidChangeSpaceOrderingMode 1127 +#define EventWindowDidChangeTitle 1128 +#define EventWindowDidChangeToolbar 1129 +#define EventWindowDidDeminiaturize 1130 +#define EventWindowDidEndSheet 1131 +#define EventWindowDidEnterFullScreen 1132 +#define EventWindowDidEnterVersionBrowser 1133 +#define EventWindowDidExitFullScreen 1134 +#define EventWindowDidExitVersionBrowser 1135 +#define EventWindowDidExpose 1136 +#define EventWindowDidFocus 1137 +#define EventWindowDidMiniaturize 1138 +#define EventWindowDidMove 1139 +#define EventWindowDidOrderOffScreen 1140 +#define EventWindowDidOrderOnScreen 1141 +#define EventWindowDidResignKey 1142 +#define EventWindowDidResignMain 1143 +#define EventWindowDidResize 1144 +#define EventWindowDidUpdate 1145 +#define EventWindowDidUpdateAlpha 1146 +#define EventWindowDidUpdateCollectionBehavior 1147 +#define EventWindowDidUpdateCollectionProperties 1148 +#define EventWindowDidUpdateShadow 1149 +#define EventWindowDidUpdateTitle 1150 +#define EventWindowDidUpdateToolbar 1151 +#define EventWindowDidZoom 1152 +#define EventWindowFileDraggingEntered 1153 +#define EventWindowFileDraggingExited 1154 +#define EventWindowFileDraggingPerformed 1155 +#define EventWindowHide 1156 +#define EventWindowMaximise 1157 +#define EventWindowUnMaximise 1158 +#define EventWindowMinimise 1159 +#define EventWindowUnMinimise 1160 +#define EventWindowShouldClose 1161 +#define EventWindowShow 1162 +#define EventWindowWillBecomeKey 1163 +#define EventWindowWillBecomeMain 1164 +#define EventWindowWillBeginSheet 1165 +#define EventWindowWillChangeOrderingMode 1166 +#define EventWindowWillClose 1167 +#define EventWindowWillDeminiaturize 1168 +#define EventWindowWillEnterFullScreen 1169 +#define EventWindowWillEnterVersionBrowser 1170 +#define EventWindowWillExitFullScreen 1171 +#define EventWindowWillExitVersionBrowser 1172 +#define EventWindowWillFocus 1173 +#define EventWindowWillMiniaturize 1174 +#define EventWindowWillMove 1175 +#define EventWindowWillOrderOffScreen 1176 +#define EventWindowWillOrderOnScreen 1177 +#define EventWindowWillResignMain 1178 +#define EventWindowWillResize 1179 +#define EventWindowWillUnfocus 1180 +#define EventWindowWillUpdate 1181 +#define EventWindowWillUpdateAlpha 1182 +#define EventWindowWillUpdateCollectionBehavior 1183 +#define EventWindowWillUpdateCollectionProperties 1184 +#define EventWindowWillUpdateShadow 1185 +#define EventWindowWillUpdateTitle 1186 +#define EventWindowWillUpdateToolbar 1187 +#define EventWindowWillUpdateVisibility 1188 +#define EventWindowWillUseStandardFrame 1189 +#define EventWindowZoomIn 1190 +#define EventWindowZoomOut 1191 +#define EventWindowZoomReset 1192 -#define MAX_EVENTS 1194 +#define MAX_EVENTS 1193 #endif \ No newline at end of file diff --git a/v3/pkg/events/events_ios.h b/v3/pkg/events/events_ios.h index 92f4e05a1..db5853708 100644 --- a/v3/pkg/events/events_ios.h +++ b/v3/pkg/events/events_ios.h @@ -6,30 +6,30 @@ extern void processApplicationEvent(unsigned int, void* data); extern void processWindowEvent(unsigned int, unsigned int); -#define EventApplicationDidBecomeActive 1238 -#define EventApplicationDidEnterBackground 1239 -#define EventApplicationDidFinishLaunching 1240 -#define EventApplicationDidReceiveMemoryWarning 1241 -#define EventApplicationWillEnterForeground 1242 -#define EventApplicationWillResignActive 1243 -#define EventApplicationWillTerminate 1244 -#define EventWindowDidLoad 1245 -#define EventWindowWillAppear 1246 -#define EventWindowDidAppear 1247 -#define EventWindowWillDisappear 1248 -#define EventWindowDidDisappear 1249 -#define EventWindowSafeAreaInsetsChanged 1250 -#define EventWindowOrientationChanged 1251 -#define EventWindowTouchBegan 1252 -#define EventWindowTouchMoved 1253 -#define EventWindowTouchEnded 1254 -#define EventWindowTouchCancelled 1255 -#define EventWebViewDidStartNavigation 1256 -#define EventWebViewDidFinishNavigation 1257 -#define EventWebViewDidFailNavigation 1258 -#define EventWebViewDecidePolicyForNavigationAction 1259 +#define EventApplicationDidBecomeActive 1237 +#define EventApplicationDidEnterBackground 1238 +#define EventApplicationDidFinishLaunching 1239 +#define EventApplicationDidReceiveMemoryWarning 1240 +#define EventApplicationWillEnterForeground 1241 +#define EventApplicationWillResignActive 1242 +#define EventApplicationWillTerminate 1243 +#define EventWindowDidLoad 1244 +#define EventWindowWillAppear 1245 +#define EventWindowDidAppear 1246 +#define EventWindowWillDisappear 1247 +#define EventWindowDidDisappear 1248 +#define EventWindowSafeAreaInsetsChanged 1249 +#define EventWindowOrientationChanged 1250 +#define EventWindowTouchBegan 1251 +#define EventWindowTouchMoved 1252 +#define EventWindowTouchEnded 1253 +#define EventWindowTouchCancelled 1254 +#define EventWebViewDidStartNavigation 1255 +#define EventWebViewDidFinishNavigation 1256 +#define EventWebViewDidFailNavigation 1257 +#define EventWebViewDecidePolicyForNavigationAction 1258 -#define MAX_EVENTS 1260 +#define MAX_EVENTS 1259 #endif \ No newline at end of file diff --git a/v3/pkg/events/events_linux.h b/v3/pkg/events/events_linux.h index 176b6340e..40ebc95c5 100644 --- a/v3/pkg/events/events_linux.h +++ b/v3/pkg/events/events_linux.h @@ -6,19 +6,19 @@ extern void processApplicationEvent(unsigned int, void* data); extern void processWindowEvent(unsigned int, unsigned int); -#define EventApplicationStartup 1051 -#define EventSystemThemeChanged 1052 -#define EventWindowDeleteEvent 1053 -#define EventWindowDidMove 1054 -#define EventWindowDidResize 1055 -#define EventWindowFocusIn 1056 -#define EventWindowFocusOut 1057 -#define EventWindowLoadStarted 1058 -#define EventWindowLoadRedirected 1059 -#define EventWindowLoadCommitted 1060 -#define EventWindowLoadFinished 1061 +#define EventApplicationStartup 1050 +#define EventSystemThemeChanged 1051 +#define EventWindowDeleteEvent 1052 +#define EventWindowDidMove 1053 +#define EventWindowDidResize 1054 +#define EventWindowFocusIn 1055 +#define EventWindowFocusOut 1056 +#define EventWindowLoadStarted 1057 +#define EventWindowLoadRedirected 1058 +#define EventWindowLoadCommitted 1059 +#define EventWindowLoadFinished 1060 -#define MAX_EVENTS 1062 +#define MAX_EVENTS 1061 #endif \ No newline at end of file diff --git a/v3/pkg/events/known_events.go b/v3/pkg/events/known_events.go index 4f944d3b7..c3d23e038 100644 --- a/v3/pkg/events/known_events.go +++ b/v3/pkg/events/known_events.go @@ -6,218 +6,217 @@ func IsKnownEvent(name string) bool { } var knownEvents = map[string]struct{}{ - "common:ApplicationOpenedWithFile": {}, - "common:ApplicationStarted": {}, - "common:ApplicationLaunchedWithUrl": {}, - "common:ThemeChanged": {}, - "common:WindowClosing": {}, - "common:WindowDidMove": {}, - "common:WindowDidResize": {}, - "common:WindowDPIChanged": {}, - "common:WindowFilesDropped": {}, - "common:WindowFocus": {}, - "common:WindowFullscreen": {}, - "common:WindowHide": {}, - "common:WindowLostFocus": {}, - "common:WindowMaximise": {}, - "common:WindowMinimise": {}, - "common:WindowToggleFrameless": {}, - "common:WindowRestore": {}, - "common:WindowRuntimeReady": {}, - "common:WindowShow": {}, - "common:WindowUnFullscreen": {}, - "common:WindowUnMaximise": {}, - "common:WindowUnMinimise": {}, - "common:WindowZoom": {}, - "common:WindowZoomIn": {}, - "common:WindowZoomOut": {}, - "common:WindowZoomReset": {}, - "common:WindowDropZoneFilesDropped": {}, - "linux:ApplicationStartup": {}, - "linux:SystemThemeChanged": {}, - "linux:WindowDeleteEvent": {}, - "linux:WindowDidMove": {}, - "linux:WindowDidResize": {}, - "linux:WindowFocusIn": {}, - "linux:WindowFocusOut": {}, - "linux:WindowLoadStarted": {}, - "linux:WindowLoadRedirected": {}, - "linux:WindowLoadCommitted": {}, - "linux:WindowLoadFinished": {}, - "mac:ApplicationDidBecomeActive": {}, - "mac:ApplicationDidChangeBackingProperties": {}, - "mac:ApplicationDidChangeEffectiveAppearance": {}, - "mac:ApplicationDidChangeIcon": {}, - "mac:ApplicationDidChangeOcclusionState": {}, - "mac:ApplicationDidChangeScreenParameters": {}, - "mac:ApplicationDidChangeStatusBarFrame": {}, - "mac:ApplicationDidChangeStatusBarOrientation": {}, - "mac:ApplicationDidChangeTheme": {}, - "mac:ApplicationDidFinishLaunching": {}, - "mac:ApplicationDidHide": {}, - "mac:ApplicationDidResignActive": {}, - "mac:ApplicationDidUnhide": {}, - "mac:ApplicationDidUpdate": {}, - "mac:ApplicationShouldHandleReopen": {}, - "mac:ApplicationWillBecomeActive": {}, - "mac:ApplicationWillFinishLaunching": {}, - "mac:ApplicationWillHide": {}, - "mac:ApplicationWillResignActive": {}, - "mac:ApplicationWillTerminate": {}, - "mac:ApplicationWillUnhide": {}, - "mac:ApplicationWillUpdate": {}, - "mac:MenuDidAddItem": {}, - "mac:MenuDidBeginTracking": {}, - "mac:MenuDidClose": {}, - "mac:MenuDidDisplayItem": {}, - "mac:MenuDidEndTracking": {}, - "mac:MenuDidHighlightItem": {}, - "mac:MenuDidOpen": {}, - "mac:MenuDidPopUp": {}, - "mac:MenuDidRemoveItem": {}, - "mac:MenuDidSendAction": {}, - "mac:MenuDidSendActionToItem": {}, - "mac:MenuDidUpdate": {}, - "mac:MenuWillAddItem": {}, - "mac:MenuWillBeginTracking": {}, - "mac:MenuWillDisplayItem": {}, - "mac:MenuWillEndTracking": {}, - "mac:MenuWillHighlightItem": {}, - "mac:MenuWillOpen": {}, - "mac:MenuWillPopUp": {}, - "mac:MenuWillRemoveItem": {}, - "mac:MenuWillSendAction": {}, - "mac:MenuWillSendActionToItem": {}, - "mac:MenuWillUpdate": {}, - "mac:WebViewDidCommitNavigation": {}, - "mac:WebViewDidFinishNavigation": {}, + "common:ApplicationOpenedWithFile": {}, + "common:ApplicationStarted": {}, + "common:ApplicationLaunchedWithUrl": {}, + "common:ThemeChanged": {}, + "common:WindowClosing": {}, + "common:WindowDidMove": {}, + "common:WindowDidResize": {}, + "common:WindowDPIChanged": {}, + "common:WindowFilesDropped": {}, + "common:WindowFocus": {}, + "common:WindowFullscreen": {}, + "common:WindowHide": {}, + "common:WindowLostFocus": {}, + "common:WindowMaximise": {}, + "common:WindowMinimise": {}, + "common:WindowToggleFrameless": {}, + "common:WindowRestore": {}, + "common:WindowRuntimeReady": {}, + "common:WindowShow": {}, + "common:WindowUnFullscreen": {}, + "common:WindowUnMaximise": {}, + "common:WindowUnMinimise": {}, + "common:WindowZoom": {}, + "common:WindowZoomIn": {}, + "common:WindowZoomOut": {}, + "common:WindowZoomReset": {}, + "linux:ApplicationStartup": {}, + "linux:SystemThemeChanged": {}, + "linux:WindowDeleteEvent": {}, + "linux:WindowDidMove": {}, + "linux:WindowDidResize": {}, + "linux:WindowFocusIn": {}, + "linux:WindowFocusOut": {}, + "linux:WindowLoadStarted": {}, + "linux:WindowLoadRedirected": {}, + "linux:WindowLoadCommitted": {}, + "linux:WindowLoadFinished": {}, + "mac:ApplicationDidBecomeActive": {}, + "mac:ApplicationDidChangeBackingProperties": {}, + "mac:ApplicationDidChangeEffectiveAppearance": {}, + "mac:ApplicationDidChangeIcon": {}, + "mac:ApplicationDidChangeOcclusionState": {}, + "mac:ApplicationDidChangeScreenParameters": {}, + "mac:ApplicationDidChangeStatusBarFrame": {}, + "mac:ApplicationDidChangeStatusBarOrientation": {}, + "mac:ApplicationDidChangeTheme": {}, + "mac:ApplicationDidFinishLaunching": {}, + "mac:ApplicationDidHide": {}, + "mac:ApplicationDidResignActive": {}, + "mac:ApplicationDidUnhide": {}, + "mac:ApplicationDidUpdate": {}, + "mac:ApplicationShouldHandleReopen": {}, + "mac:ApplicationWillBecomeActive": {}, + "mac:ApplicationWillFinishLaunching": {}, + "mac:ApplicationWillHide": {}, + "mac:ApplicationWillResignActive": {}, + "mac:ApplicationWillTerminate": {}, + "mac:ApplicationWillUnhide": {}, + "mac:ApplicationWillUpdate": {}, + "mac:MenuDidAddItem": {}, + "mac:MenuDidBeginTracking": {}, + "mac:MenuDidClose": {}, + "mac:MenuDidDisplayItem": {}, + "mac:MenuDidEndTracking": {}, + "mac:MenuDidHighlightItem": {}, + "mac:MenuDidOpen": {}, + "mac:MenuDidPopUp": {}, + "mac:MenuDidRemoveItem": {}, + "mac:MenuDidSendAction": {}, + "mac:MenuDidSendActionToItem": {}, + "mac:MenuDidUpdate": {}, + "mac:MenuWillAddItem": {}, + "mac:MenuWillBeginTracking": {}, + "mac:MenuWillDisplayItem": {}, + "mac:MenuWillEndTracking": {}, + "mac:MenuWillHighlightItem": {}, + "mac:MenuWillOpen": {}, + "mac:MenuWillPopUp": {}, + "mac:MenuWillRemoveItem": {}, + "mac:MenuWillSendAction": {}, + "mac:MenuWillSendActionToItem": {}, + "mac:MenuWillUpdate": {}, + "mac:WebViewDidCommitNavigation": {}, + "mac:WebViewDidFinishNavigation": {}, "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation": {}, - "mac:WebViewDidStartProvisionalNavigation": {}, - "mac:WindowDidBecomeKey": {}, - "mac:WindowDidBecomeMain": {}, - "mac:WindowDidBeginSheet": {}, - "mac:WindowDidChangeAlpha": {}, - "mac:WindowDidChangeBackingLocation": {}, - "mac:WindowDidChangeBackingProperties": {}, - "mac:WindowDidChangeCollectionBehavior": {}, - "mac:WindowDidChangeEffectiveAppearance": {}, - "mac:WindowDidChangeOcclusionState": {}, - "mac:WindowDidChangeOrderingMode": {}, - "mac:WindowDidChangeScreen": {}, - "mac:WindowDidChangeScreenParameters": {}, - "mac:WindowDidChangeScreenProfile": {}, - "mac:WindowDidChangeScreenSpace": {}, - "mac:WindowDidChangeScreenSpaceProperties": {}, - "mac:WindowDidChangeSharingType": {}, - "mac:WindowDidChangeSpace": {}, - "mac:WindowDidChangeSpaceOrderingMode": {}, - "mac:WindowDidChangeTitle": {}, - "mac:WindowDidChangeToolbar": {}, - "mac:WindowDidDeminiaturize": {}, - "mac:WindowDidEndSheet": {}, - "mac:WindowDidEnterFullScreen": {}, - "mac:WindowDidEnterVersionBrowser": {}, - "mac:WindowDidExitFullScreen": {}, - "mac:WindowDidExitVersionBrowser": {}, - "mac:WindowDidExpose": {}, - "mac:WindowDidFocus": {}, - "mac:WindowDidMiniaturize": {}, - "mac:WindowDidMove": {}, - "mac:WindowDidOrderOffScreen": {}, - "mac:WindowDidOrderOnScreen": {}, - "mac:WindowDidResignKey": {}, - "mac:WindowDidResignMain": {}, - "mac:WindowDidResize": {}, - "mac:WindowDidUpdate": {}, - "mac:WindowDidUpdateAlpha": {}, - "mac:WindowDidUpdateCollectionBehavior": {}, - "mac:WindowDidUpdateCollectionProperties": {}, - "mac:WindowDidUpdateShadow": {}, - "mac:WindowDidUpdateTitle": {}, - "mac:WindowDidUpdateToolbar": {}, - "mac:WindowDidZoom": {}, - "mac:WindowFileDraggingEntered": {}, - "mac:WindowFileDraggingExited": {}, - "mac:WindowFileDraggingPerformed": {}, - "mac:WindowHide": {}, - "mac:WindowMaximise": {}, - "mac:WindowUnMaximise": {}, - "mac:WindowMinimise": {}, - "mac:WindowUnMinimise": {}, - "mac:WindowShouldClose": {}, - "mac:WindowShow": {}, - "mac:WindowWillBecomeKey": {}, - "mac:WindowWillBecomeMain": {}, - "mac:WindowWillBeginSheet": {}, - "mac:WindowWillChangeOrderingMode": {}, - "mac:WindowWillClose": {}, - "mac:WindowWillDeminiaturize": {}, - "mac:WindowWillEnterFullScreen": {}, - "mac:WindowWillEnterVersionBrowser": {}, - "mac:WindowWillExitFullScreen": {}, - "mac:WindowWillExitVersionBrowser": {}, - "mac:WindowWillFocus": {}, - "mac:WindowWillMiniaturize": {}, - "mac:WindowWillMove": {}, - "mac:WindowWillOrderOffScreen": {}, - "mac:WindowWillOrderOnScreen": {}, - "mac:WindowWillResignMain": {}, - "mac:WindowWillResize": {}, - "mac:WindowWillUnfocus": {}, - "mac:WindowWillUpdate": {}, - "mac:WindowWillUpdateAlpha": {}, - "mac:WindowWillUpdateCollectionBehavior": {}, - "mac:WindowWillUpdateCollectionProperties": {}, - "mac:WindowWillUpdateShadow": {}, - "mac:WindowWillUpdateTitle": {}, - "mac:WindowWillUpdateToolbar": {}, - "mac:WindowWillUpdateVisibility": {}, - "mac:WindowWillUseStandardFrame": {}, - "mac:WindowZoomIn": {}, - "mac:WindowZoomOut": {}, - "mac:WindowZoomReset": {}, - "windows:APMPowerSettingChange": {}, - "windows:APMPowerStatusChange": {}, - "windows:APMResumeAutomatic": {}, - "windows:APMResumeSuspend": {}, - "windows:APMSuspend": {}, - "windows:ApplicationStarted": {}, - "windows:SystemThemeChanged": {}, - "windows:WebViewNavigationCompleted": {}, - "windows:WindowActive": {}, - "windows:WindowBackgroundErase": {}, - "windows:WindowClickActive": {}, - "windows:WindowClosing": {}, - "windows:WindowDidMove": {}, - "windows:WindowDidResize": {}, - "windows:WindowDPIChanged": {}, - "windows:WindowDragDrop": {}, - "windows:WindowDragEnter": {}, - "windows:WindowDragLeave": {}, - "windows:WindowDragOver": {}, - "windows:WindowEndMove": {}, - "windows:WindowEndResize": {}, - "windows:WindowFullscreen": {}, - "windows:WindowHide": {}, - "windows:WindowInactive": {}, - "windows:WindowKeyDown": {}, - "windows:WindowKeyUp": {}, - "windows:WindowKillFocus": {}, - "windows:WindowNonClientHit": {}, - "windows:WindowNonClientMouseDown": {}, - "windows:WindowNonClientMouseLeave": {}, - "windows:WindowNonClientMouseMove": {}, - "windows:WindowNonClientMouseUp": {}, - "windows:WindowPaint": {}, - "windows:WindowRestore": {}, - "windows:WindowSetFocus": {}, - "windows:WindowShow": {}, - "windows:WindowStartMove": {}, - "windows:WindowStartResize": {}, - "windows:WindowUnFullscreen": {}, - "windows:WindowZOrderChanged": {}, - "windows:WindowMinimise": {}, - "windows:WindowUnMinimise": {}, - "windows:WindowMaximise": {}, - "windows:WindowUnMaximise": {}, + "mac:WebViewDidStartProvisionalNavigation": {}, + "mac:WindowDidBecomeKey": {}, + "mac:WindowDidBecomeMain": {}, + "mac:WindowDidBeginSheet": {}, + "mac:WindowDidChangeAlpha": {}, + "mac:WindowDidChangeBackingLocation": {}, + "mac:WindowDidChangeBackingProperties": {}, + "mac:WindowDidChangeCollectionBehavior": {}, + "mac:WindowDidChangeEffectiveAppearance": {}, + "mac:WindowDidChangeOcclusionState": {}, + "mac:WindowDidChangeOrderingMode": {}, + "mac:WindowDidChangeScreen": {}, + "mac:WindowDidChangeScreenParameters": {}, + "mac:WindowDidChangeScreenProfile": {}, + "mac:WindowDidChangeScreenSpace": {}, + "mac:WindowDidChangeScreenSpaceProperties": {}, + "mac:WindowDidChangeSharingType": {}, + "mac:WindowDidChangeSpace": {}, + "mac:WindowDidChangeSpaceOrderingMode": {}, + "mac:WindowDidChangeTitle": {}, + "mac:WindowDidChangeToolbar": {}, + "mac:WindowDidDeminiaturize": {}, + "mac:WindowDidEndSheet": {}, + "mac:WindowDidEnterFullScreen": {}, + "mac:WindowDidEnterVersionBrowser": {}, + "mac:WindowDidExitFullScreen": {}, + "mac:WindowDidExitVersionBrowser": {}, + "mac:WindowDidExpose": {}, + "mac:WindowDidFocus": {}, + "mac:WindowDidMiniaturize": {}, + "mac:WindowDidMove": {}, + "mac:WindowDidOrderOffScreen": {}, + "mac:WindowDidOrderOnScreen": {}, + "mac:WindowDidResignKey": {}, + "mac:WindowDidResignMain": {}, + "mac:WindowDidResize": {}, + "mac:WindowDidUpdate": {}, + "mac:WindowDidUpdateAlpha": {}, + "mac:WindowDidUpdateCollectionBehavior": {}, + "mac:WindowDidUpdateCollectionProperties": {}, + "mac:WindowDidUpdateShadow": {}, + "mac:WindowDidUpdateTitle": {}, + "mac:WindowDidUpdateToolbar": {}, + "mac:WindowDidZoom": {}, + "mac:WindowFileDraggingEntered": {}, + "mac:WindowFileDraggingExited": {}, + "mac:WindowFileDraggingPerformed": {}, + "mac:WindowHide": {}, + "mac:WindowMaximise": {}, + "mac:WindowUnMaximise": {}, + "mac:WindowMinimise": {}, + "mac:WindowUnMinimise": {}, + "mac:WindowShouldClose": {}, + "mac:WindowShow": {}, + "mac:WindowWillBecomeKey": {}, + "mac:WindowWillBecomeMain": {}, + "mac:WindowWillBeginSheet": {}, + "mac:WindowWillChangeOrderingMode": {}, + "mac:WindowWillClose": {}, + "mac:WindowWillDeminiaturize": {}, + "mac:WindowWillEnterFullScreen": {}, + "mac:WindowWillEnterVersionBrowser": {}, + "mac:WindowWillExitFullScreen": {}, + "mac:WindowWillExitVersionBrowser": {}, + "mac:WindowWillFocus": {}, + "mac:WindowWillMiniaturize": {}, + "mac:WindowWillMove": {}, + "mac:WindowWillOrderOffScreen": {}, + "mac:WindowWillOrderOnScreen": {}, + "mac:WindowWillResignMain": {}, + "mac:WindowWillResize": {}, + "mac:WindowWillUnfocus": {}, + "mac:WindowWillUpdate": {}, + "mac:WindowWillUpdateAlpha": {}, + "mac:WindowWillUpdateCollectionBehavior": {}, + "mac:WindowWillUpdateCollectionProperties": {}, + "mac:WindowWillUpdateShadow": {}, + "mac:WindowWillUpdateTitle": {}, + "mac:WindowWillUpdateToolbar": {}, + "mac:WindowWillUpdateVisibility": {}, + "mac:WindowWillUseStandardFrame": {}, + "mac:WindowZoomIn": {}, + "mac:WindowZoomOut": {}, + "mac:WindowZoomReset": {}, + "windows:APMPowerSettingChange": {}, + "windows:APMPowerStatusChange": {}, + "windows:APMResumeAutomatic": {}, + "windows:APMResumeSuspend": {}, + "windows:APMSuspend": {}, + "windows:ApplicationStarted": {}, + "windows:SystemThemeChanged": {}, + "windows:WebViewNavigationCompleted": {}, + "windows:WindowActive": {}, + "windows:WindowBackgroundErase": {}, + "windows:WindowClickActive": {}, + "windows:WindowClosing": {}, + "windows:WindowDidMove": {}, + "windows:WindowDidResize": {}, + "windows:WindowDPIChanged": {}, + "windows:WindowDragDrop": {}, + "windows:WindowDragEnter": {}, + "windows:WindowDragLeave": {}, + "windows:WindowDragOver": {}, + "windows:WindowEndMove": {}, + "windows:WindowEndResize": {}, + "windows:WindowFullscreen": {}, + "windows:WindowHide": {}, + "windows:WindowInactive": {}, + "windows:WindowKeyDown": {}, + "windows:WindowKeyUp": {}, + "windows:WindowKillFocus": {}, + "windows:WindowNonClientHit": {}, + "windows:WindowNonClientMouseDown": {}, + "windows:WindowNonClientMouseLeave": {}, + "windows:WindowNonClientMouseMove": {}, + "windows:WindowNonClientMouseUp": {}, + "windows:WindowPaint": {}, + "windows:WindowRestore": {}, + "windows:WindowSetFocus": {}, + "windows:WindowShow": {}, + "windows:WindowStartMove": {}, + "windows:WindowStartResize": {}, + "windows:WindowUnFullscreen": {}, + "windows:WindowZOrderChanged": {}, + "windows:WindowMinimise": {}, + "windows:WindowUnMinimise": {}, + "windows:WindowMaximise": {}, + "windows:WindowUnMaximise": {}, } diff --git a/v3/pkg/services/kvstore/kvstore.go b/v3/pkg/services/kvstore/kvstore.go index 899dd86ec..4d2f511b0 100644 --- a/v3/pkg/services/kvstore/kvstore.go +++ b/v3/pkg/services/kvstore/kvstore.go @@ -2,10 +2,10 @@ package kvstore import ( "context" - "encoding/json" "os" "sync" + "encoding/json" "github.com/pkg/errors" "github.com/wailsapp/wails/v3/pkg/application" ) diff --git a/v3/pkg/services/notifications/notifications_darwin.go b/v3/pkg/services/notifications/notifications_darwin.go index c497e8c35..b9695b3f9 100644 --- a/v3/pkg/services/notifications/notifications_darwin.go +++ b/v3/pkg/services/notifications/notifications_darwin.go @@ -15,12 +15,13 @@ package notifications import "C" import ( "context" - "encoding/json" "fmt" "sync" "time" "unsafe" + "encoding/json" + "github.com/wailsapp/wails/v3/pkg/application" ) diff --git a/v3/pkg/services/notifications/notifications_linux.go b/v3/pkg/services/notifications/notifications_linux.go index 8dc9765cd..23e21433e 100644 --- a/v3/pkg/services/notifications/notifications_linux.go +++ b/v3/pkg/services/notifications/notifications_linux.go @@ -4,12 +4,13 @@ package notifications import ( "context" - "encoding/json" "fmt" "os" "path/filepath" "sync" + "encoding/json" + "github.com/godbus/dbus/v5" "github.com/wailsapp/wails/v3/pkg/application" ) diff --git a/v3/pkg/services/notifications/notifications_windows.go b/v3/pkg/services/notifications/notifications_windows.go index 1a1a6dd85..b1063dbf8 100644 --- a/v3/pkg/services/notifications/notifications_windows.go +++ b/v3/pkg/services/notifications/notifications_windows.go @@ -5,13 +5,14 @@ package notifications import ( "context" "encoding/base64" - "encoding/json" "fmt" "os" "path/filepath" "sync" _ "unsafe" + "encoding/json" + "git.sr.ht/~jackmordaunt/go-toast/v2" wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast" "github.com/google/uuid" diff --git a/v3/pkg/w32/shcore.go b/v3/pkg/w32/shcore.go index f2d6c5516..f83062eb3 100644 --- a/v3/pkg/w32/shcore.go +++ b/v3/pkg/w32/shcore.go @@ -12,9 +12,26 @@ var ( modshcore = syscall.NewLazyDLL("shcore.dll") procGetDpiForMonitor = modshcore.NewProc("GetDpiForMonitor") + procGetProcessDpiAwareness = modshcore.NewProc("GetProcessDpiAwareness") procSetProcessDpiAwareness = modshcore.NewProc("SetProcessDpiAwareness") ) +func HasGetProcessDpiAwarenessFunc() bool { + err := procGetProcessDpiAwareness.Find() + return err == nil +} + +// GetProcessDpiAwareness retrieves the DPI awareness of the current process. +// Returns one of: PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE, or PROCESS_PER_MONITOR_DPI_AWARE. +func GetProcessDpiAwareness() (uint, error) { + var awareness uint + status, _, err := procGetProcessDpiAwareness.Call(0, uintptr(unsafe.Pointer(&awareness))) + if status != S_OK { + return 0, fmt.Errorf("GetProcessDpiAwareness failed: %v", err) + } + return awareness, nil +} + func HasSetProcessDpiAwarenessFunc() bool { err := procSetProcessDpiAwareness.Find() return err == nil diff --git a/v3/pkg/w32/window.go b/v3/pkg/w32/window.go index 3e488a49c..c26ec414d 100644 --- a/v3/pkg/w32/window.go +++ b/v3/pkg/w32/window.go @@ -4,7 +4,6 @@ package w32 import ( "fmt" - "github.com/samber/lo" "strconv" "strings" "sync" @@ -180,17 +179,33 @@ func MustStringToUTF16Ptr(input string) *uint16 { return result } +// MustStringToUTF16uintptr converts input to a NUL-terminated UTF-16 buffer and returns its pointer as a uintptr. +// It first removes any internal NUL characters from input, then converts the result to a UTF-16 pointer. +// The function panics if the conversion fails. func MustStringToUTF16uintptr(input string) uintptr { input = stripNulls(input) - ret := lo.Must(syscall.UTF16PtrFromString(input)) + ret, err := syscall.UTF16PtrFromString(input) + if err != nil { + panic(err) + } return uintptr(unsafe.Pointer(ret)) } +// MustStringToUTF16 converts s to UTF-16 encoding, stripping any embedded NULs and panicking on error. +// +// The returned slice is suitable for Windows API calls that expect a UTF-16 encoded string. func MustStringToUTF16(input string) []uint16 { input = stripNulls(input) - return lo.Must(syscall.UTF16FromString(input)) + ret, err := syscall.UTF16FromString(input) + if err != nil { + panic(err) + } + return ret } +// StringToUTF16 converts input to a UTF-16 encoded, NUL-terminated []uint16 suitable for Windows API calls. +// It first removes any embedded NUL ('\x00') characters from input. The returned slice is NUL-terminated; +// an error is returned if the conversion fails. func StringToUTF16(input string) ([]uint16, error) { input = stripNulls(input) return syscall.UTF16FromString(input) @@ -359,4 +374,4 @@ func SendMessageToWindow(hwnd HWND, msg string) { func GetMenu(hwnd HWND) HMENU { ret, _, _ := getMenuProc.Call(hwnd) return ret -} +} \ No newline at end of file