Skip to content

perf(v3): optimize JSON processing and reduce allocations in hot paths#4843

Merged
leaanthony merged 9 commits into
v3-alphafrom
v3/performance-improvements
Jan 1, 2026
Merged

perf(v3): optimize JSON processing and reduce allocations in hot paths#4843
leaanthony merged 9 commits into
v3-alphafrom
v3/performance-improvements

Conversation

@leaanthony

@leaanthony leaanthony commented Jan 1, 2026

Copy link
Copy Markdown
Member

Summary of Performance Optimizations

Method Binding Optimizations (pkg/application/bindings.go)

Change Impact
Switch to goccy/go-json 5x faster JSON unmarshaling
Optimize struct padding (BoundMethod, CallOptions, CallError) 8-16 bytes saved per struct
Cache isVariadic flag at registration Eliminates reflect call per invocation
Stack-allocated argument buffer (<=8 args) Avoids heap allocation for common case
Optimize result collection for single return values Eliminates slice allocation

Asset Server Optimizations (internal/assetserver/)

Change Impact
Use sync.Map for MIME cache Lock-free reads, better concurrency
Lazily allocate CloseNotify channel Avoids channel allocation per request
Remove debug CSS logging Removes unnecessary I/O
Use rw.Write(blob) instead of io.Copy Avoids Reader allocation

HTTP Transport Optimizations (pkg/application/transport_http.go)

Change Impact
Buffer pool for request body reading Reuses buffers across requests

Final Performance Results

Benchmark Before After Improvement
SimpleCall 1290 ns, 240 B, 7 allocs 930 ns, 80 B, 5 allocs 28% faster, 67% less memory
ComplexCall 10500 ns, 1192 B, 35 allocs 3900 ns, 1020 B, 22 allocs 63% faster, 14% less memory
VariadicCall 3460 ns, 512 B, 16 allocs 1600 ns, 289 B, 11 allocs 54% faster, 44% less memory

Benchmark Tests

Added comprehensive benchmark tests with //go:build bench tag so they don't run during normal go test:

# Run benchmarks explicitly
go test -tags=bench -bench=. ./pkg/application/
go test -tags=bench -bench=. ./internal/assetserver/

Summary by CodeRabbit

  • Changed
    • Significant runtime performance and memory improvements, pooled buffers and lazy allocations, faster JSON handling, and optimized method invocation for snappier behavior.
  • Removed
    • Several external dependencies replaced with smaller in-house implementations, reducing binary size.
  • New Features
    • Expanded MIME-by-extension detection and a lightweight .desktop parser for Linux file explorer; new iOS runtime toggles.
  • Tests
    • Extensive new benchmark and unit test suites across bindings, events, asset serving, transport, system tray, and window subsystems.

✏️ Tip: You can customize this high-level summary in your review settings.

- 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)
@coderabbitai

coderabbitai Bot commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Switches runtime JSON to github.com/goccy/go-json, adds invocation and allocation optimizations (cached variadic, stack args, pooled buffers), replaces MIME detection with an extension-first sync.Map + stdlib fallback, adds a minimal .desktop parser, removes several dependencies, and adds many benchmarks and tests.

Changes

Cohort / File(s) Summary
Module & deps
v3/go.mod
Bump Go toolchain; add multiple JSON/encoding libs and indirect deps; remove github.com/wailsapp/mimetype.
Global JSON swap
v3/.../*.go
Replace encoding/json with github.com/goccy/go-json (aliased json) across many packages (application, internal, services, webview).
Bindings core
v3/pkg/application/bindings.go
Reorder/add fields on BoundMethod (isVariadic, needsContext, marshalError); move MethodID in CallOptions/CallError; cache variadic flag; optimize result collection and error handling.
Bindings optimized & benches
v3/pkg/application/bindings_bench_test.go, v3/pkg/application/bindings_optimized_bench_test.go
Add pool-backed invocation paths (CallOptimized, CallWithJSONv2) and extensive benchmarks comparing call paths and JSON libs.
MIME cache & sniffing
v3/internal/assetserver/mimecache.go, v3/internal/assetserver/content_type_sniffer.go, v3/internal/assetserver/common.go
Replace mutexed map with sync.Map, add large extension→MIME map, fast extension-first lookup then http.DetectContentType fallback; lazily init CloseNotify; add ServeFile; remove CSS debug logging.
Assetserver tests & benches
v3/internal/assetserver/assetserver_bench_test.go, v3/internal/assetserver/mimetype_stdlib_test.go
Add comprehensive benchmarks and unit tests validating stdlib-based MIME detection and extension map completeness.
HTTP transport pooling
v3/pkg/application/transport_http.go
Add package-level bufferPool and cap; read request bodies into pooled buffers before JSON parse to reduce allocations.
Desktop file parser & replacement
v3/internal/fileexplorer/desktopfile.go, v3/internal/fileexplorer/desktopfile_test.go, v3/internal/fileexplorer/fileexplorer_linux.go
Add minimal .desktop parser + tests; remove INI dependency and use parser in file explorer.
Small utilities & lo removal
v3/internal/sliceutil/*, various files
Add sliceutil.Unique and sliceutil.FindMapKey; replace samber/lo uses with stdlib or sliceutil.
Bench suites (various)
v3/pkg/application/*bench_test.go, v3/internal/.../*bench_test.go
Many bench-only files (bench build-tagged) for JSON libs, events, window, system tray, bindings, and concurrency.
Platform & service JSON swaps
v3/pkg/.../*.go, v3/internal/*/*
Multiple platform/service files updated to import go-json; no exported signature changes.
Win32 UTF-16 helpers
v3/pkg/w32/window.go
Add StringToUTF16, MustStringToUTF16, MustStringToUTF16uintptr; remove lo dependency.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant AssetServer
  participant ExtMap as mimeTypesByExt
  participant Cache as mimeCache (sync.Map)
  participant Detector as http.DetectContentType

  Client->>AssetServer: Request resource (filename + data)
  AssetServer->>ExtMap: lookup by extension (fast path)
  alt extension hit
    ExtMap-->>AssetServer: mimeType
    AssetServer->>Client: respond with Content-Type
  else extension miss
    AssetServer->>Cache: Load(key)
    alt cache hit
      Cache-->>AssetServer: cached mimeType
      AssetServer->>Client: respond with Content-Type
    else cache miss
      AssetServer->>Detector: DetectContentType(data)
      Detector-->>AssetServer: mimeType
      AssetServer->>Cache: Store(key, mimeType)
      AssetServer->>Client: respond with Content-Type
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Enhancement, Documentation, Implemented in v3, Windows, MacOS, Linux, v3-alpha, size:XXL, bindings

Suggested reviewers

  • ndianabasi

Poem

🐇 I tucked args in stacks so hops stay light,
I pooled my buffers and scurried out of sight,
Sync.Map hums softly as MIME maps sing,
Faster JSON wings — hear the benchmarks spring,
Small hops, big gains — a rabbit’s delight!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provides a structured summary with tables, benchmark results, and clear instructions for running tests. However, it lacks sections required by the template such as issue reference (Fixes #), type of change checkbox, test coverage details, and wails doctor output. Add 'Fixes #' reference, check the appropriate type of change box, document test coverage (Windows/macOS/Linux), provide wails doctor output, and confirm checklist items (changelog, documentation, warnings, unit tests).
Docstring Coverage ⚠️ Warning Docstring coverage is 62.05% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main optimization focus (JSON processing and allocation reduction) and aligns with the core changes across bindings, asset server, and HTTP transport.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (14)
v3/pkg/application/systemtray_bench_test.go (5)

39-61: Move icon allocations outside benchmark loops for accuracy.

The icon byte slice allocations at lines 41, 49, and 57 occur inside the benchmark loops, which includes allocation overhead in the measurements. For more accurate benchmarks that isolate the SetIcon, SetDarkModeIcon, and SetTemplateIcon operations, move these allocations before the loop (similar to line 71 in the ChainedConfiguration test).

🔎 Proposed refactor
 	b.Run("SetIcon", func(b *testing.B) {
 		tray := newSystemTray(1)
+		icon := make([]byte, 1024) // 1KB icon data
-		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() {
-			icon := make([]byte, 1024)
 			tray.SetDarkModeIcon(icon)
 		}
 	})

 	b.Run("SetTemplateIcon", func(b *testing.B) {
 		tray := newSystemTray(1)
+		icon := make([]byte, 1024)
 		for b.Loop() {
-			icon := make([]byte, 1024)
 			tray.SetTemplateIcon(icon)
 		}
 	})

209-223: Remove unused submenu variable.

The submenu variable created at lines 214-217 is never used. It's populated with items but never attached to the main menu or passed to SetMenu. Remove this dead code or add it to the menu if it was intended to be used.

🔎 Proposed fix
 	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)
 		}
 	})

227-251: Consider moving tray creation after ResetTimer for consistency.

The tray is created before b.ResetTimer() at line 243, which means its creation overhead is excluded from the measurement. While this is acceptable since creation happens only once, other benchmarks in this file create the tray inside the loop or have different patterns. For consistency, you could move it after ResetTimer().

🔎 Proposed refactor
 	for _, size := range sizes {
 		b.Run(size.name, func(b *testing.B) {
 			icon := make([]byte, size.size)
-			tray := newSystemTray(1)
 
 			b.ResetTimer()
+			tray := newSystemTray(1)
 			for b.Loop() {
 				tray.SetIcon(icon)
 			}
 		})
 	}

266-277: Consider removing unexported fields for realistic benchmarking.

The FullConfig benchmark at lines 272-273 initializes unexported fields (justClosed, hasBeenShown) which user code cannot set. This makes the benchmark less representative of actual usage patterns. Consider removing these fields or documenting why they're included.

🔎 Proposed refactor
 	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
 		}
 	})

306-328: Consider whether nested loops affect benchmark intent.

The nested loop structure (lines 322-326) measures setting all 9 icon positions repeatedly. If the intent is to measure individual SetIconPosition calls, the inner loop may introduce overhead. If measuring bulk operations is intentional, this is fine.

v3/go.mod (1)

11-27: Multiple JSON libraries added for benchmarking purposes.

The PR adds several JSON libraries (bytedance/sonic, go-json-experiment, goccy/go-json, json-iterator). Based on the PR objectives, goccy/go-json is the primary replacement for encoding/json in production code, while others appear to be for benchmark comparisons only.

Consider documenting which library is used for production vs. benchmarking to avoid confusion. If sonic and json-iterator are only for benchmarks, they could potentially be moved to a separate module or clearly documented.

v3/pkg/application/window_bench_test.go (2)

90-120: Event dispatch benchmark only measures map lookup, not callback invocation.

The BenchmarkWindowEventDispatch benchmark only measures the lock acquisition and map lookup, but doesn't actually invoke the registered callbacks. This may not accurately represent real dispatch performance.

Consider whether this is intentional (measuring only the lookup overhead) or if callback invocation should be included for a more complete picture.


219-245: Unbounded slice growth in MixedReadWrite benchmark.

The MixedReadWrite benchmark appends to eventListeners[eventID] on write operations without any cleanup. Over many iterations, this slice will grow unbounded, potentially affecting memory measurements and GC behavior in later iterations.

Consider adding a cleanup mechanism or capping the slice size to maintain consistent benchmark conditions.

🔎 Suggested fix
 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()
+				// Cap slice size to prevent unbounded growth
+				if len(w.eventListeners[eventID]) > 100 {
+					w.eventListeners[eventID] = w.eventListeners[eventID][:0]
+				}
 				w.eventListeners[eventID] = append(w.eventListeners[eventID], &WindowEventListener{
 					callback: func(event *WindowEvent) {},
 				})
 				w.eventListenersLock.Unlock()
v3/pkg/application/events_bench_test.go (1)

271-287: WaitGroup synchronization in Once benchmark may skew measurements.

The BenchmarkOnceEvent includes sync.WaitGroup synchronization within the benchmark loop. This measures both the event system overhead AND the synchronization overhead together, which may not isolate the Once registration/trigger cost accurately.

If the goal is to measure pure event system performance, consider restructuring to separate synchronization overhead, or document that this benchmark includes synchronization costs.

v3/pkg/application/bindings_optimized_bench_test.go (3)

37-43: CallError pool objects are not returned to the pool.

The callErrorPool is used to get CallError objects on error paths (lines 55, 86), but these objects are never returned to the pool. Since errors are returned to the caller, they can't be pooled safely in this pattern.

Consider removing the callErrorPool since pooling error objects that escape the function scope doesn't provide benefits and adds complexity.

