From 6b8c8d495f48bb0b6584270f5c13d9af4e2096ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Dec 2025 02:41:03 +0000 Subject: [PATCH 01/40] chore(v3): bump to v3.0.0-alpha.49 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index fe2422da3..e93284cf9 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.49 - 2025-12-18 + +## Changed +- Update the `Frontend Runtime` documentation by adding more options for generating frontend bindings by @ndianabasi + ## v3.0.0-alpha.48 - 2025-12-16 ## Added diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index bd2039680..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -19,7 +19,6 @@ After processing, the content will be moved to the main changelog and this file ## Changed -- Update the `Frontend Runtime` documentation by adding more options for generating frontend bindings by @ndianabasi ## Fixed diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index f65f1f889..1840432d8 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.48 \ No newline at end of file +v3.0.0-alpha.49 \ No newline at end of file From 618489800cb60881d152a881558fcecd179a8d99 Mon Sep 17 00:00:00 2001 From: ruhuang Date: Sat, 20 Dec 2025 12:50:32 +0800 Subject: [PATCH 02/40] docs: add examples for window X/Y coordinate options (#4817) * docs: add examples for window X/Y coordinate options * Update UNRELEASED_CHANGELOG.md --- docs/src/content/docs/features/windows/options.mdx | 12 +++++++++++- v3/UNRELEASED_CHANGELOG.md | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 0b278e651..521d50b01 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -158,7 +158,17 @@ Y: 100, // 100px from top edge - Positive X goes right - Positive Y goes down -**Best practice:** Use `Center()` instead: +**Example:** + +```go +settings := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Name: "coordinate-window", + InitialPosition: application.WindowXY, // use coordinate system + X: 100, + Y: 100, +}) + +**Best practice:** Use `Center()` to center the window: ```go window := app.NewWebviewWindow() diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..0123dbe00 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,6 +20,7 @@ After processing, the content will be moved to the main changelog and this file ## Changed +- Update the documentation for Window `X/Y` options @ruhuang2001 ## Fixed From 0271c9e10cd76d122795bcd1a8d3360a6ae5d794 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Dec 2025 02:47:21 +0000 Subject: [PATCH 03/40] chore(v3): bump to v3.0.0-alpha.50 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index e93284cf9..2f826dab1 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.50 - 2025-12-21 + +## Changed +- Update the documentation for Window `X/Y` options @ruhuang2001 + ## v3.0.0-alpha.49 - 2025-12-18 ## Changed diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 0123dbe00..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,7 +20,6 @@ After processing, the content will be moved to the main changelog and this file ## Changed -- Update the documentation for Window `X/Y` options @ruhuang2001 ## Fixed diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 1840432d8..f24592cb0 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.49 \ No newline at end of file +v3.0.0-alpha.50 \ No newline at end of file From 9174be76f293fb0f88298c7363ec2ff689fa8a7c Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Tue, 23 Dec 2025 06:33:09 +1100 Subject: [PATCH 04/40] Prevent app crashing when calling systray.Run() before app.Run() (#4826) --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/systemtray.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..98c0451dd 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Prevent app crashing when calling systray.Run() before app.Run() by @leaanthony ## Deprecated 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 { From e34c172f0cedd6d9805064507d4e86ab6afb5005 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 23 Dec 2025 02:43:45 +0000 Subject: [PATCH 05/40] chore(v3): bump to v3.0.0-alpha.51 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 2f826dab1..d36feed8f 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 98c0451dd..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,7 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## Fixed -- Prevent app crashing when calling systray.Run() before app.Run() by @leaanthony ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index f24592cb0..541143cbe 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.51 \ No newline at end of file From f30c888c5bce9b18f0f08c3f5212baf4431df634 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 25 Dec 2025 19:35:44 +1100 Subject: [PATCH 06/40] fix(windows): Prevent systray menu crash on rapid clicks (#4151) (#4812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add guard against concurrent TrackPopupMenuEx calls using atomic.Bool to prevent race condition when user clicks systray icon repeatedly. Change TrackPopupMenuEx failure from fatal to debug log with early return so the application gracefully handles failed menu display attempts instead of crashing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Vibe Kanban Co-authored-by: Claude --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/popupmenu_windows.go | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..c6bcc38a2 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix Windows systray menu crash when clicking icon repeatedly by adding guard against concurrent TrackPopupMenuEx calls (#4151) by @leaanthony ## Deprecated 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 { From 3b055efcae822d5cdd405267801c75ae0b37dd35 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 02:43:00 +0000 Subject: [PATCH 07/40] chore(v3): bump to v3.0.0-alpha.52 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index d36feed8f..f23edf746 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index c6bcc38a2..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,7 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## Fixed -- Fix Windows systray menu crash when clicking icon repeatedly by adding guard against concurrent TrackPopupMenuEx calls (#4151) by @leaanthony ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 541143cbe..11d2c7943 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.51 \ No newline at end of file +v3.0.0-alpha.52 \ No newline at end of file From ab33eb594e111bbb2b7cf2ab088746210ad9840c Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 27 Dec 2025 09:52:26 +1100 Subject: [PATCH 08/40] docs: fix custom protocol association documentation (#4825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: fix custom protocol association documentation The documentation was incorrectly referencing `wails.json` with JSON format when the actual configuration file is `build/config.yml` using YAML format. Changes: - Update config file reference from `wails.json` to `build/config.yml` - Change format from JSON to YAML in code examples - Fix structure: `protocols` is at root level, not nested under `info` - Correct template variable references from `{{.Info.Protocols}}` to `{{.Protocols}}` - Update Info.plist example to show actual generated format (`wails.com.scheme`) - Add note about running `wails3 task common:update:build-assets` after changes - Clean up redundant file path references in platform-specific sections 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * docs: consolidate custom protocol docs and add Universal Links - Remove duplicate custom-protocol-association.mdx - Add Universal Links section to macOS tab - Add Web-to-App Linking section to Windows tab - Keep the more comprehensive distribution/custom-protocols.mdx Addresses review comment about duplicate documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * feat(windows): add custom protocol support to MSIX packaging - Add uap3 namespace and protocol extension to MSIX template - Protocols defined in build/config.yml are now automatically registered when building MSIX packages - Update docs with MSIX section and clarify Web-to-App linking requires manual manifest configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude --- .../guides/custom-protocol-association.mdx | 216 ------------------ .../guides/distribution/custom-protocols.mdx | 73 +++++- v3/UNRELEASED_CHANGELOG.md | 2 + .../windows/msix/app_manifest.xml.tmpl | 11 +- 4 files changed, 84 insertions(+), 218 deletions(-) delete mode 100644 docs/src/content/docs/guides/custom-protocol-association.mdx 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..760e8e2d8 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`. @@ -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. + diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..f9ed39c39 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,12 +17,14 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add custom protocol support to MSIX packaging by @leaanthony ## Changed ## Fixed +- Consolidate custom protocol documentation and add Universal Links sections by @leaanthony ## Deprecated 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}} From 2604ecc0f8bc60eb03868937e82e10b12c718b03 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 27 Dec 2025 09:53:24 +1100 Subject: [PATCH 09/40] test(v3): add comprehensive unit tests for pkg/application (#4827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(v3): add comprehensive unit tests for pkg/application Add 11 new test files to improve test coverage of the pkg/application package from 13.6% to 17.7%. New test files: - context_test.go: Context struct operations - services_test.go: Service management and lifecycle - parameter_test.go: Parameter and CallError types - dialogs_test.go: Dialog utilities and button methods - webview_window_options_test.go: Window options and constants - application_options_test.go: ChainMiddleware and app config - keys_test.go: Keyboard accelerator parsing - single_instance_test.go: Single instance management and encryption - menuitem_internal_test.go: Menu item internal functions - menu_internal_test.go: Menu internal functions - screenmanager_internal_test.go: Screen geometry and transformations The 40% target was not fully achievable because ~50% of the codebase is platform-specific code that can only be tested on respective platforms. Tests focus on pure Go logic, utility functions, and data structures that can be tested cross-platform. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: address CodeRabbit review comments - dialogs_test.go: improve ID recycling test to verify either recycled ID (id3 == id1) or new unique ID (id3 > id2) - keys_test.go: make accelerator String() tests platform-agnostic by checking suffix patterns rather than exact platform-specific strings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: normalize temp dir path for macOS compatibility in test os.TempDir() returns a trailing slash on macOS, causing path comparison to fail. Use filepath.Clean() to normalize both paths. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * chore: remove REVIEW.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * chore: simplify changelog entry 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- v3/UNRELEASED_CHANGELOG.md | 1 + .../application/application_options_test.go | 301 ++++++++++++ v3/pkg/application/context_test.go | 120 +++++ v3/pkg/application/dialogs_test.go | 223 +++++++++ v3/pkg/application/keys_test.go | 293 ++++++++++++ v3/pkg/application/menu_internal_test.go | 403 ++++++++++++++++ v3/pkg/application/menuitem_internal_test.go | 413 +++++++++++++++++ v3/pkg/application/parameter_test.go | 118 +++++ .../screenmanager_internal_test.go | 435 ++++++++++++++++++ v3/pkg/application/services_test.go | 203 ++++++++ v3/pkg/application/single_instance_test.go | 229 +++++++++ .../webview_window_options_test.go | 388 ++++++++++++++++ 12 files changed, 3127 insertions(+) create mode 100644 v3/pkg/application/application_options_test.go create mode 100644 v3/pkg/application/context_test.go create mode 100644 v3/pkg/application/dialogs_test.go create mode 100644 v3/pkg/application/keys_test.go create mode 100644 v3/pkg/application/menu_internal_test.go create mode 100644 v3/pkg/application/menuitem_internal_test.go create mode 100644 v3/pkg/application/parameter_test.go create mode 100644 v3/pkg/application/screenmanager_internal_test.go create mode 100644 v3/pkg/application/services_test.go create mode 100644 v3/pkg/application/single_instance_test.go create mode 100644 v3/pkg/application/webview_window_options_test.go diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index f9ed39c39..3aad74272 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add unit tests for pkg/application by @leaanthony - Add custom protocol support to MSIX packaging by @leaanthony ## Changed 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/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/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/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/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/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/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/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_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/webview_window_options_test.go b/v3/pkg/application/webview_window_options_test.go new file mode 100644 index 000000000..309f3f542 --- /dev/null +++ b/v3/pkg/application/webview_window_options_test.go @@ -0,0 +1,388 @@ +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 TestDragEffect_Constants(t *testing.T) { + if DragEffectNone != 1 { + t.Error("DragEffectNone should be 1") + } + if DragEffectCopy != 2 { + t.Error("DragEffectCopy should be 2") + } + if DragEffectMove != 3 { + t.Error("DragEffectMove should be 3") + } + if DragEffectLink != 4 { + t.Error("DragEffectLink 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") + } +} From 9ebdf229bb4ebb6189843f24d95733a4ee1debd2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 02:40:47 +0000 Subject: [PATCH 10/40] chore(v3): bump to v3.0.0-alpha.53 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 9 +++++++++ v3/UNRELEASED_CHANGELOG.md | 3 --- v3/internal/version/version.txt | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index f23edf746..7c2938c71 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 3aad74272..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,15 +17,12 @@ After processing, the content will be moved to the main changelog and this file ## Added -- Add unit tests for pkg/application by @leaanthony -- Add custom protocol support to MSIX packaging by @leaanthony ## Changed ## Fixed -- Consolidate custom protocol documentation and add Universal Links sections by @leaanthony ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 11d2c7943..1c9bf46cc 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.52 \ No newline at end of file +v3.0.0-alpha.53 \ No newline at end of file From 7bc6501a846cd5c78918198ee38c9794a37773bd Mon Sep 17 00:00:00 2001 From: Dzmitryi Hilimovich Date: Mon, 29 Dec 2025 01:05:17 +0100 Subject: [PATCH 11/40] [v3] Debug printf statements left in webview_window_darwin.m (#4834) * Clean up debug output in webView URL scheme handler Removed debug print statements for CSS requests in web view. * Add changelog entry for #4834 * Fix Changelog --------- Co-authored-by: Lea Anthony --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/webview_window_darwin.m | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..21cde115e 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -29,6 +29,7 @@ After processing, the content will be moved to the main changelog and this file ## Removed +- Remove debug printf statements from Darwin URL scheme handler (#4834) ## Security diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 34c0ef019..752e3675a 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -373,14 +373,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); From 0de429b8fc8332ea4ab278b52ae95f6ebbcb88b6 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 29 Dec 2025 11:07:37 +1100 Subject: [PATCH 12/40] feat(macos): add CollectionBehavior option to MacWindow (#4799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(macos): add CollectionBehavior option to MacWindow (#4756) Add configurable NSWindowCollectionBehavior support for macOS windows, allowing control over window behavior across Spaces and fullscreen. New options include: - MacWindowCollectionBehaviorCanJoinAllSpaces - MacWindowCollectionBehaviorFullScreenAuxiliary - MacWindowCollectionBehaviorMoveToActiveSpace - And more... This enables building Spotlight-like apps that appear on all Spaces or overlay fullscreen applications. Closes #4756 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Potential fix for code scanning alert no. 140: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 139: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * feat(examples): add spotlight example for CollectionBehavior Demonstrates creating a Spotlight-like launcher window that: - Appears on all macOS Spaces - Floats above other windows - Uses accessory activation policy (no Dock icon) - Has frameless translucent design 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat(macos): support bitwise OR for CollectionBehavior options Update CollectionBehavior to use actual NSWindowCollectionBehavior bitmask values, allowing multiple behaviors to be combined: ```go CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | application.MacWindowCollectionBehaviorFullScreenAuxiliary, ``` Changes: - Update Go constants to use actual bitmask values (1<<0, 1<<1, etc.) - Simplify C function to pass through combined bitmask directly - Add ParticipatesInCycle, IgnoresCycle, FullScreenDisallowsTiling options - Update documentation with combined behavior examples - Update spotlight example to demonstrate combining behaviors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/automated-releases.yml | 1 + .github/workflows/test-nightly-releases.yml | 2 + .../content/docs/features/windows/basics.mdx | 19 ++- .../content/docs/features/windows/options.mdx | 90 ++++++++--- v3/UNRELEASED_CHANGELOG.md | 1 + v3/examples/spotlight/README.md | 66 ++++++++ v3/examples/spotlight/main.go | 142 ++++++++++++++++++ v3/pkg/application/webview_window_darwin.go | 24 ++- v3/pkg/application/webview_window_options.go | 37 +++++ 9 files changed, 352 insertions(+), 30 deletions(-) create mode 100644 v3/examples/spotlight/README.md create mode 100644 v3/examples/spotlight/main.go 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/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx index 007c62c52..3137111ac 100644 --- a/docs/src/content/docs/features/windows/basics.mdx +++ b/docs/src/content/docs/features/windows/basics.mdx @@ -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, + 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). diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 521d50b01..93a468120 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -665,39 +665,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, }, ``` diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 21cde115e..a3289242e 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add `CollectionBehavior` option to `MacWindow` for controlling window behavior across macOS Spaces and fullscreen (#4756) by @leaanthony ## Changed 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/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index b8d3736d3..fc81974bf 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 @@ -1162,6 +1175,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 @@ -1260,6 +1277,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) diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 4c9c0f57a..8ca629559 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -489,6 +489,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 +509,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 From cee729dd85119d18df04eba27e087b08da8613e1 Mon Sep 17 00:00:00 2001 From: wux1an <87492350+wux1an@users.noreply.github.com> Date: Mon, 29 Dec 2025 09:14:25 +0800 Subject: [PATCH 13/40] fix(v3): resolve some issues in template code when compiling macOS program on Linux (#4832) * fix(v3): resolve Docker error "undefined symbol: ___ubsan_handle_xxxxxxx" when running 'wails3 build GOOS=darwin GOARCH=arm64' on Linux * fix(v3): correct command argument error when executing 'build:universal:lipo:go' task on Linux * docs(v3): update changelog * Fix CHANGELOG * Fix CHANGELOG --------- Co-authored-by: Lea Anthony --- v3/UNRELEASED_CHANGELOG.md | 2 ++ v3/internal/commands/build_assets/darwin/Taskfile.yml | 2 +- v3/internal/commands/build_assets/docker/Dockerfile.cross | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index a3289242e..f91d072e3 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -24,6 +24,8 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Deprecated diff --git a/v3/internal/commands/build_assets/darwin/Taskfile.yml b/v3/internal/commands/build_assets/darwin/Taskfile.yml index 3458c96ef..054da1a63 100644 --- a/v3/internal/commands/build_assets/darwin/Taskfile.yml +++ b/v3/internal/commands/build_assets/darwin/Taskfile.yml @@ -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: 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 From 3256041d8b0a3cd4559b37c83e06ced971049047 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Dec 2025 02:52:26 +0000 Subject: [PATCH 14/40] chore(v3): bump to v3.0.0-alpha.54 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 12 ++++++++++++ v3/UNRELEASED_CHANGELOG.md | 4 ---- v3/internal/version/version.txt | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 7c2938c71..d0edd228a 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index f91d072e3..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,22 +17,18 @@ After processing, the content will be moved to the main changelog and this file ## Added -- Add `CollectionBehavior` option to `MacWindow` for controlling window behavior across macOS Spaces and fullscreen (#4756) by @leaanthony ## Changed ## 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 ## Deprecated ## Removed -- Remove debug printf statements from Darwin URL scheme handler (#4834) ## Security diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 1c9bf46cc..3ca87f3ac 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.53 \ No newline at end of file +v3.0.0-alpha.54 \ No newline at end of file From a06d55804cfb150db9aa107a249e5d1e8f6e1549 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Fri, 2 Jan 2026 07:03:36 +1100 Subject: [PATCH 15/40] perf(v3): optimize JSON processing and reduce allocations in hot paths (#4843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(v3): optimize JSON processing and reduce allocations in hot paths - Switch to goccy/go-json for method binding, events, and HTTP transport (21-63% faster, 40-60% less memory for method calls) - Optimize BoundMethod struct layout to reduce padding (144 -> 136 bytes) - Cache isVariadic flag at registration to avoid reflect call per invocation - Use stack-allocated buffer for method arguments (<=8 args) - Optimize result collection to avoid slice allocation for single return values - Use sync.Map for MIME cache to improve concurrent read performance - Use buffer pool for HTTP transport request body reading - Lazily allocate CloseNotify channel in content type sniffer - Remove debug CSS logging from asset server - Add comprehensive benchmark tests (build tag: bench) Performance improvements for BoundMethod.Call: - SimpleCall: 1290ns -> 930ns (28% faster), 240B -> 80B (67% less memory) - ComplexCall: 10500ns -> 3900ns (63% faster), 1192B -> 1020B (14% less) - VariadicCall: 3460ns -> 1600ns (54% faster), 512B -> 289B (44% less) * perf(v3): add max size limit to buffer pool to prevent memory bloat Buffers larger than 512KB are not returned to the pool, allowing GC to reclaim memory after large requests (e.g., base64 encoded images). * perf(v3): remove mimetype library dependency, saving ~208KB binary size - Replace github.com/wailsapp/mimetype with expanded extension map + stdlib - Expand MIME type map from 16 to 50+ common web formats (fonts, audio, video, etc.) - Add comprehensive test suite validating MIME detection for all web formats - Use http.DetectContentType as fallback for unknown extensions - Actual binary size reduction: 1.2MB (11MB -> 9.8MB in test app) * perf(v3): migrate all runtime code to goccy/go-json Migrate remaining encoding/json usages to goccy/go-json in: - pkg/application (android, darwin, ios, single_instance, webview_window) - pkg/services (kvstore, notifications on all platforms) - internal/assetserver/webview (request/response handling) - internal/runtime and internal/capabilities Note: encoding/json (110KB) remains in binary because: 1. goccy/go-json imports it for interface compatibility (json.Marshaler, etc.) 2. log/slog (stdlib) uses it for JSON output The performance benefit is in the hot paths which now use the faster library. * perf(v3): replace gopkg.in/ini.v1 with minimal .desktop file parser Replace the gopkg.in/ini.v1 dependency with a purpose-built minimal parser for Linux .desktop files. The new parser: - Only extracts the Exec key from [Desktop Entry] section (all we need) - Follows the Desktop Entry Specification - Has comprehensive test coverage (40 tests) including: - All major file managers (Nautilus, Dolphin, Thunar, PCManFM, Caja, Nemo) - Edge cases (UTF-8, special chars, comments, empty files, etc.) - Buffer limit handling Binary size reduction: 45KB (10.22MB -> 10.18MB) * perf(v3): remove samber/lo from runtime code, saving ~310KB binary size Replace samber/lo with Go 1.21+ stdlib slices package and minimal internal helpers in all runtime code paths. This removes 80 transitive dependencies from the production binary. Changes: - Create internal/sliceutil package with Unique and FindMapKey helpers - Replace lo.Without with slices.DeleteFunc in event handling - Replace lo.Ternary with inline if/else in Windows code - Replace lo.Uniq with sliceutil.Unique for feature flags - Replace lo.FindKey with sliceutil.FindMapKey for method aliases - Replace lo.Filter with slices.DeleteFunc in event listeners - Replace lo.Must with inline panic in w32 package Binary size: 10.18MB -> 9.87MB (~310KB / 3% reduction) Note: CLI tools still use samber/lo since they don't affect production binary size. The application_debug.go file also retains lo usage as it has //go:build !production tag. * fix: address CodeRabbit review comments - Use application/x-typescript MIME type (not IANA-registered text/typescript) - Fix potential panic in mimetype_stdlib_test.go for short MIME strings - Use cached isVariadic flag in bindings_optimized_bench_test.go * fix: initialize goccy/go-json decoder early to fix Windows test failure On Windows, goccy/go-json's type address calculation can fail if the decoder is first invoked during test execution rather than at init time. Force early initialization by unmarshaling a []int during package init. See: https://github.com/goccy/go-json/issues/474 * 📝 Add docstrings to `v3/performance-improvements` (#4844) * fix: initialize goccy/go-json decoder early to fix Windows test failure On Windows, goccy/go-json's type address calculation can fail if the decoder is first invoked during test execution rather than at init time. Force early initialization by unmarshaling a []int during package init. See: https://github.com/goccy/go-json/issues/474 * 📝 Add docstrings to `v3/performance-improvements` Docstrings generation was requested by @leaanthony. * https://github.com/wailsapp/wails/pull/4843#issuecomment-3703472562 The following files were modified: * `v3/internal/assetserver/common.go` * `v3/internal/assetserver/content_type_sniffer.go` * `v3/internal/assetserver/mimecache.go` * `v3/internal/fileexplorer/desktopfile.go` * `v3/internal/fileexplorer/fileexplorer_linux.go` * `v3/internal/sliceutil/sliceutil.go` * `v3/pkg/application/application_ios.go` * `v3/pkg/application/bindings.go` * `v3/pkg/application/ios_runtime_ios.go` * `v3/pkg/w32/window.go` --------- Co-authored-by: Lea Anthony Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- v3/UNRELEASED_CHANGELOG.md | 12 + v3/go.mod | 17 +- v3/go.sum | 34 +- .../assetserver/assetserver_bench_test.go | 242 ++++++++ v3/internal/assetserver/common.go | 29 +- .../assetserver/content_type_sniffer.go | 18 +- v3/internal/assetserver/mimecache.go | 125 ++-- .../assetserver/mimetype_stdlib_test.go | 277 +++++++++ .../assetserver/webview/request_darwin.go | 3 +- .../assetserver/webview/request_ios.go | 3 +- .../webview/responsewriter_darwin.go | 3 +- .../assetserver/webview/responsewriter_ios.go | 3 +- v3/internal/capabilities/capabilities.go | 2 +- v3/internal/fileexplorer/desktopfile.go | 96 ++++ v3/internal/fileexplorer/desktopfile_test.go | 532 ++++++++++++++++++ .../fileexplorer/fileexplorer_linux.go | 15 +- v3/internal/runtime/runtime.go | 3 +- v3/internal/sliceutil/sliceutil.go | 39 ++ v3/internal/sliceutil/sliceutil_test.go | 172 ++++++ v3/pkg/application/application_android.go | 3 +- .../application/application_android_nocgo.go | 3 +- v3/pkg/application/application_darwin.go | 4 +- v3/pkg/application/application_ios.go | 37 +- v3/pkg/application/bindings.go | 171 ++++-- v3/pkg/application/bindings_bench_test.go | 506 +++++++++++++++++ .../bindings_optimized_bench_test.go | 469 +++++++++++++++ v3/pkg/application/event_manager.go | 9 +- v3/pkg/application/events.go | 17 +- v3/pkg/application/events_bench_test.go | 380 +++++++++++++ v3/pkg/application/ios_runtime_ios.go | 36 +- v3/pkg/application/json_libs_bench_test.go | 310 ++++++++++ v3/pkg/application/json_v2_bench_test.go | 270 +++++++++ v3/pkg/application/logger_ios.go | 9 +- v3/pkg/application/messageprocessor_args.go | 3 +- v3/pkg/application/messageprocessor_call.go | 3 +- v3/pkg/application/messageprocessor_events.go | 2 +- v3/pkg/application/single_instance.go | 3 +- v3/pkg/application/systemtray_bench_test.go | 374 ++++++++++++ v3/pkg/application/transport_http.go | 30 +- v3/pkg/application/webview_window.go | 13 +- v3/pkg/application/webview_window_windows.go | 27 +- v3/pkg/application/window_bench_test.go | 442 +++++++++++++++ v3/pkg/services/kvstore/kvstore.go | 2 +- .../notifications/notifications_darwin.go | 3 +- .../notifications/notifications_linux.go | 3 +- .../notifications/notifications_windows.go | 3 +- v3/pkg/w32/window.go | 23 +- 47 files changed, 4580 insertions(+), 200 deletions(-) create mode 100644 v3/internal/assetserver/assetserver_bench_test.go create mode 100644 v3/internal/assetserver/mimetype_stdlib_test.go create mode 100644 v3/internal/fileexplorer/desktopfile.go create mode 100644 v3/internal/fileexplorer/desktopfile_test.go create mode 100644 v3/internal/sliceutil/sliceutil.go create mode 100644 v3/internal/sliceutil/sliceutil_test.go create mode 100644 v3/pkg/application/bindings_bench_test.go create mode 100644 v3/pkg/application/bindings_optimized_bench_test.go create mode 100644 v3/pkg/application/events_bench_test.go create mode 100644 v3/pkg/application/json_libs_bench_test.go create mode 100644 v3/pkg/application/json_v2_bench_test.go create mode 100644 v3/pkg/application/systemtray_bench_test.go create mode 100644 v3/pkg/application/window_bench_test.go diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..c4109328c 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,6 +20,15 @@ After processing, the content will be moved to the main changelog and this file ## 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 @@ -29,6 +38,9 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Security diff --git a/v3/go.mod b/v3/go.mod index 5eb97b298..407db2905 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 @@ -8,11 +8,14 @@ require ( github.com/adrg/xdg v0.5.3 github.com/atterpac/refresh v0.8.6 github.com/bep/debounce v1.2.1 + github.com/bytedance/sonic v1.14.2 github.com/charmbracelet/glamour v0.9.0 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/goccy/go-json v0.10.5 github.com/godbus/dbus/v5 v5.1.0 github.com/google/go-cmp v0.7.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 @@ -21,6 +24,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/jackmordaunt/icns/v2 v2.2.7 github.com/jaypipes/ghw v0.17.0 + github.com/json-iterator/go v1.1.12 github.com/konoui/lipo v0.10.0 github.com/leaanthony/clir v1.7.0 github.com/leaanthony/go-ansi-parser v1.6.1 @@ -38,13 +42,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 ) @@ -53,6 +55,8 @@ require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect atomicgo.dev/schedule v0.1.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect github.com/charmbracelet/bubbletea v1.3.6 // indirect @@ -60,13 +64,18 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/konoui/go-qsort v0.1.0 // indirect github.com/mattn/go-localereader v0.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect ) require ( @@ -160,7 +169,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..899e70059 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -77,6 +77,12 @@ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= @@ -117,6 +123,8 @@ github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGl github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= @@ -157,6 +165,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= @@ -168,12 +178,15 @@ github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetT github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= @@ -215,6 +228,8 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEE github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -283,6 +298,10 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4 github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -349,22 +368,28 @@ github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4= github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 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= @@ -388,6 +413,8 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -406,7 +433,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 +490,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_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/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..2b961d781 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" + + json "github.com/goccy/go-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..1a1941c7e 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" + + json "github.com/goccy/go-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..4bac699d6 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" + + json "github.com/goccy/go-json" ) var _ ResponseWriter = &responseWriter{} diff --git a/v3/internal/assetserver/webview/responsewriter_ios.go b/v3/internal/assetserver/webview/responsewriter_ios.go index 6dc56bb81..a6284b804 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" + + json "github.com/goccy/go-json" ) var _ ResponseWriter = &responseWriter{} diff --git a/v3/internal/capabilities/capabilities.go b/v3/internal/capabilities/capabilities.go index af9428bb2..1af25e91a 100644 --- a/v3/internal/capabilities/capabilities.go +++ b/v3/internal/capabilities/capabilities.go @@ -1,6 +1,6 @@ package capabilities -import "encoding/json" +import json "github.com/goccy/go-json" type Capabilities struct { HasNativeDrag bool 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/runtime/runtime.go b/v3/internal/runtime/runtime.go index 0c08108df..04b29fd4d 100644 --- a/v3/internal/runtime/runtime.go +++ b/v3/internal/runtime/runtime.go @@ -1,8 +1,9 @@ package runtime import ( - "encoding/json" "fmt" + + json "github.com/goccy/go-json" ) var runtimeInit = `window._wails=window._wails||{};window.wails=window.wails||{};` 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/pkg/application/application_android.go b/v3/pkg/application/application_android.go index 46a45fc17..e5da744eb 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" + json "github.com/goccy/go-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..cb31203dd 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" + + json "github.com/goccy/go-json" ) var ( diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index b70f98870..7393035c3 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -196,12 +196,12 @@ static void startSingleInstanceListener(const char *uniqueID) { */ import "C" import ( - "encoding/json" "unsafe" - "github.com/wailsapp/wails/v3/internal/operatingsystem" + json "github.com/goccy/go-json" "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/operatingsystem" "github.com/wailsapp/wails/v3/pkg/events" ) diff --git a/v3/pkg/application/application_ios.go b/v3/pkg/application/application_ios.go index 8965f2afb..7b0da7eee 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" + json "github.com/goccy/go-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/bindings.go b/v3/pkg/application/bindings.go index 431caab62..42830d07e 100644 --- a/v3/pkg/application/bindings.go +++ b/v3/pkg/application/bindings.go @@ -2,22 +2,81 @@ package application import ( "context" - "encoding/json" "errors" "fmt" "reflect" "runtime" "strings" - "github.com/wailsapp/wails/v3/internal/hash" + json "github.com/goccy/go-json" - "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/hash" + "github.com/wailsapp/wails/v3/internal/sliceutil" ) +// init forces goccy/go-json to initialize its type address cache at program startup. +// This prevents a Windows-specific index out-of-bounds panic that can occur when the decoder is first invoked later (see https://github.com/goccy/go-json/issues/474). +func init() { + // Force goccy/go-json to initialize its type address cache early. + // On Windows, if the decoder is first invoked later (e.g., during tests), + // the type address calculation can fail with an index out of bounds panic. + // See: https://github.com/goccy/go-json/issues/474 + var ( + s string + i int + i8 int8 + i16 int16 + i32 int32 + i64 int64 + u uint + u8 uint8 + u16 uint16 + u32 uint32 + u64 uint64 + f32 float32 + f64 float64 + b bool + bs []byte + ss []string + si []int + sf []float64 + sa []any + msa map[string]any + mss map[string]string + msi map[string]int + rm json.RawMessage + ) + _ = json.Unmarshal([]byte(`""`), &s) + _ = json.Unmarshal([]byte(`0`), &i) + _ = json.Unmarshal([]byte(`0`), &i8) + _ = json.Unmarshal([]byte(`0`), &i16) + _ = json.Unmarshal([]byte(`0`), &i32) + _ = json.Unmarshal([]byte(`0`), &i64) + _ = json.Unmarshal([]byte(`0`), &u) + _ = json.Unmarshal([]byte(`0`), &u8) + _ = json.Unmarshal([]byte(`0`), &u16) + _ = json.Unmarshal([]byte(`0`), &u32) + _ = json.Unmarshal([]byte(`0`), &u64) + _ = json.Unmarshal([]byte(`0`), &f32) + _ = json.Unmarshal([]byte(`0`), &f64) + _ = json.Unmarshal([]byte(`false`), &b) + _ = json.Unmarshal([]byte(`""`), &bs) + _ = json.Unmarshal([]byte(`[]`), &ss) + _ = json.Unmarshal([]byte(`[]`), &si) + _ = json.Unmarshal([]byte(`[]`), &sf) + _ = json.Unmarshal([]byte(`[]`), &sa) + _ = json.Unmarshal([]byte(`{}`), &msa) + _ = json.Unmarshal([]byte(`{}`), &mss) + _ = json.Unmarshal([]byte(`{}`), &msi) + _ = json.Unmarshal([]byte(`""`), &rm) +} + +// 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 +87,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 +125,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 +176,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 +227,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 +269,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 +335,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 +362,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 +392,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 +418,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 @@ -395,4 +482,4 @@ func isNamed(value interface{}) bool { } return rv.Type().Name() != "" -} +} \ No newline at end of file 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/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..f70d37123 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" + json "github.com/goccy/go-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..0b4b5b35b 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" + + json "github.com/goccy/go-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..274b29b5f --- /dev/null +++ b/v3/pkg/application/json_libs_bench_test.go @@ -0,0 +1,310 @@ +//go:build bench + +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/logger_ios.go b/v3/pkg/application/logger_ios.go index 559a033a4..ed77d09b5 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" + + json "github.com/goccy/go-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/messageprocessor_args.go b/v3/pkg/application/messageprocessor_args.go index 838e389f5..3eeb98701 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" + + json "github.com/goccy/go-json" ) type Args struct { diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go index 20023a5a6..9b58801da 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" + json "github.com/goccy/go-json" + "github.com/wailsapp/wails/v3/pkg/errs" ) diff --git a/v3/pkg/application/messageprocessor_events.go b/v3/pkg/application/messageprocessor_events.go index 9de3ae136..d251c6ace 100644 --- a/v3/pkg/application/messageprocessor_events.go +++ b/v3/pkg/application/messageprocessor_events.go @@ -1,7 +1,7 @@ package application import ( - "encoding/json" + json "github.com/goccy/go-json" "github.com/wailsapp/wails/v3/pkg/errs" ) diff --git a/v3/pkg/application/single_instance.go b/v3/pkg/application/single_instance.go index 24bbf5c31..681998874 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" + + json "github.com/goccy/go-json" ) var alreadyRunningError = errors.New("application is already running") 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..d043c9b53 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" + + json "github.com/goccy/go-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..95102c048 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" + json "github.com/goccy/go-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 + }) } } diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 5a0b9a30f..abc759d7b 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -20,8 +20,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" @@ -245,8 +244,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, @@ -1186,14 +1191,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)) } } @@ -1914,13 +1927,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) } 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/services/kvstore/kvstore.go b/v3/pkg/services/kvstore/kvstore.go index 899dd86ec..94ce6c61f 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" + json "github.com/goccy/go-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..70a505fc9 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" + json "github.com/goccy/go-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..455f96cdb 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" + json "github.com/goccy/go-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..6bca1e747 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" + json "github.com/goccy/go-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/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 From 0b6dfe032cb0968a09174c7c8a2e15fa7bc6c765 Mon Sep 17 00:00:00 2001 From: Ndianabasi Udonkang Date: Fri, 2 Jan 2026 01:21:22 +0100 Subject: [PATCH 16/40] Improve Build Commands to Accommodate Spaces (#4845) * fix: improve darwin build commands to accommodate spaces in APP_NAME * fix: improve android build commands to accommodate spaces in APP_NAME * fix: improve ios build commands to accommodate spaces in APP_NAME * fix: improve linux build commands to accommodate spaces in APP_NAME * fix: improve windows build commands to accommodate spaces in APP_NAME * docs: update `v3/UNRELEASED_CHANGELOG.md` * fix(docs): correct changelog * fix: remove quotes around GO_CACHE_MOUNT and REPLACE_MOUNTS --------- Co-authored-by: Lea Anthony --- v3/UNRELEASED_CHANGELOG.md | 2 +- .../build_assets/android/Taskfile.yml | 8 ++--- .../commands/build_assets/darwin/Taskfile.yml | 28 +++++++-------- .../commands/build_assets/ios/Taskfile.yml | 36 +++++++++---------- .../commands/build_assets/linux/Taskfile.yml | 22 ++++++------ .../build_assets/windows/Taskfile.yml | 8 ++--- 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index c4109328c..abe9f78a6 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -31,7 +31,7 @@ After processing, the content will be moved to the main changelog and this file - 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 ## Deprecated 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 054da1a63..b76d5bae0 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}}' @@ -138,17 +138,17 @@ 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,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 +158,11 @@ 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,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 +173,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 +189,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/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" From 53cd30acc77c5f891599d981db36df27f86a545c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Jan 2026 02:46:35 +0000 Subject: [PATCH 17/40] chore(v3): bump to v3.0.0-alpha.55 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 21 +++++++++++++++++++++ v3/UNRELEASED_CHANGELOG.md | 14 +------------- v3/internal/version/version.txt | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index d0edd228a..15463dde6 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index abe9f78a6..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,27 +20,15 @@ After processing, the content will be moved to the main changelog and this file ## 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 + ## Deprecated ## 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 ## Security diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 3ca87f3ac..8b6038c41 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.54 \ No newline at end of file +v3.0.0-alpha.55 \ No newline at end of file From 53c2275fea08e236b1f0e75efad855c7aeac54b2 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 4 Jan 2026 11:08:29 +1100 Subject: [PATCH 18/40] fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows implementation (#4848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows This commit fixes drag-and-drop reliability on Linux and simplifies the Windows implementation. ## Linux - Rewrite GTK drag handlers to properly intercept external file drops - Fix HTML5 internal drag-and-drop being broken when file drop enabled - Add hover effects during file drag operations - Fix multiple app instances interfering with each other ## Windows - Remove native IDropTarget in favor of JavaScript approach (matches v2) - File drops now handled via chrome.webview.postMessageWithAdditionalObjects ## All Platforms - Rename EnableDragAndDrop to EnableFileDrop - Rename data-wails-drop-target to data-file-drop-target - Rename wails-drop-target-active to file-drop-target-active - Add comprehensive drag-and-drop documentation ## Breaking Changes - EnableDragAndDrop -> EnableFileDrop - data-wails-dropzone -> data-file-drop-target - wails-dropzone-hover -> file-drop-target-active - DropZoneDetails -> DropTargetDetails - Remove WindowDropZoneFilesDropped event (use WindowFilesDropped) * feat(macos): optimize drag event performance with debouncing and caching - Add 50ms debouncing to limit drag events to 20/sec (was 120/sec) - Implement window implementation caching to avoid repeated lookups - Maintain existing 5-pixel threshold for immediate response - Keep zero-allocation path with pre-allocated buffers - Rename linuxDragActive to nativeDragActive for clarity - Update IMPLEMENTATION.md with optimization details and Windows guidance Performance improvements: - 83% reduction in event frequency - ~6x reduction in CPU/memory usage during drag operations - Maintains smooth visual feedback with InvokeSync for timer callbacks * fix(windows): implement proper file drop support for Windows - Remove incorrect AllowExternalDrag(false) call that was blocking file drops - Fix message prefix from 'FilesDropped' to 'file:drop:' to match JS runtime - Fix coordinate parsing for 'file:drop:x:y' format (indices 2,3 not 1,2) - Add enableFileDrop flag injection to JS runtime during navigation - Update JS runtime to check enableFileDrop flag before processing drops - Always call preventDefault() to stop browser navigation on file drags - Show 'no drop' cursor when file drops are disabled - Update example to filter file drags from HTML drop zone handlers - Add documentation for combining file drop with HTML drag-and-drop * fix(v3): block file drops on Linux when EnableFileDrop is false - Add disableDND() to intercept and reject external file drags at GTK level - Show 'no drop' cursor when files are dragged over window - Allow internal HTML5 drag-and-drop to work normally - Initialize _wails.flags object in runtime core to prevent undefined errors - Inject enableFileDrop flag on Linux and macOS (matching Windows) - Fix bare _wails reference to use window._wails - Update docs with info about blocked drops and combining with HTML DnD * fix(darwin): add missing fmt import in webview_window_darwin.go * fix(macOS): implement hover effects for file drag-and-drop with optimizations - Added draggingUpdated: handler to track mouse movement during drag operations - Implemented macosOnDragEnter/Exit/Over export functions for real-time hover state - Fixed JS function call from '_wails.handlePlatformFileDrop' to correct 'wails.Window.HandlePlatformFileDrop' - Added EnableFileDrop flag checks to prevent hover effects when file drops are disabled - Renamed linuxDragActive to nativeDragActive for cross-platform consistency Performance optimizations: - Added 50ms debounce to reduce event frequency from ~120/sec to ~20/sec - Implemented 5-pixel movement threshold for immediate response - Added window caching with sync.Map to avoid repeated lookups - Zero-allocation JavaScript calls with pre-allocated 128-byte buffer - Reduced memory usage to ~18 bytes per event (6x reduction) Build improvements: - Updated runtime Taskfile to include documentation generation - Added docs:build task to runtime build process - Fixed build order: events → docs → runtime Documentation: - Added IMPLEMENTATION.md with optimization details - Included guidance for Windows implementation * chore(v3/examples): remove html-dnd-api example The drag-n-drop example now demonstrates both external file drops and internal HTML5 drag-and-drop, making this separate example redundant. * docs(v3): move drag-and-drop implementation details to runtime-internals - Add drag-and-drop section to contributing/runtime-internals.mdx - Remove IMPLEMENTATION.md from example (content now in proper docs) - Covers platform differences, debugging tips, and key files * fix(v3): remove html-dnd-api from example build list * fix(v3): remove duplicate json import in application_darwin.go * fix(v3): address CodeRabbit review feedback - Fix docs to use app.Window.NewWithOptions() instead of deprecated API - Add mutex protection to dragOverJSBuffer to prevent race conditions - Add mutex protection to dragThrottleState fields for thread safety * docs: add coderabbit pre-push requirement to AGENTS.md * fix(v3/test): use correct CSS class name file-drop-target-active * chore(v3/test): remove dnd-test directory This was a development test file that shouldn't be in the PR. The drag-n-drop example serves as the proper test case. * docs(v3): update Windows file drop comment to reflect implemented fix Remove stale TODO - enableFileDrop flag is now injected in navigationCompleted * refactor(v3): make handleDragAndDropMessage unexported Internal method only called by application event loop, not part of public API. --- AGENTS.md | 1 + docs/astro.config.mjs | 6 +- .../docs/contributing/runtime-internals.mdx | 74 ++ .../docs/features/drag-and-drop/files.mdx | 212 +++++ .../docs/features/drag-and-drop/html.mdx | 232 +++++ .../content/docs/features/windows/options.mdx | 55 ++ .../content/docs/quick-start/next-steps.mdx | 2 +- v3/Taskfile.yaml | 2 - v3/UNRELEASED_CHANGELOG.md | 13 + v3/examples/drag-n-drop/README.md | 76 +- v3/examples/drag-n-drop/assets/index.html | 749 ++++++++-------- v3/examples/drag-n-drop/main.go | 150 +--- v3/examples/html-dnd-api/README.md | 43 - v3/examples/html-dnd-api/assets/index.html | 75 -- v3/examples/html-dnd-api/main.go | 40 - v3/examples/liquid-glass/main.go | 126 +-- .../bundledassets/runtime.debug.js | 241 +++-- .../assetserver/bundledassets/runtime.js | 2 +- v3/internal/generator/collect/known_events.go | 425 +++++---- v3/internal/runtime/Taskfile.yaml | 59 +- .../@wailsio/runtime/package-lock.json | 4 + .../@wailsio/runtime/src/event_types.ts | 1 - .../desktop/@wailsio/runtime/src/index.ts | 7 +- .../desktop/@wailsio/runtime/src/system.ts | 33 - .../desktop/@wailsio/runtime/src/window.ts | 306 +++++-- v3/internal/runtime/runtime.go | 2 +- v3/pkg/application/application.go | 43 +- v3/pkg/application/application_darwin.go | 250 ++++++ v3/pkg/application/context_window_event.go | 20 +- v3/pkg/application/linux_cgo.go | 333 ++++++- v3/pkg/application/linux_purego.go | 7 +- v3/pkg/application/messageprocessor_window.go | 34 +- v3/pkg/application/webview_window.go | 99 ++- v3/pkg/application/webview_window_darwin.go | 27 +- v3/pkg/application/webview_window_darwin.m | 18 - .../application/webview_window_darwin_drag.m | 44 +- v3/pkg/application/webview_window_linux.go | 9 +- v3/pkg/application/webview_window_options.go | 24 +- .../webview_window_options_test.go | 15 - v3/pkg/application/webview_window_windows.go | 106 +-- v3/pkg/application/window.go | 2 +- v3/pkg/events/events.go | 839 +++++++++--------- v3/pkg/events/events.txt | 1 - v3/pkg/events/events_darwin.h | 266 +++--- v3/pkg/events/events_ios.h | 46 +- v3/pkg/events/events_linux.h | 24 +- v3/pkg/events/known_events.go | 425 +++++---- 47 files changed, 3342 insertions(+), 2226 deletions(-) create mode 100644 docs/src/content/docs/features/drag-and-drop/files.mdx create mode 100644 docs/src/content/docs/features/drag-and-drop/html.mdx delete mode 100644 v3/examples/html-dnd-api/README.md delete mode 100644 v3/examples/html-dnd-api/assets/index.html delete mode 100644 v3/examples/html-dnd-api/main.go 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/contributing/runtime-internals.mdx b/docs/src/content/docs/contributing/runtime-internals.mdx index d150c2d8b..7d35ae91b 100644 --- a/docs/src/content/docs/contributing/runtime-internals.mdx +++ b/docs/src/content/docs/contributing/runtime-internals.mdx @@ -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/features/drag-and-drop/files.mdx b/docs/src/content/docs/features/drag-and-drop/files.mdx new file mode 100644 index 000000000..6d53d7934 --- /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.EmitEvent("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/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 93a468120..d2f338fc9 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -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.NewWebviewWindowWithOptions(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 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/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 8e4648038..61496656d 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,15 +20,28 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Deprecated ## Removed +- Remove native `IDropTarget` implementation on Windows in favor of JavaScript-based approach (matches v2 behavior) ## Security diff --git a/v3/examples/drag-n-drop/README.md b/v3/examples/drag-n-drop/README.md index cb3c56fd0..d1a4bfb31 100644 --- a/v3/examples/drag-n-drop/README.md +++ b/v3/examples/drag-n-drop/README.md @@ -1,27 +1,73 @@ -# 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: +## See also -```bash -wails3 task build -``` +- [html-dnd-api](../html-dnd-api) - For dragging elements *within* your application (HTML5 Drag and Drop API) -# Status +## Status -| Platform | Status | -|----------|-------------| -| Mac | Working | -| Windows | Not Working | -| Linux | | +| 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..7843cc057 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/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/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/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/package-lock.json b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json index 87d3dc6da..23919ec7b 100644 --- a/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json @@ -1584,6 +1584,7 @@ "integrity": "sha512-HL26ajjMVe/wr3xlzjF0sCPCiAKaZJcIRFZHmG4yKHRJp4YAkHPG5X6GfWxCeDTpOmuHhNiOyNKUoZjjnm0tjw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" @@ -2561,6 +2562,7 @@ "integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@gerrit0/mini-shiki": "^1.24.0", "lunr": "^2.3.9", @@ -2633,6 +2635,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2661,6 +2664,7 @@ "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", 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 04b29fd4d..42fed010d 100644 --- a/v3/internal/runtime/runtime.go +++ b/v3/internal/runtime/runtime.go @@ -6,7 +6,7 @@ import ( json "github.com/goccy/go-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/pkg/application/application.go b/v3/pkg/application/application.go index 5a8c6f068..b4d5b9cb7 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -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) { diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index 7393035c3..e6e0b080a 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -196,6 +196,8 @@ static void startSingleInstanceListener(const char *uniqueID) { */ import "C" import ( + "sync" + "time" "unsafe" json "github.com/goccy/go-json" @@ -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/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/linux_cgo.go b/v3/pkg/application/linux_cgo.go index 476636ade..81706597a 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); @@ -224,34 +225,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" @@ -848,10 +1019,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 +1045,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 @@ -1568,6 +1799,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 +1848,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 diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go index 5d6c1cd95..7520de4d3 100644 --- a/v3/pkg/application/linux_purego.go +++ b/v3/pkg/application/linux_purego.go @@ -393,12 +393,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 { diff --git a/v3/pkg/application/messageprocessor_window.go b/v3/pkg/application/messageprocessor_window.go index 1b3134c4e..85095e866 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,18 +353,11 @@ 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( "[DragDropDebug] processWindowMethod: Decoded payload from 'args'", @@ -373,30 +365,24 @@ func (m *MessageProcessor) processWindowMethod( 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/webview_window.go b/v3/pkg/application/webview_window.go index 95102c048..08d21ebb3 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -1180,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) } @@ -1205,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 @@ -1454,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 } @@ -1470,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, @@ -1487,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 fc81974bf..baf40556c 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -352,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 @@ -847,6 +853,7 @@ static void setContentProtection(void *nsWindow, bool enabled) { */ import "C" import ( + "fmt" "sync" "sync/atomic" "unsafe" @@ -1094,6 +1101,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)) } @@ -1107,7 +1125,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 } @@ -1241,7 +1262,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) @@ -1477,6 +1498,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 752e3675a..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... 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 8ca629559..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 diff --git a/v3/pkg/application/webview_window_options_test.go b/v3/pkg/application/webview_window_options_test.go index 309f3f542..4d43e123e 100644 --- a/v3/pkg/application/webview_window_options_test.go +++ b/v3/pkg/application/webview_window_options_test.go @@ -82,21 +82,6 @@ func TestBackdropType_Constants(t *testing.T) { } } -func TestDragEffect_Constants(t *testing.T) { - if DragEffectNone != 1 { - t.Error("DragEffectNone should be 1") - } - if DragEffectCopy != 2 { - t.Error("DragEffectCopy should be 2") - } - if DragEffectMove != 3 { - t.Error("DragEffectMove should be 3") - } - if DragEffectLink != 4 { - t.Error("DragEffectLink should be 4") - } -} - func TestTheme_Constants(t *testing.T) { if SystemDefault != 0 { t.Error("SystemDefault should be 0") diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index abc759d7b..85cd1f950 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" @@ -67,7 +66,6 @@ type windowsWebviewWindow struct { resizeBorderWidth int32 resizeBorderHeight int32 focusingChromium bool - dropTarget *w32.DropTarget onceDo sync.Once // Window move debouncer @@ -721,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) } @@ -1977,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 { @@ -2151,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) } @@ -2176,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} @@ -2277,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) @@ -2319,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/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": {}, } From 722717a11e38497eca8de1b69152982f46ee1161 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 4 Jan 2026 00:09:35 +0000 Subject: [PATCH 19/40] [skip ci] Publish @wailsio/runtime v3.0.0-alpha.78 --- .../@wailsio/runtime/docs/assets/hierarchy.js | 2 +- .../runtime/docs/assets/navigation.js | 2 +- .../@wailsio/runtime/docs/assets/search.js | 2 +- .../runtime/docs/classes/CancelError.html | 2 +- .../docs/classes/CancellablePromise.html | 2 +- .../docs/classes/CancelledRejectionError.html | 2 +- .../docs/classes/_internal_.Window.html | 110 +++++++++--------- .../docs/functions/System.Capabilities.html | 2 +- .../docs/functions/System.Environment.html | 2 +- .../System.HandlePlatformFileDrop.html | 6 - .../docs/functions/System.IsAMD64.html | 2 +- .../runtime/docs/functions/System.IsARM.html | 2 +- .../docs/functions/System.IsARM64.html | 2 +- .../docs/functions/System.IsDarkMode.html | 2 +- .../docs/functions/System.IsDebug.html | 2 +- .../docs/functions/System.IsLinux.html | 2 +- .../runtime/docs/functions/System.IsMac.html | 2 +- .../docs/functions/System.IsWindows.html | 2 +- .../runtime/docs/functions/System.invoke.html | 2 +- .../@wailsio/runtime/docs/hierarchy.html | 2 +- .../interfaces/System.EnvironmentInfo.html | 12 +- .../docs/interfaces/System.OSInfo.html | 10 +- .../docs/interfaces/_internal_.Position.html | 6 +- .../docs/interfaces/_internal_.Size.html | 6 +- .../@wailsio/runtime/docs/modules/System.html | 2 +- .../runtime/docs/variables/Events.Types.html | 2 +- .../runtime/docs/variables/Window.html | 2 +- .../@wailsio/runtime/package-lock.json | 8 +- .../desktop/@wailsio/runtime/package.json | 2 +- 29 files changed, 96 insertions(+), 106 deletions(-) delete mode 100644 v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/System.HandlePlatformFileDrop.html 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 = "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"; \ 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 23919ec7b..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", @@ -1584,7 +1584,6 @@ "integrity": "sha512-HL26ajjMVe/wr3xlzjF0sCPCiAKaZJcIRFZHmG4yKHRJp4YAkHPG5X6GfWxCeDTpOmuHhNiOyNKUoZjjnm0tjw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" @@ -2562,7 +2561,6 @@ "integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@gerrit0/mini-shiki": "^1.24.0", "lunr": "^2.3.9", @@ -2635,7 +2633,6 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2664,7 +2661,6 @@ "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", 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": { From f1a4ffe72dc7bc2fa6bf4f020b51a8d9fb86ef26 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 4 Jan 2026 11:59:22 +1100 Subject: [PATCH 20/40] feat(linux): add libpath package for finding native library paths (#4847) * feat(linux): add libpath package for finding native library paths Add a new internal/libpath package that locates shared libraries (.so files) on Linux systems. Supports multiple distributions and package managers. Features: - Multi-tier search: pkg-config -> ldconfig -> filesystem scanning - Parallel search using goroutines for faster lookups - Cached dynamic path discovery for Flatpak, Snap, and Nix - Support for Debian/Ubuntu, Fedora/RHEL, Arch, openSUSE, NixOS - Context-aware cancellation for graceful shutdown Performance: - Library found: ~1.4ms (parallel search) - Library not found: ~46ms (was 84ms sequential) - Cached path discovery: 14ns (was 15ms uncached) * feat(libpath): add multi-library parallel search functions Add functions to search for multiple library candidates in parallel: - FindFirstLibrary: Search multiple libs in parallel, return first found - FindFirstLibraryOrdered: Search in order of preference (for version priority) - FindAllLibraries: Find all available libraries from a list Useful when the exact library version is unknown, e.g.: match, _ := FindFirstLibrary("webkit2gtk-4.1", "webkit2gtk-4.0", "webkit2gtk-6.0") Also adds findLibraryPathCtx for context-aware searching used by the multi-library functions. * refactor(libpath): split into separate files and fix race condition Split libpath_linux.go into smaller, focused files: - cache_linux.go: Path cache with thread-safe init/invalidate - flatpak_linux.go: Flatpak runtime path discovery - snap_linux.go: Snap package path discovery - nix_linux.go: Nix/NixOS path discovery - libpath_linux.go: Core search functions Fixes: - Fix data race between init() and invalidate() by holding mutex during cache writes inside sync.Once.Do (CodeRabbit review) - Fix FindLibraryPathWithOptions not searching dynamic paths (Flatpak/Snap/Nix) - now uses GetAllLibPaths() (CodeRabbit review) --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/internal/libpath/cache_linux.go | 79 +++ v3/internal/libpath/flatpak_linux.go | 53 ++ v3/internal/libpath/libpath.go | 104 +++ v3/internal/libpath/libpath_linux.go | 551 ++++++++++++++++ v3/internal/libpath/libpath_linux_test.go | 769 ++++++++++++++++++++++ v3/internal/libpath/libpath_other.go | 73 ++ v3/internal/libpath/nix_linux.go | 28 + v3/internal/libpath/snap_linux.go | 42 ++ 9 files changed, 1700 insertions(+) create mode 100644 v3/internal/libpath/cache_linux.go create mode 100644 v3/internal/libpath/flatpak_linux.go create mode 100644 v3/internal/libpath/libpath.go create mode 100644 v3/internal/libpath/libpath_linux.go create mode 100644 v3/internal/libpath/libpath_linux_test.go create mode 100644 v3/internal/libpath/libpath_other.go create mode 100644 v3/internal/libpath/nix_linux.go create mode 100644 v3/internal/libpath/snap_linux.go diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 61496656d..82ca3d975 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix ## Changed 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 +} From d0aa2a6e1fd448bd9b20b926b233f89c24330cdb Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 4 Jan 2026 13:35:28 +1100 Subject: [PATCH 21/40] fix(docs): escape MDX special characters in changelog and linux guide (#4849) - Wrap `<=8` in backticks in changelog.mdx to prevent MDX interpreting `<` as JSX element start - Remove unsupported {#custom-id} syntax from heading in linux.mdx as Starlight/Astro auto-generates heading IDs This fixes the docs build failure in the Deploy to GitHub Pages workflow. --- docs/src/content/docs/changelog.mdx | 2 +- docs/src/content/docs/guides/build/linux.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 15463dde6..255dde1b7 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -35,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 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 +- 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 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. From a1dd1f4d195894e1931d244f5a5b78dfc013e804 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 4 Jan 2026 02:54:09 +0000 Subject: [PATCH 22/40] chore(v3): bump to v3.0.0-alpha.56 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 24 ++++++++++++++++++++++++ v3/UNRELEASED_CHANGELOG.md | 14 -------------- v3/internal/version/version.txt | 2 +- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 255dde1b7..34ee2368d 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 82ca3d975..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,32 +17,18 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Deprecated ## Removed -- Remove native `IDropTarget` implementation on Windows in favor of JavaScript-based approach (matches v2 behavior) ## Security diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 8b6038c41..5e74e2d46 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.55 \ No newline at end of file +v3.0.0-alpha.56 \ No newline at end of file From 9039051fd332757e5cc77727821e2f44b02e9675 Mon Sep 17 00:00:00 2001 From: samstanier Date: Sun, 4 Jan 2026 03:40:10 +0000 Subject: [PATCH 23/40] [v3] Fix save dialog filename not pre-populated on Linux (#4841) (#4842) The SaveFileDialog.SetFilename() option had no effect on Linux because the filename was never passed to the GTK file chooser. This adds a currentName parameter to runChooserDialog and calls gtk_file_chooser_set_current_name() for save dialogs when a filename is specified. Changes: - linux_cgo.go: Add currentName parameter and GTK call - linux_purego.go: Add gtkFileChooserSetCurrentName binding and same fix Fixes the Linux implementation to match the behaviour on Windows and macOS. Co-authored-by: sas229 Co-authored-by: Lea Anthony --- v3/UNRELEASED_CHANGELOG.md | 6 ++++++ v3/pkg/application/linux_cgo.go | 17 ++++++++++++++--- v3/pkg/application/linux_purego.go | 16 +++++++++++++--- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..a617517a7 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,12 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix SaveFileDialog.SetFilename() having no effect on Linux (#4841) by @samstanier +- 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 ## Deprecated diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go index 81706597a..bd8dc7071 100644 --- a/v3/pkg/application/linux_cgo.go +++ b/v3/pkg/application/linux_cgo.go @@ -1997,7 +1997,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") @@ -2047,6 +2047,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{} @@ -2120,7 +2129,8 @@ func runOpenFileDialog(dialog *OpenFileDialogStruct) (chan string, error) { dialog.title, action, buttonText, - dialog.filters) + dialog.filters, + "") } func runQuestionDialog(parent pointer, options *MessageDialog) int { @@ -2202,7 +2212,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 7520de4d3..5bfe97af0 100644 --- a/v3/pkg/application/linux_purego.go +++ b/v3/pkg/application/linux_purego.go @@ -152,6 +152,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) @@ -305,6 +306,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") @@ -1027,7 +1029,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 @@ -1059,6 +1061,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) @@ -1128,7 +1136,8 @@ func runOpenFileDialog(dialog *OpenFileDialogStruct) ([]string, error) { dialog.title, GtkFileChooserActionOpen, buttonText, - dialog.filters) + dialog.filters, + "") } func runQuestionDialog(parent pointer, options *MessageDialog) int { @@ -1209,7 +1218,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 From ee7e95af52155e541f18482c924766d8203c60bb Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 4 Jan 2026 15:48:03 +1100 Subject: [PATCH 24/40] fix(v3): fix macOS mkdir brace expansion when APP_NAME contains spaces (#4850) fix(v3): fix macOS mkdir when APP_NAME contains spaces Replace brace expansion {MacOS,Resources} with two separate mkdir commands. Brace expansion doesn't work inside quoted strings and is shell-dependent. Adds integration test to verify mkdir works with spaces in paths. --- v3/UNRELEASED_CHANGELOG.md | 1 + .../commands/build_assets/darwin/Taskfile.yml | 6 +- v3/internal/commands/task_integration_test.go | 61 +++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index a617517a7..82fe9293a 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -29,6 +29,7 @@ After processing, the content will be moved to the main changelog and this file - 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 +- Fix macOS app bundle creation failing when APP_NAME contains spaces (brace expansion issue) ## Deprecated diff --git a/v3/internal/commands/build_assets/darwin/Taskfile.yml b/v3/internal/commands/build_assets/darwin/Taskfile.yml index b76d5bae0..50600ced9 100644 --- a/v3/internal/commands/build_assets/darwin/Taskfile.yml +++ b/v3/internal/commands/build_assets/darwin/Taskfile.yml @@ -138,7 +138,8 @@ tasks: create:app:bundle: summary: Creates an `.app` bundle cmds: - - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}" + - 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" @@ -158,7 +159,8 @@ tasks: run: cmds: - - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}" + - 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" 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") +} From 23b3424415d882d4c118b78de6966f0671e53ad8 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 5 Jan 2026 06:51:41 +1100 Subject: [PATCH 25/40] fix(v3): use correct JSON field names for drop coordinates in example (#4858) * fix(v3): use correct JSON field names for drop coordinates in example The DropTargetDetails struct uses lowercase JSON tags (x, y), but the example frontend was accessing uppercase (X, Y). * docs(v3): add coordinates fix to changelog --- v3/UNRELEASED_CHANGELOG.md | 6 +----- v3/examples/drag-n-drop/assets/index.html | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 82fe9293a..2f857d519 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -24,11 +24,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed - Fix SaveFileDialog.SetFilename() having no effect on Linux (#4841) by @samstanier -- 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 +- 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) ## Deprecated diff --git a/v3/examples/drag-n-drop/assets/index.html b/v3/examples/drag-n-drop/assets/index.html index 7843cc057..53af94212 100644 --- a/v3/examples/drag-n-drop/assets/index.html +++ b/v3/examples/drag-n-drop/assets/index.html @@ -359,7 +359,7 @@ let info = `External: ${files.length} file(s) dropped`; if (details) { - info += ` at (${details.X}, ${details.Y})`; + info += ` at (${details.x}, ${details.y})`; } dropDetails.textContent = info; }); From 4dce80d88769e28c2ca90cfa7176b72a64577261 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 5 Jan 2026 08:26:35 +1100 Subject: [PATCH 26/40] fix(v3): revert goccy/go-json to stdlib encoding/json to fix Windows panic (#4859) * fix(v3): warm up dialog types in go-json cache to prevent Windows panic Add FileFilter, OpenFileDialogOptions, SaveFileDialogOptions, and MessageDialogOptions to the init() warmup to prevent index out of bounds panic on Windows when these types are first unmarshaled. Fixes goccy/go-json#474 for Wails internal dialog types. * fix(v3): revert goccy/go-json to stdlib encoding/json to fix Windows panic goccy/go-json has a type address calculation bug on Windows that causes index out of bounds panic when decoding user-defined types for the first time. This reverts all runtime usages of goccy/go-json back to stdlib encoding/json. Test and benchmark files are left unchanged. Partially reverts PR #4843. --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/go.mod | 10 --- v3/go.sum | 27 -------- .../assetserver/webview/request_darwin.go | 2 +- .../assetserver/webview/request_ios.go | 2 +- .../webview/responsewriter_darwin.go | 2 +- .../assetserver/webview/responsewriter_ios.go | 2 +- v3/internal/capabilities/capabilities.go | 2 +- v3/internal/runtime/runtime.go | 2 +- v3/pkg/application/application_android.go | 2 +- .../application/application_android_nocgo.go | 2 +- v3/pkg/application/application_darwin.go | 2 +- v3/pkg/application/application_ios.go | 2 +- v3/pkg/application/bindings.go | 64 +------------------ v3/pkg/application/events.go | 2 +- v3/pkg/application/ios_runtime_ios.go | 2 +- v3/pkg/application/json_libs_bench_test.go | 4 ++ v3/pkg/application/logger_ios.go | 2 +- v3/pkg/application/messageprocessor_args.go | 2 +- v3/pkg/application/messageprocessor_call.go | 2 +- v3/pkg/application/messageprocessor_events.go | 2 +- v3/pkg/application/single_instance.go | 2 +- v3/pkg/application/transport_http.go | 2 +- v3/pkg/application/webview_window.go | 2 +- v3/pkg/services/kvstore/kvstore.go | 2 +- .../notifications/notifications_darwin.go | 2 +- .../notifications/notifications_linux.go | 2 +- .../notifications/notifications_windows.go | 2 +- 28 files changed, 31 insertions(+), 121 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 2f857d519..cc6c74940 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -26,6 +26,7 @@ After processing, the content will be moved to the main changelog and this file - 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) ## Deprecated diff --git a/v3/go.mod b/v3/go.mod index 407db2905..b5fe8b282 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -8,14 +8,12 @@ require ( github.com/adrg/xdg v0.5.3 github.com/atterpac/refresh v0.8.6 github.com/bep/debounce v1.2.1 - github.com/bytedance/sonic v1.14.2 github.com/charmbracelet/glamour v0.9.0 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/goccy/go-json v0.10.5 github.com/godbus/dbus/v5 v5.1.0 github.com/google/go-cmp v0.7.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 @@ -24,7 +22,6 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/jackmordaunt/icns/v2 v2.2.7 github.com/jaypipes/ghw v0.17.0 - github.com/json-iterator/go v1.1.12 github.com/konoui/lipo v0.10.0 github.com/leaanthony/clir v1.7.0 github.com/leaanthony/go-ansi-parser v1.6.1 @@ -55,8 +52,6 @@ require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect atomicgo.dev/schedule v0.1.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect github.com/charmbracelet/bubbletea v1.3.6 // indirect @@ -64,18 +59,13 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/konoui/go-qsort v0.1.0 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect ) require ( diff --git a/v3/go.sum b/v3/go.sum index 899e70059..f5bde5f7c 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -77,12 +77,6 @@ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= -github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= @@ -123,8 +117,6 @@ github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGl github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= @@ -178,15 +170,12 @@ github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetT github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= @@ -228,8 +217,6 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEE github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -298,10 +285,6 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4 github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -368,24 +351,16 @@ github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4= github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 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= @@ -413,8 +388,6 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/v3/internal/assetserver/webview/request_darwin.go b/v3/internal/assetserver/webview/request_darwin.go index 2b961d781..dd2d0232a 100644 --- a/v3/internal/assetserver/webview/request_darwin.go +++ b/v3/internal/assetserver/webview/request_darwin.go @@ -115,7 +115,7 @@ import ( "net/http" "unsafe" - json "github.com/goccy/go-json" + "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 1a1941c7e..81d23b0bc 100644 --- a/v3/internal/assetserver/webview/request_ios.go +++ b/v3/internal/assetserver/webview/request_ios.go @@ -113,7 +113,7 @@ import ( "net/http" "unsafe" - json "github.com/goccy/go-json" + "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 4bac699d6..ff29a08bb 100644 --- a/v3/internal/assetserver/webview/responsewriter_darwin.go +++ b/v3/internal/assetserver/webview/responsewriter_darwin.go @@ -71,7 +71,7 @@ import ( "net/http" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" ) var _ ResponseWriter = &responseWriter{} diff --git a/v3/internal/assetserver/webview/responsewriter_ios.go b/v3/internal/assetserver/webview/responsewriter_ios.go index a6284b804..119ef1628 100644 --- a/v3/internal/assetserver/webview/responsewriter_ios.go +++ b/v3/internal/assetserver/webview/responsewriter_ios.go @@ -73,7 +73,7 @@ import ( "strings" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" ) var _ ResponseWriter = &responseWriter{} diff --git a/v3/internal/capabilities/capabilities.go b/v3/internal/capabilities/capabilities.go index 1af25e91a..af9428bb2 100644 --- a/v3/internal/capabilities/capabilities.go +++ b/v3/internal/capabilities/capabilities.go @@ -1,6 +1,6 @@ package capabilities -import json "github.com/goccy/go-json" +import "encoding/json" type Capabilities struct { HasNativeDrag bool diff --git a/v3/internal/runtime/runtime.go b/v3/internal/runtime/runtime.go index 42fed010d..d35ff0552 100644 --- a/v3/internal/runtime/runtime.go +++ b/v3/internal/runtime/runtime.go @@ -3,7 +3,7 @@ package runtime import ( "fmt" - json "github.com/goccy/go-json" + "encoding/json" ) var runtimeInit = `window._wails=window._wails||{};window._wails.flags=window._wails.flags||{};window.wails=window.wails||{};` diff --git a/v3/pkg/application/application_android.go b/v3/pkg/application/application_android.go index e5da744eb..87bb1b2f4 100644 --- a/v3/pkg/application/application_android.go +++ b/v3/pkg/application/application_android.go @@ -148,7 +148,7 @@ import ( "time" "unsafe" - json "github.com/goccy/go-json" + "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 cb31203dd..ea769f3e1 100644 --- a/v3/pkg/application/application_android_nocgo.go +++ b/v3/pkg/application/application_android_nocgo.go @@ -7,7 +7,7 @@ import ( "sync" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" ) var ( diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index e6e0b080a..f5738f93f 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -200,7 +200,7 @@ import ( "time" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" "github.com/wailsapp/wails/v3/internal/assetserver/webview" "github.com/wailsapp/wails/v3/internal/operatingsystem" diff --git a/v3/pkg/application/application_ios.go b/v3/pkg/application/application_ios.go index 7b0da7eee..90e896b07 100644 --- a/v3/pkg/application/application_ios.go +++ b/v3/pkg/application/application_ios.go @@ -20,7 +20,7 @@ import ( "time" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" "github.com/wailsapp/wails/v3/internal/assetserver/webview" "github.com/wailsapp/wails/v3/pkg/events" diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go index 42830d07e..e16948399 100644 --- a/v3/pkg/application/bindings.go +++ b/v3/pkg/application/bindings.go @@ -2,75 +2,17 @@ package application import ( "context" + "encoding/json" "errors" "fmt" "reflect" "runtime" "strings" - json "github.com/goccy/go-json" - "github.com/wailsapp/wails/v3/internal/hash" "github.com/wailsapp/wails/v3/internal/sliceutil" ) -// init forces goccy/go-json to initialize its type address cache at program startup. -// This prevents a Windows-specific index out-of-bounds panic that can occur when the decoder is first invoked later (see https://github.com/goccy/go-json/issues/474). -func init() { - // Force goccy/go-json to initialize its type address cache early. - // On Windows, if the decoder is first invoked later (e.g., during tests), - // the type address calculation can fail with an index out of bounds panic. - // See: https://github.com/goccy/go-json/issues/474 - var ( - s string - i int - i8 int8 - i16 int16 - i32 int32 - i64 int64 - u uint - u8 uint8 - u16 uint16 - u32 uint32 - u64 uint64 - f32 float32 - f64 float64 - b bool - bs []byte - ss []string - si []int - sf []float64 - sa []any - msa map[string]any - mss map[string]string - msi map[string]int - rm json.RawMessage - ) - _ = json.Unmarshal([]byte(`""`), &s) - _ = json.Unmarshal([]byte(`0`), &i) - _ = json.Unmarshal([]byte(`0`), &i8) - _ = json.Unmarshal([]byte(`0`), &i16) - _ = json.Unmarshal([]byte(`0`), &i32) - _ = json.Unmarshal([]byte(`0`), &i64) - _ = json.Unmarshal([]byte(`0`), &u) - _ = json.Unmarshal([]byte(`0`), &u8) - _ = json.Unmarshal([]byte(`0`), &u16) - _ = json.Unmarshal([]byte(`0`), &u32) - _ = json.Unmarshal([]byte(`0`), &u64) - _ = json.Unmarshal([]byte(`0`), &f32) - _ = json.Unmarshal([]byte(`0`), &f64) - _ = json.Unmarshal([]byte(`false`), &b) - _ = json.Unmarshal([]byte(`""`), &bs) - _ = json.Unmarshal([]byte(`[]`), &ss) - _ = json.Unmarshal([]byte(`[]`), &si) - _ = json.Unmarshal([]byte(`[]`), &sf) - _ = json.Unmarshal([]byte(`[]`), &sa) - _ = json.Unmarshal([]byte(`{}`), &msa) - _ = json.Unmarshal([]byte(`{}`), &mss) - _ = json.Unmarshal([]byte(`{}`), &msi) - _ = json.Unmarshal([]byte(`""`), &rm) -} - // CallOptions defines the options for a method call. // Field order is optimized to minimize struct padding. type CallOptions struct { @@ -228,7 +170,7 @@ 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) { @@ -482,4 +424,4 @@ func isNamed(value interface{}) bool { } return rv.Type().Name() != "" -} \ No newline at end of file +} diff --git a/v3/pkg/application/events.go b/v3/pkg/application/events.go index f70d37123..dbe6d20a2 100644 --- a/v3/pkg/application/events.go +++ b/v3/pkg/application/events.go @@ -7,7 +7,7 @@ import ( "sync" "sync/atomic" - json "github.com/goccy/go-json" + "encoding/json" "github.com/wailsapp/wails/v3/pkg/events" ) diff --git a/v3/pkg/application/ios_runtime_ios.go b/v3/pkg/application/ios_runtime_ios.go index 0b4b5b35b..6a388d9a9 100644 --- a/v3/pkg/application/ios_runtime_ios.go +++ b/v3/pkg/application/ios_runtime_ios.go @@ -12,7 +12,7 @@ import "C" import ( "unsafe" - json "github.com/goccy/go-json" + "encoding/json" ) // iosHapticsImpact triggers an iOS haptic impact using the provided style. diff --git a/v3/pkg/application/json_libs_bench_test.go b/v3/pkg/application/json_libs_bench_test.go index 274b29b5f..c3e7e4918 100644 --- a/v3/pkg/application/json_libs_bench_test.go +++ b/v3/pkg/application/json_libs_bench_test.go @@ -1,7 +1,10 @@ //go:build bench +// Disabled: goccy/go-json causes Windows panics. See PR #4859. + package application_test +/* import ( "encoding/json" "testing" @@ -308,3 +311,4 @@ func BenchmarkMarshal_Complex_Sonic(b *testing.B) { _, _ = sonic.Marshal(complexResult) } } +*/ diff --git a/v3/pkg/application/logger_ios.go b/v3/pkg/application/logger_ios.go index ed77d09b5..584e96609 100644 --- a/v3/pkg/application/logger_ios.go +++ b/v3/pkg/application/logger_ios.go @@ -17,7 +17,7 @@ import ( "time" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" ) // iosConsoleHandler implements slog.Handler and forwards records to the WKWebView console. diff --git a/v3/pkg/application/messageprocessor_args.go b/v3/pkg/application/messageprocessor_args.go index 3eeb98701..bb3ffc6d0 100644 --- a/v3/pkg/application/messageprocessor_args.go +++ b/v3/pkg/application/messageprocessor_args.go @@ -3,7 +3,7 @@ package application import ( "fmt" - json "github.com/goccy/go-json" + "encoding/json" ) type Args struct { diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go index 9b58801da..924acdbf0 100644 --- a/v3/pkg/application/messageprocessor_call.go +++ b/v3/pkg/application/messageprocessor_call.go @@ -4,7 +4,7 @@ import ( "context" "errors" - json "github.com/goccy/go-json" + "encoding/json" "github.com/wailsapp/wails/v3/pkg/errs" ) diff --git a/v3/pkg/application/messageprocessor_events.go b/v3/pkg/application/messageprocessor_events.go index d251c6ace..9de3ae136 100644 --- a/v3/pkg/application/messageprocessor_events.go +++ b/v3/pkg/application/messageprocessor_events.go @@ -1,7 +1,7 @@ package application import ( - json "github.com/goccy/go-json" + "encoding/json" "github.com/wailsapp/wails/v3/pkg/errs" ) diff --git a/v3/pkg/application/single_instance.go b/v3/pkg/application/single_instance.go index 681998874..7f04320eb 100644 --- a/v3/pkg/application/single_instance.go +++ b/v3/pkg/application/single_instance.go @@ -10,7 +10,7 @@ import ( "path/filepath" "sync" - json "github.com/goccy/go-json" + "encoding/json" ) var alreadyRunningError = errors.New("application is already running") diff --git a/v3/pkg/application/transport_http.go b/v3/pkg/application/transport_http.go index d043c9b53..0c2b0fd45 100644 --- a/v3/pkg/application/transport_http.go +++ b/v3/pkg/application/transport_http.go @@ -10,7 +10,7 @@ import ( "strconv" "sync" - json "github.com/goccy/go-json" + "encoding/json" "github.com/wailsapp/wails/v3/pkg/errs" ) diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 08d21ebb3..dbf7eebcc 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -9,7 +9,7 @@ import ( "sync/atomic" "unsafe" - json "github.com/goccy/go-json" + "encoding/json" "github.com/leaanthony/u" "github.com/wailsapp/wails/v3/internal/assetserver" diff --git a/v3/pkg/services/kvstore/kvstore.go b/v3/pkg/services/kvstore/kvstore.go index 94ce6c61f..4d2f511b0 100644 --- a/v3/pkg/services/kvstore/kvstore.go +++ b/v3/pkg/services/kvstore/kvstore.go @@ -5,7 +5,7 @@ import ( "os" "sync" - json "github.com/goccy/go-json" + "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 70a505fc9..b9695b3f9 100644 --- a/v3/pkg/services/notifications/notifications_darwin.go +++ b/v3/pkg/services/notifications/notifications_darwin.go @@ -20,7 +20,7 @@ import ( "time" "unsafe" - json "github.com/goccy/go-json" + "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 455f96cdb..23e21433e 100644 --- a/v3/pkg/services/notifications/notifications_linux.go +++ b/v3/pkg/services/notifications/notifications_linux.go @@ -9,7 +9,7 @@ import ( "path/filepath" "sync" - json "github.com/goccy/go-json" + "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 6bca1e747..b1063dbf8 100644 --- a/v3/pkg/services/notifications/notifications_windows.go +++ b/v3/pkg/services/notifications/notifications_windows.go @@ -11,7 +11,7 @@ import ( "sync" _ "unsafe" - json "github.com/goccy/go-json" + "encoding/json" "git.sr.ht/~jackmordaunt/go-toast/v2" wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast" From 93c307b4b27bda704dd4abe96230b8ea8c524561 Mon Sep 17 00:00:00 2001 From: mbaklor <78748463+mbaklor@users.noreply.github.com> Date: Sun, 4 Jan 2026 23:48:29 +0200 Subject: [PATCH 27/40] V3/fix debug logs (#4857) * fix(v3): replace various debug logs from Info to Debug * fix(v3): fixed logging on linux as well as windows * fix(v3): add format directive for warning log argument --------- Co-authored-by: Lea Anthony --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/internal/assetserver/assetserver.go | 2 +- v3/pkg/application/application.go | 10 +++++----- v3/pkg/application/application_linux.go | 12 ++++++------ v3/pkg/application/application_windows.go | 6 +++--- v3/pkg/application/messageprocessor.go | 6 +++--- v3/pkg/application/messageprocessor_call.go | 6 +++--- v3/pkg/application/messageprocessor_window.go | 2 +- v3/pkg/application/webview_window_windows.go | 2 +- 9 files changed, 24 insertions(+), 23 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index cc6c74940..a78adee44 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,6 +20,7 @@ After processing, the content will be moved to the main changelog and this file ## Changed +- Replace various debug logs from Info to Debug (by @mbaklor) ## Fixed 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/pkg/application/application.go b/v3/pkg/application/application.go index b4d5b9cb7..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) @@ -733,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) @@ -741,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 { @@ -754,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_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_windows.go b/v3/pkg/application/application_windows.go index 364bb902c..7b3298cb3 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() 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_call.go b/v3/pkg/application/messageprocessor_call.go index 924acdbf0..71f60a00e 100644 --- a/v3/pkg/application/messageprocessor_call.go +++ b/v3/pkg/application/messageprocessor_call.go @@ -31,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 } @@ -55,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)) @@ -108,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 85095e866..1baf57606 100644 --- a/v3/pkg/application/messageprocessor_window.go +++ b/v3/pkg/application/messageprocessor_window.go @@ -359,7 +359,7 @@ func (m *MessageProcessor) processWindowMethod( if err != nil { 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), diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 85cd1f950..b2bb3c04d 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1490,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 From 862e2e8d6cc0fa084ee716bdfcacf682835d4682 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 5 Jan 2026 02:55:08 +0000 Subject: [PATCH 28/40] chore(v3): bump to v3.0.0-alpha.57 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 11 +++++++++++ v3/UNRELEASED_CHANGELOG.md | 5 ----- v3/internal/version/version.txt | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 34ee2368d..ddfa886b8 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index a78adee44..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -20,14 +20,9 @@ After processing, the content will be moved to the main changelog and this file ## 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) ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 5e74e2d46..63ca63748 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.56 \ No newline at end of file +v3.0.0-alpha.57 \ No newline at end of file From 1fb60abdc97660a0e02c5baa0ca029500e7251b7 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 8 Jan 2026 21:16:45 +1100 Subject: [PATCH 29/40] docs(v3): update API references to new Manager API pattern (#4863) * docs(v3): update API references to new Manager API pattern Update documentation to reflect the Manager API refactoring from alpha.10: Window API: - app.NewWebviewWindow() -> app.Window.New() - app.NewWebviewWindowWithOptions() -> app.Window.NewWithOptions() - app.CurrentWindow() -> app.Window.Current() - app.GetAllWindows() -> app.Window.GetAll() - app.WindowByName() -> app.Window.GetByName() Event API: - app.EmitEvent() -> app.Event.Emit() - app.OnEvent() -> app.Event.On() - app.OnApplicationEvent() -> app.Event.OnApplicationEvent() Dialog API: - app.OpenFileDialog() -> app.Dialog.OpenFile() - app.SaveFileDialog() -> app.Dialog.SaveFile() - app.InfoDialog() -> app.Dialog.Info() - app.ErrorDialog() -> app.Dialog.Error() - app.WarningDialog() -> app.Dialog.Warning() - app.QuestionDialog() -> app.Dialog.Question() Other APIs: - app.NewSystemTray() -> app.SystemTray.New() - app.SetApplicationMenu() -> app.Menu.Set() - app.GetPrimaryScreen() -> app.Screen.GetPrimary() - app.GetAllScreens() -> app.Screen.GetAll() - app.ClipboardGetText() -> app.Clipboard.Text() - app.ClipboardSetText() -> app.Clipboard.SetText() - app.BrowserOpenURL() -> app.Browser.OpenURL() Fixes outdated code examples across 31 documentation files. * chore(v3): add docs fix to unreleased changelog --- .../content/docs/concepts/architecture.mdx | 2 +- docs/src/content/docs/concepts/bridge.mdx | 2 +- .../content/docs/concepts/build-system.mdx | 2 +- .../docs/contributing/runtime-internals.mdx | 2 +- docs/src/content/docs/faq.mdx | 12 +-- .../docs/features/bindings/best-practices.mdx | 2 +- .../docs/features/bindings/methods.mdx | 4 +- .../content/docs/features/bindings/models.mdx | 2 +- .../docs/features/bindings/services.mdx | 6 +- .../docs/features/clipboard/basics.mdx | 6 +- .../content/docs/features/dialogs/custom.mdx | 28 +++---- .../docs/features/drag-and-drop/files.mdx | 2 +- .../content/docs/features/events/system.mdx | 84 +++++++++---------- .../docs/features/menus/application.mdx | 4 +- .../content/docs/features/menus/context.mdx | 2 +- .../content/docs/features/menus/systray.mdx | 18 ++-- .../content/docs/features/screens/info.mdx | 4 +- .../content/docs/features/windows/basics.mdx | 30 +++---- .../content/docs/features/windows/events.mdx | 14 ++-- .../docs/features/windows/frameless.mdx | 20 ++--- .../docs/features/windows/multiple.mdx | 40 ++++----- .../content/docs/features/windows/options.mdx | 16 ++-- .../guides/distribution/custom-protocols.mdx | 8 +- docs/src/content/docs/guides/performance.mdx | 4 +- docs/src/content/docs/guides/raw-messages.mdx | 6 +- docs/src/content/docs/migration/v2-to-v3.mdx | 30 +++---- .../content/docs/reference/application.mdx | 46 +++++----- docs/src/content/docs/reference/events.mdx | 78 ++++++++--------- docs/src/content/docs/reference/menu.mdx | 24 +++--- docs/src/content/docs/reference/overview.mdx | 2 +- docs/src/content/docs/reference/window.mdx | 12 +-- v3/UNRELEASED_CHANGELOG.md | 1 + 32 files changed, 257 insertions(+), 256 deletions(-) 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 7d35ae91b..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: ``` 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..f8348269d 100644 --- a/docs/src/content/docs/features/bindings/methods.mdx +++ b/docs/src/content/docs/features/bindings/methods.mdx @@ -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 index 6d53d7934..ffc75093a 100644 --- a/docs/src/content/docs/features/drag-and-drop/files.mdx +++ b/docs/src/content/docs/features/drag-and-drop/files.mdx @@ -115,7 +115,7 @@ window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.W details := event.Context().DropTargetDetails() // Send to frontend - app.EmitEvent("files-dropped", map[string]any{ + app.Event.Emit("files-dropped", map[string]any{ "files": files, "target": details.ElementID, }) 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/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 3137111ac..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 }) @@ -413,7 +413,7 @@ childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ ```go // Transparent title bar - window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Mac: application.MacWindow{ TitleBar: application.MacTitleBar{ AppearsTransparent: true, @@ -442,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, }, @@ -467,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, @@ -493,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 d2f338fc9..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, }) @@ -574,7 +574,7 @@ When enabled: **Example:** ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "File Uploader", EnableFileDrop: true, }) @@ -635,7 +635,7 @@ ContentProtectionEnabled: true, **Example:** ```go -window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Secure Window", ContentProtectionEnabled: true, }) @@ -892,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/distribution/custom-protocols.mdx b/docs/src/content/docs/guides/distribution/custom-protocols.mdx index 760e8e2d8..39a9b157c 100644 --- a/docs/src/content/docs/guides/distribution/custom-protocols.mdx +++ b/docs/src/content/docs/guides/distribution/custom-protocols.mdx @@ -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 @@ -362,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, @@ -370,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/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/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..ff79bbb3d 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Deprecated From 0abad1ffb282c804f2c0a1acdd30109944226ac7 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 8 Jan 2026 21:20:13 +1100 Subject: [PATCH 30/40] fix(v3/linux): fix crash on panic in JS-bound Go methods (#4856) * fix(v3/linux): fix crash on panic in JS-bound Go methods WebKit2GTK installs signal handlers after gtk_main() starts, overriding our SA_ONSTACK fix. This causes Go panics (e.g., nil pointer dereference) in JS-bound methods to crash with 'non-Go code set up signal handler without SA_ONSTACK flag'. Fix by deferring signal handler installation via g_idle_add() to run after GTK main loop starts, ensuring we fix handlers AFTER WebKit has installed its own. Fixes #3965 * test(v3/linux): add test case for signal handler fix Adds a test application that can trigger panics from JS-bound Go methods to verify the signal handler fix for issue #3965 works correctly. The test app has buttons to: - Trigger a panic in a goroutine - Trigger an immediate panic - Call a safe method Before the fix, clicking 'Trigger Panic' would crash the app. After the fix, panics are recovered and logged. * chore: remove test case from PR * fix(linux): call signal handler fix after WebKit init, not in idle callback Move install_signal_handlers() call to after webkit_web_view_new_with_user_content_manager() to ensure WebKit has finished setting up its signal handlers before we add SA_ONSTACK. The previous g_idle_add approach was still too early - WebKit initialization continues after GTK init. This matches the v2 approach which waits for actual webview creation. Also add documentation about the known Go limitation (golang/go#7227) where signals may still be delivered on the wrong stack in some C interop scenarios. --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/linux_cgo.go | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index ff79bbb3d..d7dcab65b 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -24,6 +24,7 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Deprecated diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go index bd8dc7071..0a21f4c34 100644 --- a/v3/pkg/application/linux_cgo.go +++ b/v3/pkg/application/linux_cgo.go @@ -161,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 @@ -489,6 +498,7 @@ var ( ) var registerURIScheme sync.Once +var fixSignalHandlers sync.Once func init() { gtkSignalToMenuItem = map[uint]*MenuItem{} @@ -553,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) @@ -1344,6 +1352,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 From be0e9bab9b995505a30957d0f6409813621c3ee4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Jan 2026 02:46:28 +0000 Subject: [PATCH 31/40] chore(v3): bump to v3.0.0-alpha.58 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 6 ++++++ v3/UNRELEASED_CHANGELOG.md | 2 -- v3/internal/version/version.txt | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index ddfa886b8..e979a2126 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index d7dcab65b..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,8 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## 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 ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 63ca63748..7a89e4e03 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.57 \ No newline at end of file +v3.0.0-alpha.58 \ No newline at end of file From 897479d52be43b48f372db1fd0ab15c60882185d Mon Sep 17 00:00:00 2001 From: Ndianabasi Udonkang Date: Sat, 10 Jan 2026 10:00:52 +0100 Subject: [PATCH 32/40] Enhance `Drag-N-Drop` README with Internal Drag and Drop Info (#4869) * Enhance README with internal drag and drop info Added details about internal drag and drop functionality. * Update UNRELEASED_CHANGE.md --- v3/UNRELEASED_CHANGELOG.md | 2 +- v3/examples/drag-n-drop/README.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..a6eb78f10 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -19,7 +19,7 @@ After processing, the content will be moved to the main changelog and this file ## Changed - +- Update the README for the `Drag N Drop` example and highlights that `Internal Drag and Drop` is demonstrated with the example @ndianabasi ## Fixed diff --git a/v3/examples/drag-n-drop/README.md b/v3/examples/drag-n-drop/README.md index d1a4bfb31..a2cde8844 100644 --- a/v3/examples/drag-n-drop/README.md +++ b/v3/examples/drag-n-drop/README.md @@ -60,9 +60,11 @@ go run main.go Then drag files from your desktop or file manager into the drop zone. -## See also +## HTML5 Drag and Drop API -- [html-dnd-api](../html-dnd-api) - For dragging elements *within* your application (HTML5 Drag and Drop API) +This example also includes a demonstration for dragging elements *within* your application via the HTML5 Drag and Drop API. + +Scroll down to the `Internal Drag and Drop` section within the launched application to interact with the demo. ## Status From 9db2f74c443016aa31c2c25ff326f7e28a81dbc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 11 Jan 2026 02:54:04 +0000 Subject: [PATCH 33/40] chore(v3): bump to v3.0.0-alpha.59 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 2 +- v3/internal/version/version.txt | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index e979a2126..292617508 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index a6eb78f10..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -19,7 +19,7 @@ After processing, the content will be moved to the main changelog and this file ## Changed -- Update the README for the `Drag N Drop` example and highlights that `Internal Drag and Drop` is demonstrated with the example @ndianabasi + ## Fixed diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 7a89e4e03..0bc1c8b52 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.58 \ No newline at end of file +v3.0.0-alpha.59 \ No newline at end of file From 37fb9b9ba55c203004724e2941b2af0849518d05 Mon Sep 17 00:00:00 2001 From: Zach Botterman <6074435+popaprozac@users.noreply.github.com> Date: Tue, 13 Jan 2026 02:42:09 -0800 Subject: [PATCH 34/40] fix(v3/linux): Fix menu update by clearing menu (#4875) * clear menu * update changelog * free list * register glistfree --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/linux_cgo.go | 18 ++++++++++++++++++ v3/pkg/application/linux_purego.go | 25 ++++++++++++++++++++++++- v3/pkg/application/menu_linux.go | 3 +++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..c90953b93 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix menu not clearing children on `menu.Update()` ## Deprecated diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go index 0a21f4c34..4d5ce4c71 100644 --- a/v3/pkg/application/linux_cgo.go +++ b/v3/pkg/application/linux_cgo.go @@ -731,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") diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go index 5bfe97af0..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) @@ -261,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") @@ -294,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") @@ -521,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 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 From d7d6720d5403aff00f1156ddc095ce3ff3e5d878 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Jan 2026 02:50:25 +0000 Subject: [PATCH 35/40] chore(v3): bump to v3.0.0-alpha.60 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 292617508..a1d57f665 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index c90953b93..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,7 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## Fixed -- Fix menu not clearing children on `menu.Update()` ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 0bc1c8b52..1ff0a688f 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.59 \ No newline at end of file +v3.0.0-alpha.60 \ No newline at end of file From 9158c49e3032faa7ba2630eaa627cdecf1e32ef1 Mon Sep 17 00:00:00 2001 From: Abdelhadi Seddar <91424903+AbdelhadiSeddar@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:41:46 +0100 Subject: [PATCH 36/40] docs: fixed incorrect tag in Custom Output Directory of Bindings (#4873) * docs: fixed incorrect tag in Custom Output Directory of Bindings After some usage of wails I found out that ```bash wails3 generate bindings -o ./src/bindings ``` does not work and according to ```bash $ wails3 generate bindings --help ``` it uses the `-d string` tag instead * Update UNRELEASED_CHANGELOG.md --------- Co-authored-by: Lea Anthony --- docs/src/content/docs/features/bindings/methods.mdx | 2 +- v3/UNRELEASED_CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/content/docs/features/bindings/methods.mdx b/docs/src/content/docs/features/bindings/methods.mdx index f8348269d..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) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..ac2d76430 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix documentation regarding generating custom binding, must use `-d String` instead of `-o String` ## Deprecated From 85980349427085ff2f5ee24442dac6998cb59eda Mon Sep 17 00:00:00 2001 From: Ndianabasi Udonkang Date: Tue, 20 Jan 2026 00:42:54 +0100 Subject: [PATCH 37/40] Change Window Type from `WebviewWindow` to `Window` (#4829) * fix: change window type from WebviewWindow to Window * docs: update `UNRELEASED_CHANGELOG` --------- Co-authored-by: Lea Anthony --- .../docs/features/keyboard/shortcuts.mdx | 56 +++++++++---------- v3/UNRELEASED_CHANGELOG.md | 1 + 2 files changed, 29 insertions(+), 28 deletions(-) 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/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index ac2d76430..a9ee2e29d 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## 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` ## Deprecated From a0d2e03f6467175218bd3a12cae4a7b234338991 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Jan 2026 02:48:15 +0000 Subject: [PATCH 38/40] chore(v3): bump to v3.0.0-alpha.61 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 6 ++++++ v3/UNRELEASED_CHANGELOG.md | 2 -- v3/internal/version/version.txt | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index a1d57f665..6531af533 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index a9ee2e29d..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,8 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## 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` ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 1ff0a688f..9b52e2a36 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.60 \ No newline at end of file +v3.0.0-alpha.61 \ No newline at end of file From 8d28e2d06b9a075e230e26158de75059386b9aaf Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 21 Jan 2026 19:23:56 +1100 Subject: [PATCH 39/40] fix(windows): skip DPI awareness API call when already set via manifest (#4811) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check current DPI awareness before calling SetProcessDpiAwarenessContext. Windows only allows setting DPI awareness once per process - either via manifest or API, not both. If already set (e.g., via application manifest in built binaries), skip the API call to avoid "Access is denied" errors. Fixes #4803 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/application_windows.go | 12 ++++++++++++ v3/pkg/w32/shcore.go | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..af0097c53 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix SetProcessDpiAwarenessContext "Access is denied" error when DPI awareness is already set via application manifest (#4803) ## Deprecated diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go index 7b3298cb3..09dad6acf 100644 --- a/v3/pkg/application/application_windows.go +++ b/v3/pkg/application/application_windows.go @@ -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/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 From 9a131a049db4aa0b195714160bb57b07d1ae35df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 02:51:17 +0000 Subject: [PATCH 40/40] chore(v3): bump to v3.0.0-alpha.62 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 6531af533..997dab63a 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ 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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index af0097c53..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,7 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## Fixed -- Fix SetProcessDpiAwarenessContext "Access is denied" error when DPI awareness is already set via application manifest (#4803) ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 9b52e2a36..d0db23e95 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.61 \ No newline at end of file +v3.0.0-alpha.62 \ No newline at end of file