🔎 Suggested fix
-	// Pool for CallError structs
-	callErrorPool = sync.Pool{
-		New: func() any {
-			return &CallError{}
-		},
-	}

And update error creation to use direct allocation:

-		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
+		return nil, &CallError{
+			Kind:    TypeError,
+			Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount),
+		}

118-128: Logic issue: nonErrorOutputs set to nil but checked afterwards.

When an error is encountered (line 120-123), nonErrorOutputs is set to nil. However, the subsequent else if nonErrorOutputs != nil check (line 125) will always be false after the first error, causing non-error outputs after errors to be silently dropped.

This may be intentional (stop collecting results after first error), but the logic is confusing. The production Call method handles this differently.


208-217: Duplicate benchService type definition.

There's a benchService type defined here (lines 209-217) and a BenchmarkService type in bindings_bench_test.go. While they're in different packages (application vs application_test), having similar types with different names could be confusing.

Consider consolidating or clearly differentiating these test service types.

v3/pkg/application/bindings_bench_test.go (1)

104-111: BenchmarkMethodBinding/MultipleServices tests error path.

Line 108 adds &BenchPerson{} as a service, but BenchPerson is a plain struct with no methods. The comment "Will fail but tests the path" clarifies this is intentional.

However, silently ignoring the error from bindings.Add means the benchmark doesn't verify the expected behavior. Consider either:

  1. Checking the error is non-nil
  2. Using a proper service type for the multi-service benchmark
🔎 Suggested fix
 	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
+			// Note: BenchPerson has no methods, so this tests the error path
+			err := bindings.Add(application.NewService(&BenchPerson{}))
+			if err == nil {
+				b.Fatal("expected error when adding struct with no methods")
+			}
 		}
 	})
v3/pkg/application/bindings.go (1)

322-352: Optimized result collection minimizes allocations.

The result processing logic uses a two-phase approach:

  1. Track first non-error result without slice allocation
  2. Only allocate slice if there are multiple non-error results

This avoids slice allocation for the common case of 0-1 return values.

However, the logic is complex and could benefit from a clarifying comment about the state machine:

  • hasFirstResult=false: no results yet
  • hasFirstResult=true, nonErrorOutputs=nil: exactly one result in firstResult
  • nonErrorOutputs!=nil: two or more results in slice
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3256041 and 904d265.

⛔ Files ignored due to path filters (1)
  • v3/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • v3/UNRELEASED_CHANGELOG.md
  • v3/go.mod
  • v3/internal/assetserver/assetserver_bench_test.go
  • v3/internal/assetserver/common.go
  • v3/internal/assetserver/content_type_sniffer.go
  • v3/internal/assetserver/mimecache.go
  • v3/pkg/application/bindings.go
  • v3/pkg/application/bindings_bench_test.go
  • v3/pkg/application/bindings_optimized_bench_test.go
  • v3/pkg/application/events.go
  • v3/pkg/application/events_bench_test.go
  • v3/pkg/application/json_libs_bench_test.go
  • v3/pkg/application/json_v2_bench_test.go
  • v3/pkg/application/messageprocessor_args.go
  • v3/pkg/application/messageprocessor_call.go
  • v3/pkg/application/systemtray_bench_test.go
  • v3/pkg/application/transport_http.go
  • v3/pkg/application/window_bench_test.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-02-13T01:05:02.267Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4066
File: v3/pkg/application/messageprocessor_call.go:174-174
Timestamp: 2025-02-13T01:05:02.267Z
Learning: When handling JSON marshaling errors in Wails v3, the error message from json.Marshal provides sufficient debugging context. Logging raw data is unnecessary and could make logs harder to read.

Applied to files:

  • v3/pkg/application/messageprocessor_call.go
  • v3/pkg/application/json_v2_bench_test.go
📚 Learning: 2025-01-15T22:30:08.796Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/no_bindings_here/person.go:9-9
Timestamp: 2025-01-15T22:30:08.796Z
Learning: When dealing with fixed-length arrays in Go structs, encoding/json automatically handles bounds checking during unmarshaling, returning an error if the JSON array length exceeds the Go array length. No additional runtime validation is needed.

Applied to files:

  • v3/pkg/application/messageprocessor_call.go
📚 Learning: 2025-02-04T23:59:43.956Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4045
File: v3/internal/generator/render/info.go:28-68
Timestamp: 2025-02-04T23:59:43.956Z
Learning: In the Wails v3 project, internal functions are designed to panic on nil parameters as they represent contract violations, rather than adding defensive nil checks.

Applied to files:

  • v3/go.mod
📚 Learning: 2024-09-30T06:13:46.595Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-09-30T06:13:46.595Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/pkg/application/window_bench_test.go
📚 Learning: 2025-12-29T08:02:06.122Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:06.122Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.

Applied to files:

  • v3/pkg/application/window_bench_test.go
📚 Learning: 2024-09-30T06:14:32.602Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/internal/commands/appimage_testfiles/main.go:295-299
Timestamp: 2024-09-30T06:14:32.602Z
Learning: In `v3/internal/commands/appimage_testfiles/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/pkg/application/window_bench_test.go
🧬 Code graph analysis (8)
v3/pkg/application/events_bench_test.go (2)
v3/pkg/application/events.go (2)
  • CustomEvent (51-60)
  • NewWailsEventProcessor (109-115)
v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts (4)
  • On (127-129)
  • Off (147-149)
  • Once (138-140)
  • OnMultiple (112-118)
v3/pkg/application/transport_http.go (1)
v3/pkg/errs/error_functions.gen.go (1)
  • WrapInvalidRuntimeCallErrorf (365-376)
v3/pkg/application/bindings_optimized_bench_test.go (1)
v3/pkg/application/bindings.go (6)
  • CallError (36-40)
  • BoundMethod (74-85)
  • TypeError (30-30)
  • RuntimeError (31-31)
  • NewBindings (94-101)
  • CallOptions (20-24)
v3/internal/assetserver/content_type_sniffer.go (1)
v3/internal/assetserver/webview/responsewriter.go (1)
  • ResponseWriter (20-28)
v3/pkg/application/systemtray_bench_test.go (1)
v3/pkg/application/systemtray.go (11)
  • NSImageLeading (22-22)
  • WindowAttachConfig (265-284)
  • IconPosition (12-12)
  • NSImageNone (15-15)
  • NSImageOnly (16-16)
  • NSImageLeft (17-17)
  • NSImageRight (18-18)
  • NSImageBelow (19-19)
  • NSImageAbove (20-20)
  • NSImageOverlaps (21-21)
  • NSImageTrailing (23-23)
v3/pkg/application/window_bench_test.go (4)
v3/pkg/application/webview_window.go (4)
  • WebviewWindow (143-177)
  • WindowEventListener (139-141)
  • WindowEvent (118-121)
  • NewWindowEvent (127-129)
v3/pkg/application/messageprocessor_window.go (1)
  • WindowFocus (16-16)
v3/pkg/events/events.go (1)
  • WindowEventType (4-4)
v3/pkg/application/webview_window_options.go (1)
  • WebviewWindowOptions (32-143)
v3/pkg/application/json_v2_bench_test.go (1)
v2/internal/binding/generate_test.go (1)
  • B (22-24)
v3/pkg/application/bindings.go (3)
v3/pkg/application/messageprocessor_args.go (1)
  • Args (9-11)
v3/pkg/errs/utils.go (1)
  • Cause (20-40)
v3/internal/hash/fnv.go (1)
  • Fnv (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (go)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (34)
v3/pkg/application/systemtray_bench_test.go (7)

1-12: LGTM! Clear documentation of benchmark limitations.

The build tag correctly gates the benchmarks, and the comment appropriately sets expectations about the scope of these tests.


14-20: LGTM! Clean benchmark using the modern Loop API.


81-150: LGTM! Comprehensive handler benchmarks.

The tests appropriately cover both registration and invocation patterns. Direct field access is acceptable within the same package for testing purposes.


152-185: LGTM! Window attachment benchmarks are well-structured.


280-304: LGTM! Show/Hide benchmarks are clear and well-documented.


330-355: LGTM! Label operation benchmarks are well-structured.

The proper use of ResetTimer() in the GetLabel benchmark ensures accurate measurements.


357-374: LGTM! Default click handler benchmarks appropriately test early return paths.

v3/internal/assetserver/common.go (1)

27-38: LGTM! Efficient optimization for blob writing.

The change from io.Copy(rw, bytes.NewReader(blob)) to rw.Write(blob) eliminates unnecessary Reader allocation and io.Copy overhead, which aligns with the PR's performance goals.

v3/pkg/application/events.go (1)

3-13: Library swap looks good.

The change from encoding/json to goccy/go-json maintains API compatibility while targeting the performance improvements mentioned in the PR objectives. The usage in CustomEvent.ToJSON() and decodeEventData() remains unchanged.

Based on learnings, the error handling from json.Marshal provides sufficient context, so no additional changes are needed there.

v3/pkg/application/json_v2_bench_test.go (2)

71-76: Critical: Invalid benchmark loop syntax.

The code uses b.Loop() which is not a valid method on testing.B. Use for i := 0; i < b.N; i++ or for range b.N (Go 1.22+) instead.

This issue affects all benchmark functions in this file (lines 72, 79, 86, 93, 100, 107, 116, 122, 128, 134, 166, 173, 180, 187, 198, 205, 221, 234, 261, 267).

🔎 Proposed fix
 func BenchmarkJSONv1_Unmarshal_Simple(b *testing.B) {
-	for b.Loop() {
+	for i := 0; i < b.N; i++ {
 		var arg SimpleArg
 		_ = json.Unmarshal(simpleArgJSON, &arg)
 	}
 }

Apply this pattern to all benchmark functions in the file.

⛔ Skipped due to learnings
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/no_bindings_here/person.go:9-9
Timestamp: 2025-01-15T22:30:08.796Z
Learning: When dealing with fixed-length arrays in Go structs, encoding/json automatically handles bounds checking during unmarshaling, returning an error if the JSON array length exceeds the Go array length. No additional runtime validation is needed.

1-10: Note: Experimental JSON v2 dependency.

This benchmark uses encoding/json/v2 which requires the goexperiment.jsonv2 build tag. This is experimental and not yet stable in the Go standard library.

v3/pkg/application/messageprocessor_args.go (1)

3-7: Code change is correct and API-compatible.

The switch to github.com/goccy/go-json in messageprocessor_args.go is properly implemented. The usage of json.RawMessage and json.Unmarshal aligns with goccy/go-json's API compatibility with the standard library. The migration has been consistently applied across the hot-path files (bindings.go, events.go, transport_http.go, messageprocessor_call.go, and messageprocessor_args.go).

v3/internal/assetserver/content_type_sniffer.go (2)

7-16: LGTM! Lazy allocation correctly implemented.

The removal of eager channel allocation from the constructor, combined with the updated field comment, correctly implements the lazy allocation pattern. This avoids allocating a channel for every request when most won't use the deprecated CloseNotify interface.


119-133: LGTM! Proper nil checks in place.

The lazy allocation in CloseNotify() and the nil check in closeClient() are correctly implemented. This ensures the channel is only allocated when actually needed and prevents nil-channel operations.

v3/pkg/application/transport_http.go (2)

18-23: LGTM! Buffer pool correctly implemented.

The sync.Pool implementation for buffer reuse is correct and will reduce allocations. The initial capacity of 4096 bytes is reasonable for typical runtime call payloads.


87-104: LGTM! Correct buffer pool usage pattern.

The buffer acquisition, reset, and return sequence follows best practices for sync.Pool:

  • Buffer is obtained and immediately reset
  • defer ensures the buffer is returned to the pool
  • io.Copy populates the buffer efficiently
  • buf.Bytes() provides the data for unmarshaling

The same JSON library verification comment from messageprocessor_call.go applies here.

v3/internal/assetserver/mimecache.go (2)

11-14: LGTM! sync.Map is appropriate for this workload.

Using sync.Map for the MIME type cache is a good choice for this read-heavy workload where MIME type lookups are far more common than new MIME type discoveries. This eliminates lock contention on the read path.


41-67: LGTM! Efficient tiered lookup strategy.

The function implements a good tiered lookup strategy:

  1. Fast path: extension-based lookup (no synchronization needed)
  2. Medium path: sync.Map.Load for cached values (lock-free read)
  3. Slow path: MIME type detection and caching

The type assertion on Line 49 (cached.(string)) is safe since only strings are stored in the cache within this codebase.

v3/UNRELEASED_CHANGELOG.md (1)

23-30: LGTM! Changelog accurately documents performance improvements.

The changelog entries clearly and concisely document all the performance optimizations in this PR:

  • JSON library switch with performance metrics
  • BoundMethod optimizations
  • Buffer pooling and lazy allocations
  • MIME cache improvements

The entries follow the "Keep a Changelog" format and provide clear value to users.

v3/pkg/application/messageprocessor_call.go (1)

7-7: No critical concerns with goccy/go-json v0.10.5 for this use case.

The library is a drop-in replacement for encoding/json with zero reported CVEs (Snyk, Jan 2026) and a recent stable release (v0.10.5, Jan 25, 2025). The codebase usage pattern in messageprocessor_call.go (basic Marshal/Unmarshal operations) aligns with the library's core compatibility promise. While the library has extended APIs and optional build-tag features, the standard Marshal/Unmarshal operations are fully compatible. Recommend running the existing test suite to confirm no regressions in this codebase's specific JSON workflows.

v3/internal/assetserver/assetserver_bench_test.go (4)

16-18: Direct reassignment of sync.Map for cache reset is correct.

The resetMimeCache() function directly reassigns the package-level mimeCache variable to a new sync.Map{}. This is a valid approach for benchmark isolation since sync.Map doesn't have a Clear() method.


21-83: Comprehensive MIME type benchmarks with proper isolation.

Good benchmark coverage including:

  • Extension-based lookups (JS, CSS, HTML, JSON)
  • Content detection (Unknown, PNG magic bytes)
  • Cache hit performance

The resetMimeCache() calls between sub-benchmarks ensure proper isolation.


97-133: Consider reusing request objects in HTTP benchmarks.

In BenchmarkAssetServerServeHTTP, the httptest.NewRequest is created once outside the loop for SimpleRequest, but httptest.NewRecorder() is created inside each iteration. This is correct since the recorder accumulates state.

However, the request object could potentially be reused across iterations if it's not mutated by ServeHTTP. Verify that request reuse is safe here.


162-196: Content type sniffer benchmarks look good.

The benchmarks test various response sizes (small, HTML, large 64KB) which covers realistic scenarios. The pattern of creating a new recorder and sniffer per iteration is correct for accurate measurements.

v3/pkg/application/window_bench_test.go (1)

415-442: RWMutex pattern benchmarks provide useful baseline comparisons.

These micro-benchmarks comparing RLock/RUnlock, Lock/Unlock, and deferred unlock patterns are helpful for understanding the overhead of different locking strategies used throughout the codebase.

v3/pkg/application/events_bench_test.go (2)

14-21: Mock dispatcher implementation is correct for benchmarking.

Using atomic.Int64 for counting dispatch calls is appropriate for concurrent benchmarks and avoids lock contention that would skew measurements.


156-185: Concurrent emit benchmark correctly uses SetParallelism.

b.SetParallelism(concurrency) sets the number of goroutines per GOMAXPROCS for RunParallel, which is the correct way to test different concurrency levels. The event object is created per-goroutine which avoids shared state issues.

v3/pkg/application/bindings_bench_test.go (2)

1-14: Well-structured benchmark file with comprehensive coverage.

The file provides thorough benchmark coverage including:

  • Service binding registration
  • Method lookup (by ID and name)
  • Various call patterns (simple, complex, variadic, context)
  • JSON marshal/unmarshal overhead
  • Hash computation
  • Alias resolution
  • Error paths
  • Scaling characteristics

465-470: No issue with WrongArgType benchmark.

The benchmark correctly produces a type error when passing a JSON number where a string is expected. Both goccy/go-json and encoding/json return identical errors for type mismatches: "json: cannot unmarshal number into Go value of type string". The BoundMethod.Call method properly captures and returns this error as a CallError.

v3/pkg/application/bindings.go (4)

284-291: Stack-allocated buffer optimization is correct.

Using a stack-allocated [8]reflect.Value array for methods with ≤8 arguments avoids heap allocation for the common case. The fallback to make() for larger argument counts ensures correctness.

This is a solid optimization that aligns with the PR's goal of reducing allocations in hot paths.


214-225: isVariadic caching at registration eliminates per-call reflection.

Caching methodType.IsVariadic() at registration time and storing it in BoundMethod.isVariadic avoids a reflect call on every method invocation. This is a good micro-optimization for the hot path.


334-336: errorOutputs capacity estimation could be more accurate.

The capacity len(callResults) for errorOutputs is an overestimate since some results will be non-errors. However, this is a minor issue since:

  1. Error paths are not the hot path
  2. Over-allocating slightly is better than reallocating

The optimization for the non-error path is more important.


11-11: Swap to goccy/go-json for JSON performance.

The import changes encoding/json to github.com/goccy/go-json, a drop-in replacement offering significantly faster JSON operations. Ensure your test suite runs against this change, particularly for custom Marshal/Unmarshal implementations, time/date handling, number formatting, and streaming APIs where subtle behavioral differences may occur. If incompatibilities arise, consider adding a fallback to encoding/json.

v3/go.mod (1)

3-3: No action needed. Go 1.25 is the current stable version (go1.25.5 released December 2, 2025).

Comment thread v3/pkg/application/bindings_optimized_bench_test.go Outdated
Comment on lines +5 to +12
import (
"encoding/json"
"testing"

"github.com/bytedance/sonic"
gojson "github.com/goccy/go-json"
jsoniter "github.com/json-iterator/go"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "json_libs_bench_test.go" -type f

Repository: wailsapp/wails

Length of output: 102


🏁 Script executed:

cat -n ./v3/pkg/application/json_libs_bench_test.go

Repository: wailsapp/wails

Length of output: 9327


🏁 Script executed:

find . -name "*test*.go" -path "*/application/*" -type f | head -20

Repository: wailsapp/wails

Length of output: 1118


🏁 Script executed:

grep -r "sonic\|goccy\|jsoniter" ./v3/pkg/application/ --include="*test.go" | grep -v "bench_test" | head -20

Repository: wailsapp/wails

Length of output: 40


🏁 Script executed:

find . -name "bindings.go" -path "*/application/*" -type f

Repository: wailsapp/wails

Length of output: 90


🏁 Script executed:

wc -l ./v3/pkg/application/bindings.go && sed -n '280,300p' ./v3/pkg/application/bindings.go

Repository: wailsapp/wails

Length of output: 521


🏁 Script executed:

sed -n '1,50p' ./v3/pkg/application/bindings.go && echo "---" && sed -n '280,310p' ./v3/pkg/application/bindings.go

Repository: wailsapp/wails

Length of output: 1936


🌐 Web query:

sonic json library Go compatibility standard library

💡 Result:

Short answer: Sonic is largely API-compatible with Go's encoding/json but not a drop-in behavioral match in every edge case. It exposes a stdlib-compatible configuration (ConfigStd / sonic.API) and will fall back to encoding/json on unsupported platforms or Go versions; however Sonic uses JIT/SIMD optimizations so some behaviors and requirements differ (Go version/arch constraints, map-key ordering, some interface{}/map[any]any edge cases, numeric handling, and the need for Pretouch to avoid JIT first-hit costs). Configure Sonic for “std” compatibility when you need closer parity with encoding/json. [1][2][3]

Relevant sources:

  • Sonic README (requirements, compatibility, ConfigStd). [1]
  • sonic package docs (ConfigStd, fallback to encoding/json, Pretouch notes). [2]
  • CloudWeGo docs (how frameworks use Sonic and fallback/conditional build behavior). [3]

Add compatibility validation to ensure JSON libraries produce identical results.

The benchmark compares sonic, goccy/go-json, and json-iterator against the standard library, but doesn't verify that unmarshaling/marshaling produces identical results. Web research confirms that Sonic, in particular, has documented compatibility differences (platform constraints, map ordering, interface{}/numeric handling edge cases) and requires explicit stdlib-compatibility configuration that isn't present in the current benchmarks. Consider adding result validation (unmarshal → marshal round-trip comparison) for the test data to confirm these libraries are true behavioral equivalents, or document intentional differences if they're meant to be performance alternatives rather than drop-in replacements.

Comment on lines +59 to +64
func BenchmarkUnmarshal_Simple_StdLib(b *testing.B) {
for b.Loop() {
var arg SimpleBindingArg
_ = json.Unmarshal(simpleJSON, &arg)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Invalid benchmark loop syntax.

The code uses b.Loop() which is not a valid method on testing.B. Standard Go benchmarks must use for i := 0; i < b.N; i++ or (in Go 1.22+) for range b.N.

This issue affects all benchmark functions in this file and will prevent the benchmarks from compiling.

🔎 Proposed fix
 func BenchmarkUnmarshal_Simple_StdLib(b *testing.B) {
-	for b.Loop() {
+	for i := 0; i < b.N; i++ {
 		var arg SimpleBindingArg
 		_ = json.Unmarshal(simpleJSON, &arg)
 	}
 }

Apply this pattern to all benchmark functions in the file (lines 60, 67, 74, 81, 90, 97, 104, 111, 120, 127, 134, 141, 150, 157, 164, 171, 180, 192, 205, 218, 263, 270, 277, 282, 289, 295, 301, 307).

🤖 Prompt for AI Agents
In v3/pkg/application/json_libs_bench_test.go around lines 59 to 64 (and
similarly at the other listed benchmark locations), the benchmarks use the
invalid b.Loop() call; replace each occurrence with a standard Go benchmark loop
such as for i := 0; i < b.N; i++ (or for range b.N in Go 1.22+) so each
benchmark iterates b.N times; apply this change to all benchmark functions
mentioned in the review (lines 60, 67, 74, 81, 90, 97, 104, 111, 120, 127, 134,
141, 150, 157, 164, 171, 180, 192, 205, 218, 263, 270, 277, 282, 289, 295, 301,
307) to restore valid compilation and correct benchmarking behavior.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jan 1, 2026

Copy link
Copy Markdown

Deploying wails with  Cloudflare Pages  Cloudflare Pages

Latest commit: 946b480
Status: ✅  Deploy successful!
Preview URL: https://7172b183.wails.pages.dev
Branch Preview URL: https://v3-performance-improvements.wails.pages.dev

View logs

Buffers larger than 512KB are not returned to the pool, allowing GC
to reclaim memory after large requests (e.g., base64 encoded images).
- 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)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
v3/internal/assetserver/mimecache.go (1)

102-105: Type assertion without safety check.

The type assertion cached.(string) on line 104 will panic if the stored value is not a string. While this is unlikely given the controlled Store call, defensive coding would use the comma-ok idiom.

🔎 Proposed safer type assertion
 	// Check cache (lock-free read)
 	if cached, ok := mimeCache.Load(filename); ok {
-		return cached.(string)
+		if s, ok := cached.(string); ok {
+			return s
+		}
 	}
v3/internal/assetserver/mimetype_stdlib_test.go (2)

95-170: Duplication: extMimeTypes duplicates mimeTypesByExt from mimecache.go.

The extMimeTypes map in this test file is an exact copy of mimeTypesByExt from mimecache.go. This creates a maintenance burden where changes to one must be manually synchronized to the other, risking drift.

Consider either:

  1. Testing against the actual mimeTypesByExt (which TestMimeTypeExtensionMapCompleteness already does)
  2. Removing extMimeTypes and using mimeTypesByExt directly in getMimeTypeStdlib
🔎 Proposed fix to use mimeTypesByExt directly
 // 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 != "" {
+	if result := mimeTypesByExt[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{
-	// ... entire map can be removed ...
-}

70-77: Tests use getMimeTypeStdlib instead of actual GetMimetype function.

The test validates getMimeTypeStdlib (a local test helper) rather than the actual GetMimetype function from mimecache.go. While this tests the stdlib approach in isolation, it doesn't validate the production code. Consider adding tests that exercise GetMimetype directly.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39cdeb2 and 43abc93.

⛔ Files ignored due to path filters (1)
  • v3/go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • v3/UNRELEASED_CHANGELOG.md
  • v3/go.mod
  • v3/internal/assetserver/mimecache.go
  • v3/internal/assetserver/mimetype_stdlib_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/go.mod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
v3/internal/assetserver/mimecache.go (2)

107-113: Race condition: concurrent cache misses may perform redundant detection.

When multiple goroutines concurrently request the same uncached filename, each will independently call http.DetectContentType and then Store. While sync.Map.Store is safe, this results in redundant work. This is acceptable for performance (no lock contention), but worth noting.

The pattern is a valid trade-off for read-heavy workloads. No change needed, just documenting the behavior.


96-114: Cache design is appropriate for static assets, but document the assumption.

The observation that the cache uses filename as key while detecting content-based MIME types is technically accurate. However, the code comments (lines 14-19) and usage context indicate this function is designed for static asset serving where the filename→content mapping is stable. The concern about "different content served under the same filename returning stale MIME types" assumes dynamic content scenarios, which do not apply here.

If this function is ever used for dynamic content or content that changes per request, the current caching strategy would be inappropriate. Consider adding a comment explicitly documenting that this cache assumes static assets only, or add a parameter to control caching behavior if dynamic content support is needed later.

v3/UNRELEASED_CHANGELOG.md (1)

23-31: Changelog entries are clear and well-documented.

The changelog entries accurately reflect the performance optimizations described in the PR objectives and match the code changes observed in the other files. The entries follow the "Keep a Changelog" format with present tense and clear descriptions of user impact.

v3/internal/assetserver/mimetype_stdlib_test.go (2)

12-78: Comprehensive test coverage for web formats.

Good coverage of common web asset formats including HTML, CSS, JS, images, fonts, audio/video, and edge cases. The use of actual magic bytes for binary format detection is thorough.


265-271: The code is correct; b.Loop() is a valid method and the preferred benchmarking idiom since Go 1.24.

Go 1.24 introduced testing.B.Loop() as the standard way to write benchmarks. It's superior to the older for i := 0; i < b.N; i++ pattern because it automatically manages timer reset/stop, prevents compiler dead-code elimination of the loop body, and enables faster ramp-up. No compilation error will occur.

Likely an incorrect or invalid review comment.

Comment thread v3/internal/assetserver/mimecache.go
Comment thread v3/internal/assetserver/mimetype_stdlib_test.go
Comment thread v3/UNRELEASED_CHANGELOG.md Outdated

## Removed
<!-- Features removed in this release -->
- Remove github.com/wailsapp/mimetype dependency in favor of expanded extension map + stdlib http.DetectContentType, reducing binary size by ~208KB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor inconsistency: binary size reduction figure.

The changelog states "~208KB" reduction, but the PR objectives mention "~1.2 MB" reduction (from 11.0 MB → 9.8 MB for an example test app). The comment in mimecache.go line 19 also says "~208KB". Clarify whether 208KB refers to just the mimetype library size vs. 1.2MB for the full binary impact (which may include other optimizations).

🤖 Prompt for AI Agents
In v3/UNRELEASED_CHANGELOG.md line 41 there's an inconsistent binary size
reduction figure (~208KB) vs the PR note (~1.2 MB) and the same ~208KB appears
in mimecache.go line 19; verify the measured sizes (library-only savings vs full
binary impact) and update the changelog and mimecache.go comment to consistently
reflect both values (e.g., "mimetype library removed: ~208KB; total example app
binary reduction: ~1.2MB") or a single accurate number, ensuring both files use
the same verified phrasing.

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.
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)
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
v3/internal/fileexplorer/desktopfile.go (1)

40-47: Consider handling whitespace-only lines more explicitly.

Lines containing only whitespace will pass the len(line) == 0 check and proceed to subsequent parsing logic. While the current implementation handles this gracefully (they'll fail to match any patterns and be skipped), explicitly handling whitespace-only lines would make the parser more robust and align with typical .desktop file parsing behavior.

🔎 Proposed refinement
 		// Skip empty lines
-		if len(line) == 0 {
+		trimmed := strings.TrimSpace(line)
+		if len(trimmed) == 0 {
 			continue
 		}
 
 		// Skip comments (# at start of line)
-		if line[0] == '#' {
+		if trimmed[0] == '#' {
 			continue
 		}

Note: You'd need to use trimmed consistently throughout the rest of the loop for this change.

v3/pkg/application/events.go (1)

70-77: Pre-existing TODO: Error handling could be improved.

The ToJSON method returns an empty string on marshaling errors with a TODO comment. While this behavior is pre-existing (not introduced by this PR), consider whether this silent failure mode is appropriate or if errors should be logged or propagated to callers.

v3/internal/sliceutil/sliceutil.go (1)

26-36: Minor documentation clarification needed.

The implementation is correct, but the documentation states "returns the first key" which could be misleading since Go map iteration order is non-deterministic. Consider clarifying to:

// FindMapKey returns a key in map m whose value equals val.
// If multiple keys have the same value, which key is returned is undefined.
// Returns the key and true if found, or zero value and false if not found.
v3/pkg/application/bindings.go (1)

321-376: Result collection optimization is correct and effective.

The lazy allocation strategy successfully avoids creating slices for the common single-result case:

  • firstResult/hasFirstResult pattern eliminates allocation for single non-error returns
  • nonErrorOutputs is only allocated when a second non-error result appears
  • Stopping non-error collection once an error is found (Line 337) is correct since errors take precedence

The logic correctly prioritizes errors over results and handles all edge cases properly.

Optional: refine capacity preallocation

Lines 334 and 345 preallocate with cap=len(callResults), but typically there's only 1 error and 1-2 non-error results. Consider using smaller initial capacities:

-errorOutputs = make([]error, 0, len(callResults))
+errorOutputs = make([]error, 0, 1)

-nonErrorOutputs = make([]any, 0, len(callResults))
+nonErrorOutputs = make([]any, 0, 2)

This minor refinement reduces preallocation overhead for typical cases while still allowing the slice to grow if needed.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77248b5 and 3a7fa9c.

⛔ Files ignored due to path filters (1)
  • v3/go.sum is excluded by !**/*.sum
📒 Files selected for processing (13)
  • v3/UNRELEASED_CHANGELOG.md
  • v3/go.mod
  • v3/internal/fileexplorer/desktopfile.go
  • v3/internal/fileexplorer/desktopfile_test.go
  • v3/internal/fileexplorer/fileexplorer_linux.go
  • v3/internal/sliceutil/sliceutil.go
  • v3/internal/sliceutil/sliceutil_test.go
  • v3/pkg/application/bindings.go
  • v3/pkg/application/event_manager.go
  • v3/pkg/application/events.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_window_windows.go
  • v3/pkg/w32/window.go
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-10-17T23:16:11.570Z
Learnt from: Sammy-T
Repo: wailsapp/wails PR: 4570
File: v2/internal/frontend/desktop/linux/window_webkit6.go:97-108
Timestamp: 2025-10-17T23:16:11.570Z
Learning: For webkit_6/GTK4 builds in v2/internal/frontend/desktop/linux/window_webkit6.go, GTK widget creation should not be wrapped in invokeOnMainThread. The activation mechanism (activateWg + onActivate export) already handles thread safety, and additional wrapping would cause issues.

Applied to files:

  • v3/pkg/application/webview_window_windows.go
📚 Learning: 2025-12-29T08:02:06.122Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:06.122Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.

Applied to files:

  • v3/pkg/application/webview_window_windows.go
  • v3/pkg/application/webview_window.go
📚 Learning: 2024-09-20T23:34:29.841Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/examples/keybindings/main.go:16-17
Timestamp: 2024-09-20T23:34:29.841Z
Learning: In the codebase, `application.Options.KeyBindings` uses the `application.Window` type, whereas `application.WebviewWindowOptions.KeyBindings` uses `*application.WebviewWindow`. This is intentional and acceptable.

Applied to files:

  • v3/pkg/application/webview_window_windows.go
  • v3/pkg/application/webview_window.go
📚 Learning: 2025-01-15T22:40:58.594Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/aliases/main.go:65-68
Timestamp: 2025-01-15T22:40:58.594Z
Learning: Files in `v3/internal/generator/testcases` are test dummies used for testing purposes only. They are never compiled into the application and should not be subject to the same level of code review as production code.

Applied to files:

  • v3/internal/fileexplorer/desktopfile_test.go
📚 Learning: 2025-02-04T23:59:43.956Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4045
File: v3/internal/generator/render/info.go:28-68
Timestamp: 2025-02-04T23:59:43.956Z
Learning: In the Wails v3 project, internal functions are designed to panic on nil parameters as they represent contract violations, rather than adding defensive nil checks.

Applied to files:

  • v3/go.mod
  • v3/pkg/application/webview_window.go
📚 Learning: 2025-12-13T19:52:13.812Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4783
File: v3/pkg/events/events.go:72-100
Timestamp: 2025-12-13T19:52:13.812Z
Learning: In Wails v3, the linux:WindowLoadChanged event was intentionally removed as a breaking change and replaced with four granular WebKit2 load events: linux:WindowLoadStarted, linux:WindowLoadRedirected, linux:WindowLoadCommitted, and linux:WindowLoadFinished. Users should migrate to linux:WindowLoadFinished for detecting when the WebView has finished loading.

Applied to files:

  • v3/pkg/application/webview_window.go
📚 Learning: 2025-01-15T22:30:08.796Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/no_bindings_here/person.go:9-9
Timestamp: 2025-01-15T22:30:08.796Z
Learning: When dealing with fixed-length arrays in Go structs, encoding/json automatically handles bounds checking during unmarshaling, returning an error if the JSON array length exceeds the Go array length. No additional runtime validation is needed.

Applied to files:

  • v3/pkg/application/webview_window.go
🧬 Code graph analysis (5)
v3/pkg/application/event_manager.go (1)
v3/pkg/application/application.go (1)
  • EventListener (34-36)
v3/internal/fileexplorer/fileexplorer_linux.go (1)
v3/internal/fileexplorer/desktopfile.go (1)
  • ParseDesktopFile (20-27)
v3/pkg/application/webview_window_windows.go (3)
v3/pkg/w32/constants.go (4)
  • HWND_TOPMOST (2239-2239)
  • HWND_NOTOPMOST (2237-2237)
  • GWL_STYLE (316-316)
  • GWL_EXSTYLE (315-315)
v3/pkg/w32/user32.go (3)
  • SetWindowPos (1066-1077)
  • SetWindowLongPtr (608-615)
  • GetWindowLongPtr (625-631)
v3/internal/sliceutil/sliceutil.go (1)
  • Unique (11-24)
v3/internal/sliceutil/sliceutil_test.go (1)
v3/internal/sliceutil/sliceutil.go (2)
  • Unique (11-24)
  • FindMapKey (28-36)
v3/internal/fileexplorer/desktopfile_test.go (1)
v3/internal/fileexplorer/desktopfile.go (1)
  • ParseDesktopReader (30-92)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (32)
v3/pkg/w32/window.go (1)

191-198: LGTM!

The explicit error handling correctly replaces the lo.Must pattern and is consistent with the Must* naming convention.

v3/go.mod (3)

3-3: Confirm Go 1.25 compatibility across all changes.

The Go toolchain was bumped from 1.24.0 to 1.25. Verify that all code changes (method binding optimizations, JSON processing, asset server changes) are compatible with Go 1.25 and that this version was explicitly tested.


172-172: Confirm purpose of howett.net/plist direct dependency.

howett.net/plist was moved to the direct dependencies section. The PR mentions removal of external mimetype dependency and a purpose-built .desktop parser. Verify this plist dependency aligns with those refactorings and is actually used in the updated code.


11-11: Review three JSON library benchmark dependencies for cleanup.

Only goccy/go-json is used in production code across 20+ files. The other three are test-only dependencies:

  • github.com/bytedance/sonic v1.14.2 — used only in json_libs_bench_test.go
  • github.com/go-json-experiment/json — used only in bindings_optimized_bench_test.go
  • github.com/json-iterator/go v1.1.12 — used only in json_libs_bench_test.go

These benchmark dependencies should be reviewed: either remove them if benchmarking is complete, or properly scope them to avoid including them in the production binary. Keeping them in direct requires adds unnecessary build and maintenance complexity.

v3/internal/fileexplorer/desktopfile.go (3)

70-84: LGTM! Good balance between spec compliance and leniency.

The key=value parsing correctly trims whitespace from keys while preserving intentional whitespace in values. The comment on line 77 clearly documents this design decision. The handling of potential multiple Exec lines (line 83) shows good defensive programming.


49-63: Efficient section parsing with early exit optimization.

The section header detection correctly implements case-sensitive matching per the Desktop Entry Specification. The early exit at line 60 when leaving the [Desktop Entry] section is a good performance optimization since only one field is needed.


17-27: LGTM! Clean file I/O handling.

The function follows standard Go patterns with proper resource management and error propagation. The separation of concerns between file I/O (ParseDesktopFile) and parsing logic (ParseDesktopReader) enables easy testing.

v3/internal/fileexplorer/fileexplorer_linux.go (1)

39-54: LGTM! Clean migration from ini-based parsing to custom parser.

The replacement of ini.Load with ParseDesktopFile is straightforward and preserves the existing error handling behavior. The variable renaming (desktopFiledesktopFilePath, execexecCmd) improves code clarity. The field code replacement logic remains functionally identical.

v3/pkg/application/webview_window.go (3)

774-776: LGTM! Correct migration from lo.Without to slices.DeleteFunc.

The predicate-based deletion using pointer comparison (l == windowEventListener) correctly identifies and removes the specific listener instance. This maintains the same semantics as the previous lo.Without implementation while using standard library functions.


797-799: LGTM! Consistent hook removal pattern.

The hook removal follows the same correct pattern as the event listener removal, using pointer comparison to identify the specific hook instance.


12-12: No compatibility concerns; go-json is already widely adopted in the codebase.

The switch to github.com/goccy/go-json is safe. This file contains only a simple json.Marshal(filenames) call marshaling a string slice (line 1466), and go-json handles basic types without issues. The library is already used throughout the codebase (runtime.go, capabilities.go, kvstore.go, notifications services, assetserver, etc.), demonstrating it's a proven, production-ready replacement.

v3/pkg/application/event_manager.go (2)

102-104: LGTM! Thread-safe listener removal with correct predicate.

The deletion logic correctly uses pointer comparison under proper lock protection. The pattern is consistent with the migration away from lo utilities across the codebase.


120-122: LGTM! Consistent hook removal implementation.

The hook removal follows the same correct pattern as the listener removal with appropriate locking.

v3/pkg/application/events.go (4)

201-203: LGTM! Consistent listener removal pattern.

The listener removal correctly uses pointer comparison with proper lock protection, consistent with the broader migration away from lo utilities.


224-226: LGTM! Consistent hook removal pattern.

The hook removal follows the same correct pattern with appropriate locking.


270-275: LGTM! Correct cleanup of expired listeners.

This DeleteFunc usage differs from the others in this file - it removes all listeners marked for deletion (when their counter reaches 0) rather than a specific instance. The predicate l.delete == true correctly identifies expired listeners, and the cleanup is performed efficiently in a single pass after event dispatch.


10-10: go-json is properly used for event marshaling and unmarshaling.

The CustomEvent.ToJSON method and decodeEventData function correctly handle JSON serialization using go-json. While Data is typed as any, the implementation registers specific types via RegisterEvent[T] and uses reflection in decodeEventData to unmarshal into the correct registered type, handling complex structs, custom types, and nil values appropriately. No additional validation is needed.

v3/pkg/application/webview_window_windows.go (4)

23-23: LGTM: Import change aligns with dependency removal goal.

The switch from external lo to internal sliceutil successfully reduces external dependencies while maintaining functionality.


246-260: LGTM: Clearer conditional logic.

The explicit if/else replaces lo.Ternary and improves readability while maintaining the same functionality.


1191-1212: LGTM: Consistent style modification logic.

Both setStyle and setExStyle now use explicit conditionals for bitwise operations, improving clarity and eliminating the external dependency.


1929-1938: LGTM: Feature deduplication using internal utility.

The replacement of lo.Uniq with sliceutil.Unique maintains functionality while removing the external dependency. The deduplication correctly prevents duplicate Chromium feature flags.

v3/internal/sliceutil/sliceutil.go (1)

11-24: LGTM: Efficient deduplication implementation.

The Unique function correctly:

  • Preserves first occurrence order
  • Handles nil input appropriately
  • Uses idiomatic map[T]struct{} for set-like tracking
  • Pre-allocates result capacity to minimize reallocations
v3/internal/sliceutil/sliceutil_test.go (1)

1-172: LGTM: Comprehensive test coverage.

The test suite thoroughly covers:

  • All edge cases (nil, empty, single element, duplicates)
  • Type variations (int, string, different key types)
  • Immutability verification
  • Performance benchmarks

Excellent test quality that validates the utility functions.

v3/internal/fileexplorer/desktopfile_test.go (1)

1-532: LGTM: Thorough testing of desktop file parser.

The test suite excellently validates:

  • Standard and edge-case .desktop file formats
  • Real-world file manager configurations (GNOME, KDE, XFCE)
  • Buffer limit handling and performance characteristics
  • UTF-8 and special character support

This comprehensive testing ensures the new minimal parser robustly replaces the ini.v1 dependency.

v3/UNRELEASED_CHANGELOG.md (1)

23-43: LGTM: Clear and comprehensive changelog entries.

The changelog accurately documents:

  • Specific performance improvements with quantified metrics
  • Memory optimization strategies
  • Dependency removals with size impact

The entries provide users with clear understanding of the changes and their benefits.

v3/pkg/application/bindings.go (7)

70-84: Excellent optimization with cached isVariadic flag.

Caching isVariadic at registration time (Line 83) eliminates a reflect call on every method invocation, which is a worthwhile performance improvement. The field reordering and JSON tag adjustments also align with the broader API surface goals.


213-224: Correct implementation of isVariadic caching.

The initialization of isVariadic from methodType.IsVariadic() at registration time properly implements the caching strategy. This avoids repeated reflect calls during method invocations.


283-290: Well-implemented stack buffer optimization.

The stack-allocated buffer for ≤8 arguments is a correct and effective optimization that avoids heap allocations for the common case. The fallback to heap allocation for larger argument counts ensures correctness.


313-319: Correct use of cached isVariadic flag.

Using the cached b.isVariadic flag to dispatch between Call and CallSlice is correct and avoids the reflect call. The dispatch logic properly handles both variadic and non-variadic methods.


278-278: Error Kind assignments are semantically correct.

The Kind field assignments are properly categorized:

  • TypeError for argument count mismatch (Line 278) and unmarshaling failures (Line 306)
  • RuntimeError for errors returned during method execution (Line 362)

This provides clear error classification for the frontend.

Also applies to: 306-306, 362-362


122-122: No issues found. The sliceutil.FindMapKey implementation correctly replaces lo.FindKey with identical semantics—both return (K, bool) after finding the first map key whose value equals the search parameter. The usage at line 122 is correct for the reverse map lookup.


11-11: goccy/go-json is a documented drop-in replacement; existing tests provide reasonable coverage for this change.

The CallOptions and CallError structures use simple types (strings, uint32, and the any type) with standard JSON tags. The concerns about number precision and null value semantics don't apply here. The existing benchmark tests in json_libs_bench_test.go compare goccy/go-json with encoding/json across similar struct patterns, and the integration tests in bindings_test.go validate the Call() path end-to-end. No additional edge case validation is needed beyond the existing test suite.

Comment thread v3/pkg/w32/window.go
Comment on lines 182 to 189
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))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent error handling across Must functions.*

MustStringToUTF16uintptr uses panic(err) on error (line 186), while the similar function MustStringToUTF16Ptr (line 177) uses Fatal(err). This inconsistency is confusing for a family of functions with the same naming pattern.

Additionally, if Fatal is nil when MustStringToUTF16Ptr encounters an error, it will cause a nil pointer dereference panic rather than a clear error message.

Consider using panic(err) consistently across all three Must* functions (including MustStringToUTF16 at line 195) to align with Go conventions where "Must" prefix indicates panic-on-error behavior.

🔎 Proposed fix for consistent error handling

Apply this change to MustStringToUTF16Ptr:

 func MustStringToUTF16Ptr(input string) *uint16 {
 	input = stripNulls(input)
 	result, err := syscall.UTF16PtrFromString(input)
 	if err != nil {
-		Fatal(err)
+		panic(err)
 	}
 	return result
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In v3/pkg/w32/window.go around lines 182 to 189, the MustStringToUTF16uintptr
function panics on error while MustStringToUTF16Ptr uses Fatal, causing
inconsistent behavior and a potential nil deref if Fatal is nil; change
MustStringToUTF16Ptr (and MustStringToUTF16 at ~line 195) to use panic(err) on
error instead of calling Fatal, making all Must* functions consistently
panic-on-error and avoiding reliance on a possibly nil Fatal function.

- 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
v3/internal/assetserver/mimecache.go (1)

29-30: TypeScript MIME type correctly updated.

The TypeScript MIME type has been properly updated to application/x-typescript, addressing the previous review feedback about using a non-standard but de-facto standard type.

v3/internal/assetserver/mimetype_stdlib_test.go (1)

243-251: Panic issue properly resolved.

The bounds-checking logic correctly addresses the previous review feedback. The code now safely extracts the base MIME type using strings.Index before comparison, avoiding the panic that would occur with short MIME strings.

🧹 Nitpick comments (1)
v3/internal/assetserver/mimetype_stdlib_test.go (1)

98-171: Consider consolidating duplicate MIME type maps.

The extMimeTypes map duplicates mimeTypesByExt from mimecache.go, creating a maintenance burden if either map is updated. Consider having getMimeTypeStdlib reference mimeTypesByExt directly. If a separate copy is needed for validation purposes (as in TestMimeTypeExtensionMapCompleteness), document this rationale clearly.

💡 Suggested refactor to eliminate duplication

If the test's purpose is to validate the production map works correctly with stdlib detection, use the production map directly:

-// 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 != "" {
+	if result := mimeTypesByExt[filepath.Ext(filename)]; result != "" {
 		return result
 	}
 
 	// Fallback to stdlib content-based detection
 	result := http.DetectContentType(data)
 	if result == "" {
 		result = "application/octet-stream"
 	}
 	return result
 }

Then remove the extMimeTypes variable entirely, or if you need it for the completeness check, add a comment explaining why it's separate.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a7fa9c and f699ea4.

📒 Files selected for processing (3)
  • v3/internal/assetserver/mimecache.go
  • v3/internal/assetserver/mimetype_stdlib_test.go
  • v3/pkg/application/bindings_optimized_bench_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/pkg/application/bindings_optimized_bench_test.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-02-06T23:10:07.704Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4049
File: v3/internal/assetserver/content_type_sniffer.go:0-0
Timestamp: 2025-02-06T23:10:07.704Z
Learning: In the content type sniffer of the asset server, storing a direct reference to chunks >= 512 bytes in `rw.prefix` is safe because:
1. It's only done for chunks >= 512 bytes
2. `rw.complete()` is called immediately after, which resets `rw.prefix` to nil
3. This optimization avoids unnecessary copying of large chunks in the common case
4. `rw.prefix` is only written in the other branch of the if statement when the chunk is < 512 bytes
5. Safety checks ensure robustness:
   - Using max(min()) to prevent negative cut sizes
   - Only modifying prefix buffer when there's actual data to append

Applied to files:

  • v3/internal/assetserver/mimetype_stdlib_test.go
📚 Learning: 2025-08-08T13:15:37.785Z
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4484
File: v2/internal/frontend/utils/urlValidator_test.go:152-155
Timestamp: 2025-08-08T13:15:37.785Z
Learning: In v2/internal/frontend/utils/urlValidator_test.go, the project accepts a literal '|' in the URL path (e.g., "https://example.com/user/123|admin") as valid for BrowserOpenURL. Maintainer (APshenkin) clarified that browsers will escape it automatically; thus, tests should not require percent-encoding '%7C' nor expect an error from utils.ValidateURL for this case. Avoid suggesting rejection or forced encoding of '|' in path in future reviews.

Applied to files:

  • v3/internal/assetserver/mimetype_stdlib_test.go
📚 Learning: 2025-02-06T23:10:07.704Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4049
File: v3/internal/assetserver/content_type_sniffer.go:0-0
Timestamp: 2025-02-06T23:10:07.704Z
Learning: In the content type sniffer of the asset server, storing a direct reference to chunks >= 512 bytes in `rw.prefix` is safe because:
1. It's only done for chunks >= 512 bytes
2. `rw.complete()` is called immediately after, which resets `rw.prefix` to nil
3. This optimization avoids unnecessary copying of large chunks in the common case
4. `rw.prefix` is only written in the other branch of the if statement when the chunk is < 512 bytes
5. Safety checks ensure local correctness independent of other methods' implementation details

Applied to files:

  • v3/internal/assetserver/mimetype_stdlib_test.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (3)
v3/internal/assetserver/mimecache.go (1)

96-115: LGTM! Efficient three-tier MIME detection strategy.

The implementation correctly prioritizes extension-based lookup (O(1), no lock) → cache read (lock-free via sync.Map) → content-based detection (with caching). The type assertion at line 104 is safe, and the fallback to application/octet-stream is appropriate.

v3/internal/assetserver/mimetype_stdlib_test.go (2)

13-79: LGTM! Comprehensive test coverage for web MIME types.

The test suite thoroughly validates extension-based and content-based detection across all common web asset formats. The prefix-matching approach appropriately handles charset parameter variations.


257-277: No action needed. The code correctly uses b.Loop(), which was introduced in Go 1.24. Since Wails v3 requires Go 1.25+, the benchmark is fully compatible and uses the appropriate modern API for this project.

Likely an incorrect or invalid review comment.

@leaanthony leaanthony force-pushed the v3/performance-improvements branch from 2845cab to 26e30af Compare January 1, 2026 08:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
v3/pkg/application/bindings.go (2)

17-44: Pre-warming addresses the known race condition; consider adding uint32.

The init() pre-warming is a solid mitigation for the goccy/go-json decoder race (issue #474). Given that CallOptions.MethodID uses uint32, consider adding it to the pre-warm list:

 	var (
 		s   string
 		i   int
 		i64 int64
+		u32 uint32
 		f64 float64
 		...
 	)
 	...
 	_ = json.Unmarshal([]byte(`0`), &i64)
+	_ = json.Unmarshal([]byte(`0`), &u32)

62-68: Padding comment may be inaccurate for this struct.

All three fields (string, any, ErrorKind/string) are 16 bytes with 8-byte alignment on 64-bit systems, so reordering doesn't affect padding here. The comment could be removed or clarified.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2845cab and 26e30af.

📒 Files selected for processing (1)
  • v3/pkg/application/bindings.go
🧰 Additional context used
🧬 Code graph analysis (1)
v3/pkg/application/bindings.go (2)
v3/internal/sliceutil/sliceutil.go (1)
  • FindMapKey (28-36)
v3/internal/hash/fnv.go (1)
  • Fnv (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Cloudflare Pages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (8)
v3/pkg/application/bindings.go (8)

11-14: Clean import aliasing for drop-in JSON replacement.

The alias json for github.com/goccy/go-json maintains API compatibility with the existing codebase while enabling the performance benefits. The internal package imports for hash and sliceutil align with the new functionality.


46-52: LGTM - struct layout optimization.

Field reordering places the uint32 field last, minimizing padding on 64-bit systems.


99-113: Good optimization: caching isVariadic and struct layout.

Caching isVariadic at registration eliminates a reflect call per invocation. The struct layout optimization (136 vs 144 bytes) is achieved by grouping smaller fields together at the end.


151-153: LGTM - internal utility replaces external dependency.

Using sliceutil.FindMapKey instead of lo.FindKey reduces external dependencies while maintaining the same functionality.


242-253: LGTM - isVariadic cached at registration.

Caching methodType.IsVariadic() during registration eliminates the reflect call from the hot path.


312-319: Stack-allocated argument buffer is a good optimization.

Using a stack-allocated [8]reflect.Value buffer for common cases avoids heap allocation. The threshold of 8 arguments covers the vast majority of method signatures.

Consider adding a brief comment explaining why 8 was chosen (e.g., "covers ~99% of method signatures" or similar) for future maintainers.


342-348: LGTM - cached isVariadic flag usage.

Using the cached b.isVariadic flag to choose between CallSlice and Call eliminates a reflect call per invocation.


350-402: Optimized result collection minimizes allocations.

The firstResult/hasFirstResult pattern elegantly avoids slice allocation for the common single-return-value case. The early exit from collecting non-error outputs once an error is found is valid since result will be nil anyway when errors exist.

@leaanthony leaanthony force-pushed the v3/performance-improvements branch from 26e30af to f699ea4 Compare January 1, 2026 09:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
v3/pkg/application/bindings.go (2)

42-48: Question: Does field reordering provide measurable benefit here?

All three fields (Message, Cause, Kind) are 16 bytes each on 64-bit systems (string=16, interface=16, ErrorKind=16), so the struct is 48 bytes regardless of field order. The padding optimization comment may not apply significantly to this struct. Consider whether this reordering provides a measurable benefit.


330-382: Result collection logic is optimized but complex.

The result processing is well-optimized to avoid allocations:

  • Lines 332-333: Track the first non-error result without allocating a slice
  • Lines 352-358: Only allocate nonErrorOutputs slice when a second non-error result is encountered
  • Line 346: Stop collecting non-error results once an error is found (correct per function contract)

While the optimization is effective, the logic is intricate. Consider adding a brief inline comment explaining the allocation strategy (e.g., "defer slice allocation until second result") to aid future maintainers.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26e30af and 62b0d95.

📒 Files selected for processing (1)
  • v3/pkg/application/bindings.go
🧰 Additional context used
🧬 Code graph analysis (1)
v3/pkg/application/bindings.go (2)
v3/internal/sliceutil/sliceutil.go (1)
  • FindMapKey (28-36)
v3/internal/hash/fnv.go (1)
  • Fnv (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
v3/pkg/application/bindings.go (7)

26-32: LGTM: Struct layout optimization is correctly applied.

The field reordering places the smaller uint32 field after larger fields, which reduces padding on 64-bit architectures. This is a good micro-optimization for hot path structures.


79-93: LGTM: Excellent optimization with cached isVariadic flag.

Caching the isVariadic flag at registration time (line 92) eliminates a reflection call (methodType.IsVariadic()) on every method invocation. This is a smart optimization for the hot path. The struct layout optimization and addition of the ID field for hash-based lookups are also well-considered improvements.


131-131: LGTM: Correct use of internal utility function.

The switch from the lo library to sliceutil.FindMapKey reduces external dependencies while maintaining the same functionality. The function signature matches the usage pattern correctly.


222-233: LGTM: Efficient caching of variadic flag at registration.

Setting isVariadic during method registration (line 232) ensures the reflection call to methodType.IsVariadic() occurs only once, rather than on every invocation. This is a clean implementation of the caching strategy.


292-299: LGTM: Effective stack buffer optimization for common case.

The stack-allocated buffer for up to 8 arguments avoids heap allocations in the common case. The threshold of 8 is reasonable, as most methods have fewer arguments. This optimization directly contributes to the reported performance improvements.


324-328: LGTM: Correct use of cached isVariadic flag.

Using the cached b.isVariadic flag eliminates the need for a reflection call (methodType.IsVariadic()) on every invocation. This directly reduces overhead in the hot path.


11-11: Verify behavior with goccy/go-json 0.10.5's known stability issues.

While v0.10.5 (released 28 Jan 2025) is the latest stable release and serves as a drop-in compatible replacement for encoding/json, the library has multiple known bugs and stability issues including reported crashes and panics. Test this implementation thoroughly, particularly around edge cases in method binding and error handling, to ensure these issues do not affect the performance gains claimed by this PR.

Comment on lines +17 to +24
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 si []int
_ = json.Unmarshal([]byte(`[]`), &si)
}

@coderabbitai coderabbitai Bot Jan 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

goccy/go-json issue #474 Windows type address cache panic fix

💡 Result:

I found the GitHub issue #474 (Concurrent decoding) in goccy/go-json — opened Oct 9, 2023 — which reports SIGSEGVs when decoding concurrently. The issue page is here. [1]

There are also community reports discussing panics tied to the library's type-address cache / cached opcode slices (e.g. cachedOpcodeSets / typeAddr assumptions) that can trigger segfaults in certain builds/environments. One such discussion is on Reddit. [2]

If you want, I can:

  • summarize the Modify build fo macOS app bundles #474 thread (comments/any follow-ups),
  • search for specific PRs that fixed type-address cache races/Windows-specific panics,
  • or find recommended workarounds (e.g., disabling the optimization or using a specific version).

Sources:
[1] goccy/go-json issue #474 — Concurrent decoding (GitHub).
[2] Discussion about cachedOpcodeSets / type-address panics (Reddit).


🏁 Script executed:

# Find all uses of json.Unmarshal in the codebase to understand what types are being unmarshaled
rg -A 3 "json\.Unmarshal\|json\.Decode" v3/pkg --type go

Repository: wailsapp/wails

Length of output: 40


🏁 Script executed:

# Check the structure of the bindings.go file and related context
head -30 v3/pkg/application/bindings.go

Repository: wailsapp/wails

Length of output: 857


🏁 Script executed:

# Look for any type definitions or structs that might be unmarshaled
fd -t f "\.go$" v3/pkg/application | head -20 | xargs wc -l | sort -n

Repository: wailsapp/wails

Length of output: 1017


🏁 Script executed:

# Search for json.Unmarshal and json.Decode calls in bindings.go specifically
rg -n "json\.(Unmarshal|Decode|NewDecoder)" v3/pkg/application/bindings.go -A 2

Repository: wailsapp/wails

Length of output: 209


🏁 Script executed:

# Check the CallOptions struct and how it's used for unmarshaling
rg -n "CallOptions|json\.RawMessage" v3/pkg/application/bindings.go -B 2 -A 2

Repository: wailsapp/wails

Length of output: 1281


🏁 Script executed:

# Look at the entire bindings.go file to understand the structure
wc -l v3/pkg/application/bindings.go && cat -n v3/pkg/application/bindings.go | head -100

Repository: wailsapp/wails

Length of output: 3542


🏁 Script executed:

# Check if there are related issues or discussions about this workaround in the repo
git log --oneline --all -S "Force goccy/go-json" -- v3/pkg/application/bindings.go 2>/dev/null | head -5

Repository: wailsapp/wails

Length of output: 138


🏁 Script executed:

# Check if there are any follow-up comments or related issues in the codebase
rg -i "goccy.*json.*cache|type.*address|decoder.*init" v3/ --type go

Repository: wailsapp/wails

Length of output: 997


🌐 Web query:

goccy/go-json type address cache race condition workaround recommended approach

💡 Result:

Short answer: this is a known problem caused by goccy/go-json's unsynchronised type/address cache (global cachedOpcodeSets). Workarounds people use are (pick one):

  • Stop using go-json for that codepath and fall back to the stdlib encoding/json (drop-in compatibility), which avoids the unsafe/unsynchronised caching. [1][2]
  • Vendor or patch the library in your repo to add proper synchronization around the cache (or apply any upstream fix) and rebuild. [1][3]
  • If you can’t change the library immediately, isolate JSON work behind a single goroutine or mutex in your app so access to go-json’s Marshal/Unmarshal is serialized (avoid concurrent reads/writes that hit the cache).

References:

  • goccy/go-json repository (shows the encoder internals and separate race/norace compiler files). [1]
  • Community reports / discussion pointing to crashes from module-level cache access. [3]

If you want, tell me which option you prefer and I’ll give a concrete patch or code example (vendor patch or mutex wrapper).

Sources:
[1] goccy/go-json repository (internal encoder files).
[2] Repo README / usage notes about drop-in replacement.
[3] Community discussion reporting panics accessing module-level cache.


This workaround is pragmatic but considers switching to stdlib encoding/json or serializing access.

The init() function pre-warms goccy/go-json's type-address cache by unmarshaling a single []int type, which initializes the shared global cache structures. Since the cache is module-level (not per-type), warming a single type is sufficient to prevent the Windows panic. The fix has been validated in tests.

However, goccy/go-json's maintainers recommend three approaches for this concurrency issue: switching to stdlib encoding/json (drop-in replacement), adding synchronization (mutex/serialization) around marshaling calls, or vendoring a patched version. The current workaround is effective but addresses only the test-time symptom; for production robustness, consider whether one of the upstream-recommended solutions is more appropriate.

🤖 Prompt for AI Agents
v3/pkg/application/bindings.go lines 17-24: the init() currently pre-warms
goccy/go-json's global type-address cache with an Unmarshal call as a workaround
for a race on Windows; replace this brittle workaround with a robust fix by
either 1) switching to the stdlib encoding/json (replace the go-json import with
encoding/json and keep the same Marshal/Unmarshal call sites), or 2)
centralizing serialization through a package-level API that serializes access
with a sync.Mutex (create package-level Marshal/Unmarshal wrappers that
lock/unlock around calls to go-json), or 3) vendor/apply the upstream patch into
a forked module; pick one approach and apply it consistently across the codebase
(if choosing 1, update imports and run tests; if choosing 2, add a mutex and
replace direct json.Marshal/Unmarshal usages with the new wrappers; if choosing
3, update go.mod to point to the patched fork and run go mod tidy).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodeRabbit - where do goccy/go-json's maintainers recommend three approaches for this concurrency issue?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🌐 Web query:

goccy/go-json issue 474 maintainer response recommendation

💡 Result:

Summary: Issue #474 ("Concurrent decoding") reports intermittent SIGSEGV when decoding JSON from multiple goroutines (race during CompileToGetDecoder / opcode cache). The issue was opened Oct 9, 2023 and shows the panic trace; there are no maintainer comments on the issue page. [1][2]

Recommended actions (practical/workaround):

  • Warm up / precompile decoder for each target type before using it concurrently (call the decode path once from a single goroutine so the opcode/cache is built). This avoids the race on first compile. [1][3]
  • If you can change the caller, serialize the first decode (or protect decoder compilation with a mutex) then allow concurrent Decode calls.
  • Run tests with the race detector (go test -race) to try to reproduce and confirm race conditions. [3]
  • Monitor the repository issue for maintainer fixes and upgrade if/when a patched release appears; you can also file a follow-up comment on the issue if you need a maintainer response. [1][2]

Sources:

References:
[1] goccy/go-json#474
[2] https://issues.ecosyste.ms/hosts/GitHub/repositories/goccy/go-json/issues
[3] community discussion referencing CompileToGetDecoder race conditions.


@leaanthony, I apologize for the misattribution in my previous comment. After checking issue #474, there are no maintainer comments on the issue page. The three approaches I mentioned were general strategies I inferred, not official recommendations from the goccy/go-json maintainers.

The actual situation is:

  • Issue Modify build fo macOS app bundles #474 reports intermittent SIGSEGV when decoding JSON from multiple goroutines (race during CompileToGetDecoder / opcode cache compilation)
  • The issue was opened in October 2023 and remains without maintainer response
  • Community workarounds suggest warming up / precompiling the decoder for each target type before using it concurrently (call the decode path once from a single goroutine so the opcode/cache is built)

Your current PR's init() function that pre-warms the cache by unmarshaling []int is actually aligned with this community-recommended workaround. It addresses the race condition by ensuring the type address cache is initialized before concurrent access during tests.

Given that the maintainers haven't provided an official fix, your pragmatic workaround is reasonable for now.


🧠 Learnings used
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/no_bindings_here/person.go:9-9
Timestamp: 2025-01-15T22:30:08.796Z
Learning: When dealing with fixed-length arrays in Go structs, encoding/json automatically handles bounds checking during unmarshaling, returning an error if the JSON array length exceeds the Go array length. No additional runtime validation is needed.

Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:91-113
Timestamp: 2025-02-24T06:08:55.645Z
Learning: The JSON quote handling in Windows notifications' activation arguments (v3/pkg/services/notifications/notifications_windows.go) has a known limitation with single-quote collisions that needs to be addressed after initial testing.

@leaanthony

Copy link
Copy Markdown
Member Author

@coderabbitai generate docstrings

@coderabbitai

coderabbitai Bot commented Jan 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai

coderabbitai Bot commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #4844

coderabbitai Bot added a commit that referenced this pull request Jan 1, 2026
Docstrings generation was requested by @leaanthony.

* #4843 (comment)

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`
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: goccy/go-json#474
@leaanthony leaanthony force-pushed the v3/performance-improvements branch from 62b0d95 to 39cac6b Compare January 1, 2026 09:46
* 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: goccy/go-json#474

* 📝 Add docstrings to `v3/performance-improvements`

Docstrings generation was requested by @leaanthony.

* #4843 (comment)

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 <lea.anthony@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
v3/pkg/application/bindings.go (4)

17-70: Simplify the init() workaround to pre-warm only one type.

The comment states the fix initializes the "type address cache" (module-level), yet the implementation pre-warms ~20 different types. Per the past review discussion, warming a single type should be sufficient to initialize the shared global cache. The current approach adds unnecessary initialization overhead.

🔎 Proposed simplification
 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)
+	var dummy int
+	_ = json.Unmarshal([]byte(`0`), &dummy)
 }

72-78: The padding optimization claim is misleading.

The comment states "Field order is optimized to minimize struct padding," but on 64-bit systems both orderings result in 48 bytes:

  • New: MethodName(16) + Args(24) + MethodID(4) + padding(4) = 48
  • Old: MethodName(16) + MethodID(4) + padding(4) + Args(24) = 48

While the reordering doesn't harm functionality, it provides no measurable benefit. Consider removing or correcting the comment.


88-94: The padding optimization claim is misleading.

The comment states "Field order is optimized to minimize struct padding," but both orderings result in 48 bytes since all fields (Message, Cause, Kind) are 16 bytes each on 64-bit systems. Consider removing or correcting the comment.


338-345: Document the stack buffer size choice.

The stack-allocated buffer for ≤8 arguments is a good optimization for the common case. However, the choice of 8 is undocumented. Consider adding a comment explaining the threshold (e.g., balancing stack usage vs. heap allocation benefits).

🔎 Suggested comment
-	// Use stack-allocated buffer for common case (<=8 args), heap for larger
+	// Use stack-allocated buffer for common case (<=8 args), heap for larger.
+	// 8 args = ~192 bytes of stack (24 bytes per reflect.Value on 64-bit),
+	// which is a reasonable trade-off to avoid heap allocation.
 	var argBuffer [8]reflect.Value
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62b0d95 and 39cac6b.

📒 Files selected for processing (1)
  • v3/pkg/application/bindings.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-02-24T06:08:55.645Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:91-113
Timestamp: 2025-02-24T06:08:55.645Z
Learning: The JSON quote handling in Windows notifications' activation arguments (v3/pkg/services/notifications/notifications_windows.go) has a known limitation with single-quote collisions that needs to be addressed after initial testing.

Applied to files:

  • v3/pkg/application/bindings.go
📚 Learning: 2025-01-15T22:30:08.796Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/no_bindings_here/person.go:9-9
Timestamp: 2025-01-15T22:30:08.796Z
Learning: When dealing with fixed-length arrays in Go structs, encoding/json automatically handles bounds checking during unmarshaling, returning an error if the JSON array length exceeds the Go array length. No additional runtime validation is needed.

Applied to files:

  • v3/pkg/application/bindings.go
📚 Learning: 2025-03-24T06:54:22.127Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.

Applied to files:

  • v3/pkg/application/bindings.go
🧬 Code graph analysis (1)
v3/pkg/application/bindings.go (2)
v3/internal/sliceutil/sliceutil.go (1)
  • FindMapKey (28-36)
v3/internal/hash/fnv.go (1)
  • Fnv (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
v3/pkg/application/bindings.go (7)

11-15: LGTM: Import changes support performance optimizations.

The switch to goccy/go-json and introduction of internal utilities (sliceutil, hash) align with the PR's performance goals. The aliasing as json maintains API compatibility with minimal code changes.


125-139: LGTM: Caching isVariadic is a good optimization.

The cached isVariadic field avoids a reflection call on every invocation, which aligns well with the PR's performance goals. While the struct padding claim of saving 8 bytes may be accurate here (unlike CallOptions/CallError), the primary benefit is eliminating repeated methodType.IsVariadic() calls.


177-177: LGTM: Using internal utility reduces dependencies.

Replacing lo.FindKey with sliceutil.FindMapKey reduces external dependencies while maintaining the same functionality.


268-279: LGTM: Caching variadic flag at registration time.

Setting isVariadic during method registration eliminates a reflection call per invocation. This is a clean optimization that directly supports the performance improvements claimed in the PR.


368-374: LGTM: Using cached isVariadic flag.

The conditional call to CallSlice vs. Call now uses the cached isVariadic field, avoiding a reflection call per invocation. This is the intended performance improvement.


376-428: LGTM: Optimized result processing minimizes allocations.

The result processing logic efficiently handles the common cases:

  • Single non-error result: returns directly without slice allocation
  • Multiple non-error results: allocates slice only when needed
  • Error results: constructs CallError with proper Kind field

The complexity is justified by the allocation savings, which align with the benchmark improvements reported in the PR (e.g., SimpleCall: 240B → 80B).


333-333: LGTM: Consistent error Kind classification.

The Kind field is now consistently set across all error paths:

  • TypeError for argument count mismatches and unmarshaling failures
  • RuntimeError for method execution errors

This improves error handling and debugging.

Also applies to: 361-361, 417-417

@sonarqubecloud

sonarqubecloud Bot commented Jan 1, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
6.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
v3/pkg/w32/window.go (1)

182-192: Inconsistency with MustStringToUTF16Ptr remains unaddressed.

While the new MustStringToUTF16uintptr and MustStringToUTF16 functions correctly use panic(err), the existing MustStringToUTF16Ptr (line 177) still uses Fatal(err). This creates confusion within the Must* function family and was previously flagged in the last review.

Consider addressing the earlier review comment by updating MustStringToUTF16Ptr to use panic(err) for consistency.

🧹 Nitpick comments (4)
v3/pkg/application/bindings.go (2)

17-72: Simplify the init() workaround to reduce startup overhead.

The expanded pre-warming now unmarshals ~20 different types, but the original simple approach (unmarshaling a single []int) was sufficient to initialize goccy/go-json's global type address cache structure. Since the cache is module-level, warming one type initializes the cache; additional types only pre-populate type-specific entries but don't provide additional race protection.

The current version adds unnecessary startup latency and complexity. Consider reverting to the minimal approach:

🔎 Proposed simplification
 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)
+	var v []int
+	_ = json.Unmarshal([]byte(`[]`), &v)
 }

74-80: Struct padding optimization may be overstated.

The comment claims field order is optimized to minimize padding, but on 64-bit systems this struct will be 48 bytes regardless of whether MethodID is before or after Args (both require 4 bytes of padding due to 8-byte alignment). While the reordering doesn't hurt, the actual memory savings are negligible or zero for this particular struct.

v3/internal/fileexplorer/desktopfile.go (2)

17-28: Consider streamlining the docstring.

The docstring on lines 18-20 contains some redundancy. The function documentation could be more concise.

🔎 Suggested improvement
-// 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.
+// ParseDesktopFile parses the [Desktop Entry] section of the desktop file at the given path.
+// It returns an error if the file cannot be opened or if parsing fails.

85-88: Consider using the first Exec occurrence instead of the last.

If duplicate Exec keys exist (line 87 comment), the current implementation uses the last occurrence. While the Desktop Entry specification prohibits duplicate keys, when they do occur, implementations typically use the first occurrence. However, this is an unlikely edge case.

🔎 Optional fix to use first occurrence
 		// 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 entry.Exec == "" {
+				entry.Exec = value
+			}
+			// Continue parsing but keep the first Exec value if duplicates exist (shouldn't happen per spec)
 		}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39cac6b and 946b480.

📒 Files selected for processing (10)
  • 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
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/internal/sliceutil/sliceutil.go
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-02-24T06:08:55.645Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:91-113
Timestamp: 2025-02-24T06:08:55.645Z
Learning: The JSON quote handling in Windows notifications' activation arguments (v3/pkg/services/notifications/notifications_windows.go) has a known limitation with single-quote collisions that needs to be addressed after initial testing.

Applied to files:

  • v3/pkg/application/bindings.go
📚 Learning: 2025-01-15T22:30:08.796Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testcases/no_bindings_here/person.go:9-9
Timestamp: 2025-01-15T22:30:08.796Z
Learning: When dealing with fixed-length arrays in Go structs, encoding/json automatically handles bounds checking during unmarshaling, returning an error if the JSON array length exceeds the Go array length. No additional runtime validation is needed.

Applied to files:

  • v3/pkg/application/bindings.go
📚 Learning: 2025-03-24T06:54:22.127Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.

Applied to files:

  • v3/pkg/application/bindings.go
📚 Learning: 2025-12-29T08:02:06.122Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:06.122Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.

Applied to files:

  • v3/pkg/application/application_ios.go
📚 Learning: 2025-02-04T23:59:43.956Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4045
File: v3/internal/generator/render/info.go:28-68
Timestamp: 2025-02-04T23:59:43.956Z
Learning: In the Wails v3 project, internal functions are designed to panic on nil parameters as they represent contract violations, rather than adding defensive nil checks.

Applied to files:

  • v3/pkg/w32/window.go
📚 Learning: 2025-01-18T21:19:47.778Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4024
File: v3/pkg/application/services.go:38-51
Timestamp: 2025-01-18T21:19:47.778Z
Learning: In the Wails v3 codebase, panic is used to catch invalid invocations that should never occur in production (programming errors), while error returns are reserved for recoverable error conditions (runtime errors). For example, the `NewService` function in `v3/pkg/application/services.go` uses panic to handle the case where more than one `ServiceOptions` instance is provided, as this is considered an unrecoverable programming error.

Applied to files:

  • v3/pkg/w32/window.go
📚 Learning: 2025-01-15T22:42:09.774Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/analyse_test.go:105-107
Timestamp: 2025-01-15T22:42:09.774Z
Learning: In the FindServices function of v3/internal/generator/analyse.go, services should never be nil when err is nil. This is a design contract and any violation should result in a panic rather than being handled silently.

Applied to files:

  • v3/pkg/w32/window.go
🧬 Code graph analysis (2)
v3/internal/fileexplorer/fileexplorer_linux.go (1)
v3/internal/fileexplorer/desktopfile.go (1)
  • ParseDesktopFile (21-28)
v3/pkg/application/bindings.go (2)
v3/internal/sliceutil/sliceutil.go (1)
  • FindMapKey (31-39)
v3/internal/hash/fnv.go (1)
  • Fnv (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: Cloudflare Pages
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (22)
v3/internal/assetserver/content_type_sniffer.go (2)

7-13: LGTM: Lazy allocation pattern reduces per-request overhead.

The removal of eager channel allocation from the constructor is a good optimization, as most requests never call the deprecated CloseNotify interface.


132-135: Good defensive nil check.

The nil guard correctly prevents panics when closeClient() is called on requests that never invoked CloseNotify().

v3/internal/assetserver/common.go (1)

27-45: Excellent optimization: eliminated unnecessary Reader allocation.

The change from io.Copy(rw, bytes.NewReader(blob)) to direct rw.Write(blob) avoids allocating a bytes.Reader, aligning with the PR's goal of reducing allocations in hot paths. The added docstring clearly documents the function's behavior.

v3/internal/assetserver/mimecache.go (3)

10-19: Excellent choice: sync.Map for read-heavy MIME cache.

Using sync.Map instead of a regular map with mutex is well-suited for this read-heavy workload. The rationale comment clearly explains the design decisions, including the O(1) extension lookup advantage and binary size savings.


20-93: Comprehensive extension coverage reduces fallback to content detection.

The significantly expanded mimeTypesByExt map covers a wide range of web formats, which will improve the fast-path hit rate and reduce reliance on slower content-based detection. The categorization with comments enhances maintainability.


97-116: Well-structured three-tier MIME lookup.

The implementation efficiently cascades through extension map → cache → content detection, with each tier getting progressively slower but more general. The sync.Map API usage is correct.

Note: The cache keys on filename, so different files with identical names would share cache entries. This should be acceptable since the asset server typically uses unique file paths.

v3/pkg/w32/window.go (1)

182-212: LGTM! UTF-16 conversion functions follow Go conventions correctly.

All three functions follow the proper Go patterns:

  • MustStringToUTF16uintptr and MustStringToUTF16 correctly use panic(err) for Must* functions
  • StringToUTF16 correctly returns an error for the non-Must variant
  • Clear docstrings document the null-stripping behavior and error handling
v3/pkg/application/ios_runtime_ios.go (2)

67-74: No issues found. The C implementation safely handles nil pointers through explicit NULL checks (lines 195 and 109 in application_ios.m), and the behavioral change of passing nil to clear the custom User-Agent is the correct iOS pattern for this operation.


14-16: go-json v0.10.5 is stable and safe for this use case.

The library version is the current stable release. While there are reported edge cases in json parsing (nested struct panics, number parsing), the iosDeviceInfo struct is simple (string, string, bool) and unmarshals directly from a C string—these known issues don't apply here. The codebase already evaluates json libraries through comprehensive benchmarking, confirming deliberate adoption.

v3/pkg/application/bindings.go (6)

11-14: LGTM - Import changes align with performance goals.

The switch to goccy/go-json with aliasing maintains drop-in compatibility while enabling performance improvements. The new internal utilities (hash, sliceutil) support the optimization work.


127-141: Excellent optimization with isVariadic caching.

Caching the isVariadic flag at registration time (line 140) eliminates a reflect.Type.IsVariadic() call on every method invocation. This is leveraged effectively in the Call method (line 376) to choose between CallSlice and Call, providing measurable performance improvement in the hot path.


179-179: Good migration to internal utility.

Switching from lo.FindKey to sliceutil.FindMapKey reduces external dependencies while maintaining identical functionality. This aligns with the PR's goal of optimizing the codebase.


274-285: Well-implemented isVariadic caching at registration.

Caching methodType.IsVariadic() during method registration (line 284) is the correct approach - compute once at setup time rather than on every invocation. This directly supports the performance improvements reported in the benchmark results.


344-351: Excellent stack-allocated buffer optimization.

Using a stack-allocated array for the common case of ≤8 arguments avoids heap allocations in the hot path. This is a textbook optimization that directly contributes to the reported allocation reductions (SimpleCall: 240B→80B, 7→5 allocs).


382-434: Optimized result collection with smart allocation strategy.

The result processing is well-optimized:

  • Avoids slice allocation for single non-error results using firstResult tracking (lines 401-403)
  • Defers slice allocation until the second non-error result (lines 404-407)
  • Pre-allocates errorOutputs capacity when needed (line 395)
  • Stops collecting non-error outputs once an error is encountered (line 398), matching Go's error-last convention

This optimization directly supports the benchmark improvements in ComplexCall (35→22 allocs).

v3/internal/fileexplorer/fileexplorer_linux.go (1)

40-60: Clean integration with the new parser.

The migration from the INI library to ParseDesktopFile is well-executed. The error handling maintains appropriate fallback behavior, and the string replacement logic correctly operates on the extracted Exec command.

v3/internal/fileexplorer/desktopfile.go (3)

10-15: Minimal design aligns well with PR objectives.

The lightweight DesktopEntry struct and the comment clearly documenting the dependency replacement rationale are well-suited to this performance-focused PR.


30-39: Well-documented parser setup.

The docstring clearly explains the parsing behavior (case-sensitivity, section boundaries, whitespace handling), and the scanner-based approach is appropriate for this use case.


40-96: Solid implementation of the desktop file parser.

The parsing logic correctly handles all Desktop Entry Specification requirements:

  • Section boundaries and case-sensitive section name comparison ([Desktop Entry])
  • Comment and empty line skipping
  • Key-value extraction with intentional whitespace preservation in values
  • Early termination after the [Desktop Entry] section

The implementation appropriately balances simplicity with correctness for this minimal parser use case, focusing on extracting the Exec value while correctly adhering to the specification's INI-style format rules.

v3/pkg/application/application_ios.go (3)

111-116: LGTM! Comprehensive docstring added.

The generated documentation clearly describes the function's purpose and the iOS-specific configuration it applies.


171-183: LGTM! Native tabs JSON marshaling is correctly implemented.

The logic properly checks EnableNativeTabs, marshals NativeTabsItems using the new JSON library, handles errors gracefully, and safely manages C memory with proper cleanup.


23-24: LGTM! Import change aligns with PR performance objectives.

The switch from encoding/json to github.com/goccy/go-json (v0.10.5) is correctly implemented with the alias for drop-in compatibility. This library is a pure-Go implementation with no cgo dependencies and is fully compatible with iOS cross-compilation.

Comment on lines 124 to 128
func (rw *contentTypeSniffer) CloseNotify() <-chan bool {
if rw.closeChannel == nil {
rw.closeChannel = make(chan bool, 1)
}
return rw.closeChannel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Data race: unprotected lazy initialization of closeChannel.

Concurrent calls to CloseNotify() can create multiple channels because the nil-check and assignment are not atomic. This violates the http.CloseNotifier contract—different callers may receive different channels, and closeClient() may send to the wrong one.

🔎 Proposed fix using sync.Once

Update the struct to include a sync.Once:

 type contentTypeSniffer struct {
 	rw              http.ResponseWriter
 	prefix          []byte
 	closeChannel    chan bool // lazily allocated only if CloseNotify is called
+	closeOnce       sync.Once
 	status          int
 	headerCommitted bool
 	headerWritten   bool
 }

Then update CloseNotify:

 func (rw *contentTypeSniffer) CloseNotify() <-chan bool {
-	if rw.closeChannel == nil {
-		rw.closeChannel = make(chan bool, 1)
-	}
+	rw.closeOnce.Do(func() {
+		rw.closeChannel = make(chan bool, 1)
+	})
 	return rw.closeChannel
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
v3/internal/assetserver/content_type_sniffer.go lines 124-128: CloseNotify
performs an unprotected lazy init of rw.closeChannel causing a data race and
possible multiple channels; add a sync.Once field to the contentTypeSniffer
struct and use it to initialize closeChannel exactly once (e.g. in CloseNotify
call the once.Do that sets rw.closeChannel = make(chan bool, 1)), ensuring the
buffer size remains 1, and confirm closeClient uses rw.closeChannel (the single
initialized channel) when sending the close signal so all callers receive the
same channel.

}
)

// "application/octet-stream".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete docstring fragment.

The comment appears to be a truncated function docstring. Complete it to explain GetMimetype's behavior, including when it returns the default "application/octet-stream".

🔎 Proposed fix
-// "application/octet-stream".
+// GetMimetype returns the MIME type for the given filename and data.
+// It first checks the extension map, then the cache, and finally falls back
+// to http.DetectContentType. If detection fails, returns "application/octet-stream".
 func GetMimetype(filename string, data []byte) string {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// "application/octet-stream".
// GetMimetype returns the MIME type for the given filename and data.
// It first checks the extension map, then the cache, and finally falls back
// to http.DetectContentType. If detection fails, returns "application/octet-stream".
func GetMimetype(filename string, data []byte) string {
🤖 Prompt for AI Agents
In v3/internal/assetserver/mimecache.go around line 96, the comment is a
truncated docstring for GetMimetype; replace it with a complete docstring that
describes the function signature, what inputs it accepts, how it determines a
MIME type (e.g., by extension or lookup), and the exact conditions when it falls
back to the default "application/octet-stream" (such as unknown extensions,
empty input, or lookup failures). Keep the docstring concise, mention the return
value and any side effects (e.g., cache use) if applicable, and use full
sentences so the behavior is clear to readers.

ini "gopkg.in/ini.v1"
)

// when possible; the fallback method does not support selecting a file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Complete the comment or rephrase for clarity.

The comment appears to start mid-sentence with "when possible", making it unclear what action should be taken when possible. Consider rephrasing to provide complete context.

🔎 Suggested fix
-// when possible; the fallback method does not support selecting a file.
+// explorerBinArgs attempts to launch the user's default file manager using the .desktop file
+// when possible; the fallback method does not support selecting a file.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// when possible; the fallback method does not support selecting a file.
// explorerBinArgs attempts to launch the user's default file manager using the .desktop file
// when possible; the fallback method does not support selecting a file.
🤖 Prompt for AI Agents
v3/internal/fileexplorer/fileexplorer_linux.go around line 16: the inline
comment is incomplete ("when possible; the fallback method does not support
selecting a file.") — replace it with a single, clear sentence that states the
intended behavior and context, e.g. indicate that the code should use the native
file-selection dialog when available and fall back to a less-capable method that
cannot select files; ensure the comment is self-contained and explains what
"when possible" refers to.

@leaanthony leaanthony merged commit a06d558 into v3-alpha Jan 1, 2026
70 of 71 checks passed
@leaanthony leaanthony deleted the v3/performance-improvements branch January 1, 2026 20:03
leaanthony added a commit that referenced this pull request Jan 4, 2026
goccy/go-json has a type address calculation bug on Windows that causes
index out of bounds panic when decoding user-defined types like FileFilter
and OpenFileDialogOptions for the first time.

The warmup approach cannot cover all possible user types, so reverting to
stdlib encoding/json is the safest fix.

Reverts the bindings.go portion of PR #4843.
leaanthony added a commit that referenced this pull request Jan 4, 2026
…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.
leaanthony added a commit that referenced this pull request Jan 4, 2026
…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.
leaanthony added a commit that referenced this pull request Jan 4, 2026
…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.
leaanthony added a commit that referenced this pull request Jan 4, 2026
…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.
Grantmartin2002 pushed a commit to Grantmartin2002/wails that referenced this pull request Apr 29, 2026
wailsapp#4843)

* 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: goccy/go-json#474

* 📝 Add docstrings to `v3/performance-improvements` (wailsapp#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: goccy/go-json#474

* 📝 Add docstrings to `v3/performance-improvements`

Docstrings generation was requested by @leaanthony.

* wailsapp#4843 (comment)

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 <lea.anthony@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Grantmartin2002 pushed a commit to Grantmartin2002/wails that referenced this pull request Apr 29, 2026
…panic (wailsapp#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 wailsapp#4843.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant