Skip to content
Merged
Show file tree
Hide file tree
Changes from 208 commits
Commits
Show all changes
209 commits
Select commit Hold shift + click to select a range
aba36f5
Fix MauiView leaks detached platform views when SafeAreaEdges include…
KarthikRajaKalaimani Jun 3, 2026
fad2855
[Android][Testing] Add dedicated CI stage for SafeAreaEdges tests on …
NafeelaNazhir Jun 4, 2026
89301e8
[Mac] Fix for DeviceDisplay.MainDisplayInfoChanged event not getting …
HarishwaranVijayakumar Jun 4, 2026
a714f92
Fixed Incorrect Window.Y and Window.Height values when closing a maxi…
Dhivya-SF4094 Jun 4, 2026
e63aea9
[Testing] Refactoring Feature Matrix UITest Cases for RadioButton Con…
HarishKumarSF4517 Jun 4, 2026
bcae41f
[iOS/Mac Catalyst 26] Fix NavigationPage content not extending behind…
SyedAbdulAzeemSF4852 Jun 5, 2026
30e208d
[Android] Fix MapHandler leaks when removed Pin instances are retaine…
SubhikshaSf4851 Jun 5, 2026
1358b45
Fix Android foreground geolocation update distance (#35783)
jfversluis Jun 5, 2026
2373539
[Android] - Fix KeepScrollOffset Behavior During Dynamic Item Additio…
prakashKannanSf3972 Jun 7, 2026
bed9b74
Don't pack .NET Standard (#32203)
mattleibow Jun 7, 2026
254559d
[ci] Remove trimming workaround (#33624)
rmarinho Jun 7, 2026
f0cd21e
Rename RunOnAndroid CI lane to match its Linux pool (#34866)
Copilot Jun 7, 2026
4e99b98
Add UsePlatformHandler<T> for custom BlazorWebView backends (#34225)
Redth Jun 7, 2026
ec18117
[HouseKeeping] Fix inconsistant owner type of DropCommandProperty (#3…
NirmalKumarYuvaraj Jun 8, 2026
4165fa6
Make MSIX install in Windows test cake script resilient (#35670)
akoeplinger Jun 8, 2026
5a71293
[iOS] Fixed Webview LoadFile ignore directories. (#31040)
NirmalKumarYuvaraj Jun 8, 2026
542c280
[Android] Fix DragGestureRecognizer DragStarting Command/Event fired …
HarishwaranVijayakumar Jun 8, 2026
5c088b2
[iOS & macOS] Fixed IndicatorView square shape does not update on loa…
NanthiniMahalingam Jun 9, 2026
89696f3
Fix RelativeSource Binding is NULL with XAML SourceGen and IsAotComp…
KarthikRajaKalaimani Jun 10, 2026
bc580f6
[Android] Fix CollectionView items not resizing after orientation cha…
SyedAbdulAzeemSF4852 Jun 10, 2026
54b2666
[Android] Fix RadialGradientBrush crash on Android when view has zero…
SyedAbdulAzeemSF4852 Jun 11, 2026
9b4d624
[Windows] Fix SwipeItem IsVisible not refreshing native swipe items w…
SubhikshaSf4851 Jun 11, 2026
551ffce
[Android] Fix grouped CollectionView grid row spacing regression (#35…
Shalini-Ashokan Jun 11, 2026
9ba0aa5
[Android] Fix RenderThread SIGSEGV crash with multiple auto-sizing We…
praveenkumarkarunanithi Jun 11, 2026
cd98879
Fix incorrect DestinationPage in NavigationPage OnNavigatingFrom even…
Shalini-Ashokan Jun 11, 2026
4995ec3
Fix Shell.Title binding in TitleView (#35800)
jfversluis Jun 12, 2026
df47b74
[Android] Fix for Android RefreshView incorrectly triggering pull-to-…
BagavathiPerumal Jun 12, 2026
9402fd5
Fix build error caused by duplicate property change registration in S…
SubhikshaSf4851 Jun 12, 2026
e2f4db2
[Android] Fix SearchHandler.ClearPlaceholderEnabled not updated dynam…
SyedAbdulAzeemSF4852 Jun 12, 2026
3f65eac
Bridge DI-registered Essentials implementations to static facades (#3…
Redth Jun 12, 2026
6e3b46c
[iOS, Mac] Fix Shell back button history menu does not update after r…
HarishwaranVijayakumar Jun 13, 2026
d2b629e
Make GenerateMsixCert work without admin on subsequent runs (#35663)
akoeplinger Jun 13, 2026
83abf0b
Reduce boolean boxes across the framework (#35596)
pictos Jun 14, 2026
b5b4b74
Support shared/custom Platforms folder mappings in SingleProject (#35…
Redth Jun 14, 2026
291eb87
[iOS][CV2] Fix Rotating the Simulator causes the text on the collecti…
devanathan-vaithiyanathan Jun 15, 2026
c3940dd
[Windows] FlyoutPage: Fix CollapsedPaneWidth mapping and updates (#33…
devanathan-vaithiyanathan Jun 15, 2026
2009b65
[iOS 26] Fix Shell TitleView not resizing on device rotation (#35883)
SyedAbdulAzeemSF4852 Jun 17, 2026
b253b2d
[WIP] [Android] Fix CollectionView KeepScrollOffset regressed after P…
HarishwaranVijayakumar Jun 17, 2026
93ddfc8
[CV2][Mac] Fix Vertical List page slows down when resizing the window…
devanathan-vaithiyanathan Jun 18, 2026
62e479a
[iOS] Fix ScrollView with RTL FlowDirection and Horizontal Orientatio…
devanathan-vaithiyanathan Jun 18, 2026
b3c588b
Fix memory leak when clearing shared PathSegments from PathFigure (#3…
devanathan-vaithiyanathan Jun 19, 2026
9b7ea1e
[Windows] Fix PlatformTicker.Windows.cs — IsRunning always returns fa…
HarishwaranVijayakumar Jun 19, 2026
a7fcad2
[Android] Fix WebView.CanGoBack() returning true on first navigated p…
praveenkumarkarunanithi Jun 19, 2026
0e67249
[Android] Fix crash in Label FormattedText span position recalculati…
Shalini-Ashokan Jun 19, 2026
bc561a8
[Windows] Throws ArgumentOutOfRangeException on WinUI when setting Ma…
SubhikshaSf4851 Jun 20, 2026
f439916
[Android] Fix Shell SearchHandler toolbar icon tint (#36016)
erikzhang Jun 20, 2026
45aaa43
Fix StrokeDashArray on Border does not reset when set to null (#29910)
devanathan-vaithiyanathan Jun 21, 2026
770302d
[BlazorWebView] Fixed Allow force reload for URLs with dots in query …
tw4 Jun 21, 2026
283bd0b
Fix safe area CSS in Blazor Hybrid templates for Android (#34463)
Copilot Jun 21, 2026
ecfc02c
[Android] Restore OnBackButtonPressed interception support when using…
Dhivya-SF4094 Jun 21, 2026
2f1d8e1
Fix s_currentTweener++ in AnimationExtensions is not thread-safe, pro…
HarishwaranVijayakumar Jun 21, 2026
b2608d1
Fixed [Windows] KeepScrollOffset Behavior Not Working as Expected in …
Shalini-Ashokan Jun 21, 2026
bc24081
[iOS]Fix Shadow Property Incorrectly Modifies Visual Transform Matrix…
devanathan-vaithiyanathan Jun 21, 2026
6583afa
Fix Picker reacts to data changes after the page is closed (#33733)
devanathan-vaithiyanathan Jun 22, 2026
ab42dd9
[Android] CarouselView: Fix position jump when tapping non-Start ali…
praveenkumarkarunanithi Jun 22, 2026
514a218
[Android] Shell Flyout: Apply safe-area insets to the flyout containe…
devanathan-vaithiyanathan Jun 22, 2026
bafa9b2
[iOS/Mac][CV2] Fix CarouselView2 freezes with infinite loop when IsSc…
SyedAbdulAzeemSF4852 Jun 22, 2026
35c32e6
[iOS] ScrollView: Fix landscape safe-area handling around the notch (…
KarthikRajaKalaimani Jun 22, 2026
3a30809
[Windows] Fix for MenuFlyoutItem displaying icon in monochrome instea…
SyedAbdulAzeemSF4852 Jun 22, 2026
9b68c5c
[Testing] Feature matrix UITest Cases for MenuBarItem Control (#34007)
HarishKumarSF4517 Jun 22, 2026
03199eb
Fix SwipeView Threshold changes width and offset of the side menu (wh…
KarthikRajaKalaimani Jun 22, 2026
8bc5408
[iOS] Fix ScrollView does not resize when children are removed from S…
devanathan-vaithiyanathan Jun 22, 2026
8174c33
[iOS] Fix Font autoscaling does not work in realtime (#34445)
HarishwaranVijayakumar Jun 23, 2026
01dea78
[Windows] Fix FilePicker Returns wrong ContentType for *.webp files (…
devanathan-vaithiyanathan Jun 23, 2026
c0ed4fd
[iOS, MacCatalyst] CollectionView: Fix grid spacing updates for first…
KarthikRajaKalaimani Jun 23, 2026
57f6afe
Fix SearchHandler.QueryIcon, ClearIcon, and ClearPlaceholderIcon do n…
SubhikshaSf4851 Jun 24, 2026
162a619
[iOS] Fix transparent Shell navigation bar appearing opaque after key…
Shalini-Ashokan Jun 24, 2026
1980c5b
[Android] ScrollView doesn't work in the Shell Flyout Header (#33192)
KarthikRajaKalaimani Jun 24, 2026
13eb50a
Fix Path.Data and Path.RenderTransform visual tree leaks with shared …
devanathan-vaithiyanathan Jun 24, 2026
8fa4d88
[iOS & Mac] Fix NullReferenceException in CollectionViewHandler2 afte…
SubhikshaSf4851 Jun 25, 2026
f9ea49b
Don't unpack netstandard workload packs (fix nightly vs-workload.prop…
PureWeen Jun 25, 2026
4a8ed17
Fix the Aspire ServiceDefaults project template (#33025)
mattleibow Jun 25, 2026
a736b1f
[Android][Windows] Fix GraphicsView passing fractional dirtyRect dime…
SyedAbdulAzeemSF4852 Jun 25, 2026
972c159
[iOS/macCatalyst] Fix Entry cursor reset when typing with TextTransfo…
SyedAbdulAzeemSF4852 Jun 25, 2026
7c8ca43
Update Windows App SDK to 1.8.260529003 and related dependencies (#34…
mattleibow Jun 26, 2026
88276cb
[Mac] Fix Titlebar content is not aligned to left on fullscreen (#30378)
devanathan-vaithiyanathan Jun 26, 2026
bfc342b
Fix CI pipeline build warnings (RS0016/RS0017, XA1006, duplicate spla…
rmarinho Jun 26, 2026
8f23f3a
Fix Shell Navigating event not firing on ShellContent change (#34351)
jpd21122012 Jun 27, 2026
1ac86f8
[CV2][iOS] Fix MeasureFirstItem measuring non-first cells (#36159)
devanathan-vaithiyanathan Jun 28, 2026
1a25b58
[Android] Fix ActivityStateManager leaking lifecycle callbacks on Act…
Shalini-Ashokan Jun 28, 2026
e03fb5b
[Android] Fix: TapGestureRecognizer not firing on horizontal ScrollVi…
Shalini-Ashokan Jun 29, 2026
73df0ca
[iOS] Fix WebView.Reload() with HtmlWebViewSource returns WebNavigati…
devanathan-vaithiyanathan Jun 29, 2026
eec854f
Revert unwanted changes
devanathan-vaithiyanathan Jun 29, 2026
66b1871
Shapes: Fix TransformGroup child subscription leaks (#36150)
HarishwaranVijayakumar Jun 29, 2026
dab137a
[iOS] Fix RefreshView causes CollectionView scroll position to reset …
devanathan-vaithiyanathan Jun 29, 2026
7eeee8a
Fix Shell flyout item template does not update selected visuals after…
devanathan-vaithiyanathan Jun 29, 2026
4f57c1c
[Windows] Make Layout AutomationPeer internal + opt-in for screen rea…
kubaflo Jun 29, 2026
423ba42
Fix Picker selection when SelectedItem is removed from ItemsSource (#…
jpd21122012 Jun 29, 2026
87b3de6
[Android] CarouselView: Fix touch interception to delegate vertical s…
Dhivya-SF4094 Jun 30, 2026
ace083b
[iOS][CV2] Fix CollectionView2 on iOS iPad jumps/scrolls randomly whe…
devanathan-vaithiyanathan Jun 30, 2026
96fd203
[Android] Fix ScrollView scroll position changes unexpectedly when Or…
devanathan-vaithiyanathan Jun 30, 2026
97b3600
[Mac]Clear button not hidden when text is cleared (#34737)
devanathan-vaithiyanathan Jun 30, 2026
ead9fe0
Fix Android nested gesture ownership (#35863)
AdamEssenmacher Jun 30, 2026
7dbe75e
Route Android HybridWebView JS->.NET messages through HTTP intercept …
jonathanpeppers Jun 30, 2026
dca3e57
[iOS] Fix MediaPicker FullPath for PHPicker results (#35805)
jfversluis Jul 1, 2026
6e5c2ae
Fix AdaptiveTrigger memory leak when VisualStateGroups are replaced a…
Shalini-Ashokan Jul 1, 2026
08bc902
[Android] Fix status bar icon contrast with custom colorPrimary (#36214)
jpd21122012 Jul 1, 2026
fb067d5
[Testing] Refactoring Feature Matrix UITest Cases for Editor Control …
LogishaSelvarajSF4525 Jul 1, 2026
834dcb3
Fix for GetPosition truncates fractional coordinates to integers In O…
SuthiYuvaraj Jul 1, 2026
e281e7f
Fix build failure caused by Issue36154 (#36296)
SubhikshaSf4851 Jul 2, 2026
726df58
Fix GeometryGroup.Children.Clear() leaking shared child geometries (#…
devanathan-vaithiyanathan Jul 2, 2026
3f05ecc
[iOS] Respect InputTransparent when updating UserInteractionEnabled (…
jfversluis Jul 3, 2026
c21ceb1
[iOS] Fix Shell Flyout SafeArea Rendering (#33335)
devanathan-vaithiyanathan Jul 3, 2026
b9c0368
[Android] Fix Entry password visibility when using Keyboard.Password …
jpd21122012 Jul 3, 2026
d5331bc
[Android] CarouselView: Fix scroll to last item after data reset in l…
SyedAbdulAzeemSF4852 Jul 5, 2026
61a2742
Fix for PathGeometry.Figures.Clear() leaks when clearing shared PathF…
BagavathiPerumal Jul 5, 2026
68c32e7
[iOS/MacCatalyst] Fix ActivityIndicator frozen static frame when IsRu…
SyedAbdulAzeemSF4852 Jul 6, 2026
799defa
[Testing] SafeArea Feature Matrix Test Cases for ContentView (#36065)
TamilarasanSF4853 Jul 6, 2026
7653022
[Testing] SafeArea Feature Matrix Test Cases for Border (#36049)
TamilarasanSF4853 Jul 7, 2026
cbb6cff
[leak-fix] Fix SwipeItemView.Command memory leak (Fixes #36343) (#36357)
github-actions[bot] Jul 8, 2026
4264190
[leak-fix] Fix ListView.RefreshCommand memory leak (Fixes #36344) (#3…
github-actions[bot] Jul 8, 2026
58bc51a
[Android] Fix blank gap when HasNavigationBar=false with SafeAreaEdge…
praveenkumarkarunanithi Jul 8, 2026
a55df8a
[iOS/MacCatalyst] Fix CollectionView (CV2) expanding to fill availabl…
SyedAbdulAzeemSF4852 Jul 8, 2026
dd6da09
[iOS] Button: Fix text updates with CharacterSpacing (#36326)
jfversluis Jul 9, 2026
2268aff
[inflight/current] [Windows/Mac] Fix Shell page title leaking into na…
devanathan-vaithiyanathan Jul 9, 2026
a534b9c
Fix FlexLayout measuring items wider than their arranged size (#36416)
Kas-code Jul 9, 2026
8424680
[Windows] Fix: StrokeLineCap values have no effect on Windows platfor…
SubhikshaSf4851 Jul 9, 2026
08164ae
[leak-fix] Fix BackButtonBehavior.Command memory leak (Fixes #36345) …
github-actions[bot] Jul 9, 2026
fb465af
[iOS] Fix WebView GoBack/GoForward not working for HtmlWebViewSource …
devanathan-vaithiyanathan Jul 9, 2026
90596f8
[Android] MediaPicker: Fix photo picker completion from child activit…
KarthikRajaKalaimani Jul 9, 2026
aadddd9
[tests] Re-enable Android memory-leak UI tests (#27411) (#36480)
kubaflo Jul 10, 2026
c040542
[leak-fix] Fix IndicatorView.ItemsSource memory leak (Fixes #35775) (…
kubaflo Jul 11, 2026
5bd640b
Fix for memory leak in Accelerometer.ReadingChanged/ShakeDetected by …
BagavathiPerumal Jul 13, 2026
3342b5d
Fix SwipeItem background not updating on AppTheme change (#36271)
Shalini-Ashokan Jul 13, 2026
de7f5e5
Shorten BindingSourceGen hint names (#35986)
jfversluis Jul 14, 2026
862cb61
[Android, iOS/Mac] Fix IsSwipeEnabled Not Working on CarouselView (#3…
Dhivya-SF4094 Jul 14, 2026
7497244
[Testing] Refactoring Feature Matrix UITest Cases for Label Control (…
NafeelaNazhir Jul 14, 2026
ae3a345
[iOS/Mac] Fix CarouselView Position and IndicatorView Not Updating Wh…
Dhivya-SF4094 Jul 15, 2026
1ca82e2
[Windows]Fix ContentPresenter Throws System.ArgumentException When Dy…
devanathan-vaithiyanathan Jul 15, 2026
9de2fd0
[iOS] Fix Span TapGestureRecognizer hitbox misalignment in FormattedS…
devanathan-vaithiyanathan Jul 15, 2026
3dedb03
Fix SearchHandler.ShowsResults runtime updates across platforms (#36215)
jpd21122012 Jul 15, 2026
d9b52f2
[leak-fix] Fix TableView.Root memory leak (Fixes #36355) (#36513)
github-actions[bot] Jul 16, 2026
1b60279
[leak-test] Add memory-leak coverage for VerticalStackLayout family (…
github-actions[bot] Jul 16, 2026
3ea1511
[leak-fix] Fix GeometryGroup.Children memory leak (Fixes #36365) (#36…
github-actions[bot] Jul 16, 2026
2ee8663
Fixed When focus is on entry and closing the app the "object disposed…
KarthikRajaKalaimani Jul 16, 2026
14d1d26
[Windows] - Fix InputTransparent layouts losing background color visi…
prakashKannanSf3972 Jul 16, 2026
8bd2353
[Windows] Fix Dynamic ShellContent Title Updates at Runtime (#35998)
devanathan-vaithiyanathan Jul 16, 2026
0900b22
Fix FormattedString memory leak caused by shared Span (#36294)
Dhivya-SF4094 Jul 16, 2026
3ad732f
[Android] Fix: WebView clamping small CSS font sizes (#36559)
SubhikshaSf4851 Jul 16, 2026
ec90ee8
[Mac] Fix Prevent CollectionView scroll position reset on Mac Catalys…
Vignesh-SF3580 Jul 17, 2026
bfeb56f
[Testing] Refactoring Feature Matrix UITest Cases for Button Control …
LogishaSelvarajSF4525 Jul 17, 2026
deb0a9a
[Android] Fix for CurrentItem is updated incorrectly when the Carouse…
BagavathiPerumal Jul 17, 2026
d7ef096
Fix font/splash assets missing on first build and when incremental ou…
PureWeen Jul 17, 2026
c73e1b5
[Android] Fix flyout item TextColor not applied from Label styles (#3…
StephaneDelcroix Jul 17, 2026
338f0bc
[Windows]Fix: Label.FormattedText does not clear previous TextHighlig…
SubhikshaSf4851 Jul 17, 2026
b358bcb
[Android] Fix RTL layout children shifting into display cutout after …
praveenkumarkarunanithi Jul 18, 2026
e70a453
Fixed HideSoftInputOnTapped="True" on one page permanently affects al…
KarthikRajaKalaimani Jul 18, 2026
449b15d
[iOS, MacCatalyst] Fix Image and ImageButton BackgroundColor does not…
devanathan-vaithiyanathan Jul 19, 2026
c50e87a
[ci-fix] De-flake SoftInputExtensionsPageTest with WaitForElement (An…
github-actions[bot] Jul 20, 2026
c263dbc
[iOS] Fix: Carouselview restores stale Position over CurrentItem afte…
SubhikshaSf4851 Jul 20, 2026
e892fcd
Fix inconsistent Grouped CollectionView (GridItemsLayout) rendering o…
Shalini-Ashokan Jul 20, 2026
b5302b1
[Testing] Feature Matrix UITest Cases for Shell SearchHandler (#36631)
NafeelaNazhir Jul 20, 2026
e9dd92e
[ci-fix] De-flake Issue31731 PickerDialogDoesNotCrashWhenPagePoppedWh…
github-actions[bot] Jul 21, 2026
85c1463
[ci-fix] De-flake SafeAreaShouldWorkOnAllShellTabs (StaleElementRefer…
github-actions[bot] Jul 21, 2026
789d6db
Fixed Fixed Build error on PR 36411 (#36720)
Dhivya-SF4094 Jul 22, 2026
9dffd57
[ci-fix] De-flake RootViewSizeDoesntChangeAfterBackground: wait for r…
HarishKumarSF4517 Jul 22, 2026
7b23243
[Windows] Fix: Border containing SwipeView causes native crash (#36714)
SubhikshaSf4851 Jul 22, 2026
6a977da
[Testing] Refactoring Feature Matrix UITest Cases for Shapes Control …
HarishKumarSF4517 Jul 22, 2026
f759f7d
[iOS] Entry: Fix Done accessory hit testing (#36413)
Redth Jul 22, 2026
0206d1f
Improve XEXPR diagnostics for interpolated strings and lambda method …
StephaneDelcroix Jul 22, 2026
03364e4
Update SkiaSharp to 4.150.1 and HarfBuzzSharp to 14.2.1.1 (#36255)
mattleibow Jul 23, 2026
a39e2f3
[Android] Fix ActivityIndicator remaining visible after IsRunning/IsV…
SyedAbdulAzeemSF4852 Jul 23, 2026
22b1cd0
[iOS] Fix Button TextColor not updating when CharacterSpacing is set …
Shalini-Ashokan Jul 23, 2026
c51a74d
[iOS/MacCatalyst] Controls.Core: Enable MemoryAnalyzers (#36312)
kubaflo Jul 24, 2026
86530cf
[iOS] Add spacing below Done accessory button (#36756)
Redth Jul 24, 2026
80a7e2e
[Testing] Refactoring Feature Matrix UITest Cases for Image and Imag…
NafeelaNazhir Jul 24, 2026
fb04326
[Windows] Fix COMException when changing Image BackgroundColor via bi…
Shalini-Ashokan Jul 24, 2026
704d74f
Fix CS1612 for struct sub-property paths in C# expression source gen …
StephaneDelcroix Jul 24, 2026
e4adf66
Bump the aspnetcore group with 3 updates (#36783)
dependabot[bot] Jul 25, 2026
c2dc570
Fix inflight/current build breaks (duplicate MAUIX2017, obsolete AddC…
kubaflo Jul 25, 2026
f303a11
Bump Magick.NET-Q8-AnyCPU from 14.14.0 to 14.15.0 (#36771)
dependabot[bot] Jul 25, 2026
a58bc62
[Windows] Fix Modal page has a 32px title bar gap at the top in full-…
devanathan-vaithiyanathan Jul 26, 2026
f3867c6
[Windows] Fix Entry.Completed firing during IME candidate confirmatio…
HarishwaranVijayakumar Jul 26, 2026
3bc6559
Fix GradientBrush.GradientStops.Clear() leaks brushes when stops are …
devanathan-vaithiyanathan Jul 26, 2026
9a01d1e
[iOS/MacCatalyst] Fix Entry clear button appearing dimmed compared to…
SyedAbdulAzeemSF4852 Jul 26, 2026
d5bd003
Fix grouped CollectionView section removal on iOS (#36758)
baaaaif Jul 26, 2026
9de1fe7
[Windows] Fix SearchHandler FontSize, FontFamily, VerticalTextAlignme…
devanathan-vaithiyanathan Jul 26, 2026
c1cfbd1
[inflight] Fix netstandard build break: ReferenceEqualityComparer in …
kubaflo Jul 26, 2026
d2d6c2d
Support xmlns prefixes and attached bindable properties in C# express…
StephaneDelcroix Jul 27, 2026
fe25444
[iOS] Add legacy Done accessory compatibility switch (#36776)
Redth Jul 27, 2026
03ab44d
[MacCatalyst] Fix page content clipped behind TitleBar on first navig…
Shalini-Ashokan Jul 27, 2026
e7d8e56
[Windows] Fix WebView appdir local-host detection and align its resou…
mattleibow Jul 27, 2026
a47bf13
[iOS And Windows] Fix Entry IsPassword ignored when Keyboard is set t…
SyedAbdulAzeemSF4852 Jul 27, 2026
c8d91b3
Bump the aspnetcore group with 3 updates (#36865)
dependabot[bot] Jul 28, 2026
a6e2d41
[iOS] Fix ButtonHandler clearing custom UIButton styling when Backgro…
devanathan-vaithiyanathan Jul 28, 2026
0217296
[iOS/Mac] Fix UI becoming unresponsive when adding many Entry/Editor …
praveenkumarkarunanithi Jul 29, 2026
eb49525
Avoid duplicate startup refresh in .NET MAUI mobile template (#36687)
jonathanpeppers Jul 29, 2026
65c852f
[Testing] SafeArea Feature Matrix Test Cases for Grid (#36764)
TamilarasanSF4853 Jul 29, 2026
e0df1f6
[Android] Fix Shell singleton page rendering blank when re-pushed aft…
HarishwaranVijayakumar Jul 31, 2026
a89dd1c
[Windows] Fix Shell.Items.Clear() memory leak by disconnecting child …
Shalini-Ashokan Jul 31, 2026
e14454e
Fix signature of sysctlbyname pinvoke (#36916)
BrzVlad Jul 31, 2026
99bb5e0
[Windows] Fix NullReferenceException in FlexLayout.Layout when _root …
sheiksyedm Jul 31, 2026
e931f6a
[BlazorWebView] Add StaticContentCacheControlProvider for static cont…
Kebechet Jun 2, 2026
bae31a9
Fix cache-control test analyzer errors and wire provider into Tizen
Kebechet Jun 9, 2026
3fd432c
[BlazorWebView] Pass original request URI to StaticContentCacheContro…
Kebechet Jun 12, 2026
84fd031
[BlazorWebView] Pass original request URI to provider on WinUI folder…
Kebechet Jun 13, 2026
07f8955
[BlazorWebView] Harden static content cache-control resolution
Kebechet Jun 14, 2026
54ec7fe
[BlazorWebView] Document and test empty-string cache-control as default
Kebechet Jul 14, 2026
581ddeb
[BlazorWebView] Address Copilot review: CR/LF guard, threading docs, …
Kebechet Jul 14, 2026
d30fcb9
[BlazorWebView] Make cache-control device tests order-independent via…
Kebechet Jul 14, 2026
b03a979
[BlazorWebView] Guard StaticContentCacheControlProvider against excep…
Kebechet Jul 22, 2026
5802de8
[BlazorWebView] Make cache-control override test resistant to persist…
Kebechet Jul 22, 2026
88f30bf
[BlazorWebView] Reference StaticContentCacheControl.Default from the …
Kebechet Jul 22, 2026
6f1987c
[BlazorWebView] Treat whitespace-only cache-control override as the d…
Kebechet Jul 22, 2026
c9e1a3c
Add configurable BlazorWebView cache control
Copilot Aug 8, 2026
d54398d
Test repeated Android static content requests
Copilot Aug 8, 2026
976cd8f
Cache BlazorWebView static responses across platforms
Copilot Aug 8, 2026
eda8191
Merge PR branch after rebuilding on current main
Copilot Aug 8, 2026
34554f9
Merge net11.0 into static content caching
Copilot Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 79 additions & 5 deletions src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning;
using Android.Content;
using Android.Runtime;
Expand All @@ -21,6 +23,9 @@ internal class WebKitWebViewClient : WebViewClient
private static readonly Uri AppOriginUri = new(AppOrigin);

private readonly BlazorWebViewHandler? _webViewHandler;
// Android does not store WebResourceResponse instances returned from ShouldInterceptRequest in Chromium's
// HTTP cache. Keep explicitly cacheable static responses in a bounded per-WebView cache instead.
private readonly StaticContentResponseCache _staticContentResponseCache = new();

public WebKitWebViewClient(BlazorWebViewHandler webViewHandler)
{
Expand Down Expand Up @@ -100,7 +105,7 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
}

// 2. Check if the request is for a Blazor resource
response = GetResponse(requestUri, _webViewHandler?.Logger);
response = GetResponse(request, requestUri, _webViewHandler?.Logger);
if (response is not null)
{
return response;
Expand All @@ -113,9 +118,33 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
return base.ShouldInterceptRequest(view, request);
}

private WebResourceResponse? GetResponse(string requestUri, ILogger? logger)
private WebResourceResponse? GetResponse(IWebResourceRequest request, string requestUri, ILogger? logger)
{
if (!Uri.TryCreate(requestUri, UriKind.Absolute, out var uri) || !AppOriginUri.IsBaseOf(uri))
{
return null;
}

StaticContentCacheRequestBehavior? cacheRequestBehavior = null;
if (_staticContentResponseCache.TryGet(requestUri, out var cachedResponse))
{
cacheRequestBehavior = StaticContentResponseCachePolicy.GetRequestBehavior(request.Method, request.RequestHeaders);
if (cacheRequestBehavior == StaticContentCacheRequestBehavior.Default)
{
var cachedRequestUri = QueryStringHelper.RemovePossibleQueryString(requestUri);
logger?.HandlingWebRequest(cachedRequestUri);
logger?.ResponseContentBeingSent(cachedRequestUri, cachedResponse.StatusCode);
return CreateWebResourceResponse(cachedResponse);
}

if (cacheRequestBehavior == StaticContentCacheRequestBehavior.Refresh)
{
_staticContentResponseCache.Remove(requestUri);
}
}

var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(requestUri);
var originalRequestUri = requestUri;
requestUri = QueryStringHelper.RemovePossibleQueryString(requestUri);

logger?.HandlingWebRequest(requestUri);
Expand All @@ -127,8 +156,43 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
{
var contentType = headers["Content-Type"];

// By default local caching is disabled so that user scripts are always re-executed. Applications can
// opt specific resources into caching via BlazorWebView.StaticContentCacheControlProvider.
// The original (unstripped) URI is passed so the provider can act on query strings (e.g. img.png?v=2).
// See https://github.com/dotnet/maui/issues/8279
var cacheControlOverride = StaticContentCacheControl.ResolveOverride(_webViewHandler?.VirtualView, originalRequestUri, contentType, logger);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Cross-Platform Behavioral Consistency — Android WebView does not use its HTTP cache for responses returned from shouldInterceptRequest; the headers on a WebResourceResponse are surfaced to JS (fetch/XHR) but are not interpreted by the WebView for caching. Concrete scenario: an app opts img.png into max-age=3600 via StaticContentCacheControlProvider, the new device test passes because the fetch response echoes max-age=3600, but every subsequent page navigation still calls ShouldInterceptRequest and re-reads the asset from the file provider — so the flicker reported in #8279 is not actually fixed on Android. Please verify on-device that a second load of a cacheable asset does not re-enter this method (see the companion comment on the test file); if the WebView does not honor it, either document the platform limitation on the public API or serve intercepted responses from an app-level cache.

if (cacheControlOverride is not null)
{
headers["Cache-Control"] = cacheControlOverride;
}

logger?.ResponseContentBeingSent(requestUri, statusCode);

if (statusCode == 200 &&
headers.TryGetValue("Cache-Control", out var cacheControl) &&
StaticContentResponseCachePolicy.TryGetCacheLifetime(cacheControl, out var cacheLifetime))
{
cacheRequestBehavior ??= StaticContentResponseCachePolicy.GetRequestBehavior(request.Method, request.RequestHeaders);
if (cacheRequestBehavior != StaticContentCacheRequestBehavior.Disabled)
{
if (StaticContentResponseBuffer.TryBuffer(content, originalRequestUri, logger, out var cachedContent, out var responseContent))
{
var responseToCache = new StaticContentResponse(
originalRequestUri,
contentType,
statusCode,
statusMessage,
headers,
cachedContent,
StaticContentResponseCachePolicy.GetExpiration(cacheLifetime));

_staticContentResponseCache.Set(responseToCache);
}

return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, responseContent);
}
}

return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content);
}
else
Expand All @@ -139,6 +203,15 @@ private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
return null;
}

private static WebResourceResponse CreateWebResourceResponse(StaticContentResponse response)
=> new(
response.ContentType,
"UTF-8",
response.StatusCode,
response.StatusMessage,
new Dictionary<string, string>(response.Headers, StringComparer.OrdinalIgnoreCase),
new MemoryStream(response.Content, writable: false));

public override void OnPageFinished(AWebView? view, string? url)
{
base.OnPageFinished(view, url);
Expand Down Expand Up @@ -234,11 +307,12 @@ private void RunBlazorStartupScripts(AWebView view)

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
//_webViewManager = null;
_staticContentResponseCache.Clear();
}

base.Dispose(disposing);
}

private class JavaScriptValueCallback : Java.Lang.Object, IValueCallback
Expand Down
20 changes: 19 additions & 1 deletion src/BlazorWebView/src/Maui/BlazorWebView.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.FileProviders;
using Microsoft.Maui;
Expand Down Expand Up @@ -67,6 +67,24 @@ public string StartPath
/// <inheritdoc cref="IBlazorWebView.RootComponents" />
public RootComponentsCollection RootComponents { get; }

/// <summary>
/// Gets or sets a callback that determines the <c>Cache-Control</c> header value used for static content
/// (such as images, fonts, or stylesheets) served from the app's content root.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Public API Surface Design — The documented scope is narrower than the implemented scope, in a way that can break apps. The doc says the callback determines the header "for static content (such as images, fonts, or stylesheets) served from the app's content root", but the callback is invoked for every app-origin response on all platforms: the host page (index.html), _framework/blazor.webview.js, _framework/blazor.modules.json, and framework assets — none of which come from the content root. The line this PR replaces on iOS said "Disable local caching. This will prevent user scripts from executing correctly", i.e. caching those framework scripts is precisely what the no-store default was guarding against. Concrete scenario: an app writes req => "max-age=86400" (or matches on a broad path prefix), the Blazor startup script and host page get cached, and after an app update the WebView serves stale scripts and Blazor fails to start. Please document that the callback fires for framework files and the host page, and recommend opting in per-resource.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Public API Surface Design — The doc scopes the callback to "static content (such as images, fonts, or stylesheets)" and the argument type is named BlazorWebViewStaticContentRequest, but every serving path wired up in this PR invokes it for all content served from the content root, including the host page and framework assets: WebKitWebViewClient.GetResponse, BlazorWebViewHandler.iOS.SchemeHandler.StartUrlSchemeTask, WinUIWebViewManager.HandleWebResourceRequest/TryServeFromFolderAsync (which explicitly covers _framework/blazor.modules.json), and the Tizen interceptor.

Concrete failure: a developer follows the documented intent and returns "max-age=86400" for a broad prefix (e.g. anything under /, or anything without a .png check). index.html and _framework/blazor.webview.js are then cached by the WebView for a day, so a shipped app update serves the stale host page/startup script — the exact scenario the pre-existing no-store default (and the "user scripts are always re-executed" comment repeated in each platform file) was protecting against.

Document here that the callback also receives the host page and _framework/* requests and that returning a cacheable value for them is unsafe, or exclude those requests from the callback.

/// <para>
/// By default no callback is set and all served content uses <c>no-cache, max-age=0, must-revalidate,
/// no-store</c>, which disables WebView caching. Provide a callback to opt specific resources into caching,
/// which can avoid repeated file reads and reduce image reload flicker when navigating between pages.
/// Return <see langword="null"/> or an empty string from the callback to keep the default behavior for a
/// given request. Cache entries remain subject to platform limits, expiration, and eviction.
/// </para>
Comment on lines +71 to +79
/// <para>
/// The callback is invoked from the platform's request handling, which may run on a background thread, so it
/// must not access UI state directly. If the callback throws, the exception is logged and the request falls
/// back to the default header.
/// </para>
/// </summary>
public Func<BlazorWebViewStaticContentRequest, string?>? StaticContentCacheControlProvider { get; set; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Public API Surface Design — API shape is inconsistent with the existing extensibility points on this control and is not forward-compatible. Every other request/navigation hook on BlazorWebView is an event (UrlLoading, BlazorWebViewInitializing, WebResourceRequested), which (a) can be wired directly in XAML — the primary way BlazorWebView is declared — while a Func<> property cannot, forcing x:Name + code-behind, and (b) can gain new information or new outputs on its EventArgs without a new API. This Func<BlazorWebViewStaticContentRequest, string?> locks the contract to exactly one input object and one Cache-Control string; the next request (ETag/Expires, per-request status code, async resolution) needs a second public API because neither the delegate signature nor the sealed BlazorWebViewStaticContentRequest can change once shipped. Consider a StaticContentRequested event whose args expose the URI/content type and a settable CacheControl, matching WebViewWebResourceRequestedEventArgs.


/// <summary>
/// Allows customizing how links are opened.
/// By default, opens internal links in the webview and external links in an external app.
Expand Down
36 changes: 36 additions & 0 deletions src/BlazorWebView/src/Maui/BlazorWebViewStaticContentRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
/// <summary>
/// Describes a request for static content served by a <see cref="BlazorWebView"/>. An instance is passed to the
/// callback set on <see cref="BlazorWebView.StaticContentCacheControlProvider"/> so the application can decide
/// which <c>Cache-Control</c> header value to send for the resource.
/// </summary>
public sealed class BlazorWebViewStaticContentRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="BlazorWebViewStaticContentRequest"/> class.
/// </summary>
/// <param name="uri">The absolute URI of the requested static content.</param>
/// <param name="contentType">The resolved MIME content type of the requested static content.</param>
public BlazorWebViewStaticContentRequest(Uri uri, string contentType)
{
ArgumentNullException.ThrowIfNull(uri);
ArgumentNullException.ThrowIfNull(contentType);

Uri = uri;
ContentType = contentType;
}
Comment on lines +17 to +24

/// <summary>
/// Gets the absolute URI of the requested static content.
/// </summary>
public Uri Uri { get; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Cross-Platform Behavioral ConsistencyUri is documented only as "the absolute URI of the requested static content", but its scheme and origin differ per platform because each handler passes its own AppOrigin-based request URL:

  • iOS / MacCatalyst: app://0.0.0.0/... (BlazorWebViewHandler.iOS.cs: AppOrigin = "app://" + BlazorWebView.AppHostAddress + "/", and StartUrlSchemeTask passes urlSchemeTask.Request.Url.AbsoluteString)
  • Android: https://0.0.0.0/... (WebKitWebViewClient.AppOrigin)
  • Windows: https://0.0.0.0/... (eventArgs.Request.Uri)
  • Tizen: http://0.0.0.0/... (BlazorWebViewHandler.Tizen.cs: AppOrigin = "http://0.0.0.0/")

Concrete failure: an app writing the natural check request.Uri.AbsoluteUri.StartsWith("https://0.0.0.0/images/") (or request.Uri.Scheme == "https") works on Android/Windows and silently never matches on iOS/MacCatalyst, so images keep flickering on exactly the platform the callback was added for. The PR's own tests only ever inspect AbsolutePath/Query, so they cannot surface this.

Either document on this property that only path/query are portable and the scheme/host are platform-specific, or normalize what is handed to the callback so all platforms agree.


/// <summary>
/// Gets the resolved MIME content type of the requested static content, for example <c>image/png</c>.
/// </summary>
public string ContentType { get; }
}
}
10 changes: 10 additions & 0 deletions src/BlazorWebView/src/Maui/IBlazorWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public string StartPath
/// </summary>
RootComponentsCollection RootComponents { get; }

/// <summary>
/// Gets a callback that returns the <c>Cache-Control</c> header value to use for a static content request
/// served by the <see cref="BlazorWebView"/>, or <see langword="null"/> to use the default (which disables
/// caching). Returning <see langword="null"/> or an empty string from the callback for a given request also
/// falls back to the default for that request. The callback may be invoked on a background thread, so it
/// must not access UI state directly. If the callback throws, the exception is logged and the request falls
/// back to the default header. Cache entries remain subject to platform limits, expiration, and eviction.
/// </summary>
Func<BlazorWebViewStaticContentRequest, string?>? StaticContentCacheControlProvider => null;

/// <summary>
/// Gets the <see cref="JSComponentConfigurationStore"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
#nullable enable
override Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler.ConnectHandler(Android.Webkit.WebView! platformView) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.set -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.BlazorWebViewStaticContentRequest(System.Uri! uri, string! contentType) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.ContentType.get -> string!
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.Uri.get -> System.Uri!
Microsoft.AspNetCore.Components.WebView.Maui.IBlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.set -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.BlazorWebViewStaticContentRequest(System.Uri! uri, string! contentType) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.ContentType.get -> string!
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.Uri.get -> System.Uri!
Microsoft.AspNetCore.Components.WebView.Maui.IBlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.set -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.BlazorWebViewStaticContentRequest(System.Uri! uri, string! contentType) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.ContentType.get -> string!
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.Uri.get -> System.Uri!
Microsoft.AspNetCore.Components.WebView.Maui.IBlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.set -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.BlazorWebViewStaticContentRequest(System.Uri! uri, string! contentType) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.ContentType.get -> string!
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.Uri.get -> System.Uri!
Microsoft.AspNetCore.Components.WebView.Maui.IBlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.set -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.BlazorWebViewStaticContentRequest(System.Uri! uri, string! contentType) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.ContentType.get -> string!
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.Uri.get -> System.Uri!
Microsoft.AspNetCore.Components.WebView.Maui.IBlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView.StaticContentCacheControlProvider.set -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.BlazorWebViewStaticContentRequest(System.Uri! uri, string! contentType) -> void
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.ContentType.get -> string!
Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest.Uri.get -> System.Uri!
Microsoft.AspNetCore.Components.WebView.Maui.IBlazorWebView.StaticContentCacheControlProvider.get -> System.Func<Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewStaticContentRequest!, string?>?
61 changes: 61 additions & 0 deletions src/BlazorWebView/src/Maui/StaticContentCacheControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
internal static class StaticContentCacheControl
{
// Historical default that disables all WebView caching of served content so that user scripts are always
// re-executed. It is applied unless the application opts a resource into caching via
// BlazorWebView.StaticContentCacheControlProvider. See https://github.com/dotnet/maui/issues/8279
internal const string Default = "no-cache, max-age=0, must-revalidate, no-store";

Comment thread
Kebechet marked this conversation as resolved.
// Returns the application-provided Cache-Control override for the request, or null to use the default.
internal static string? ResolveOverride(IBlazorWebView? blazorWebView, string requestUri, string contentType, ILogger? logger)
{
var provider = blazorWebView?.StaticContentCacheControlProvider;
if (provider is null)
{
return null;
}

// The request handlers run on background threads, so guard against a malformed URI rather than letting
// an unexpected UriFormatException surface as a crash. If parsing fails we keep the default header.
if (!Uri.TryCreate(requestUri, UriKind.Absolute, out var uri))
{
return null;
}

string? cacheControl;
try
{
cacheControl = provider(new BlazorWebViewStaticContentRequest(uri, contentType));
}
catch (Exception ex)
{
// The provider is arbitrary application code invoked from the native request-handling path. On Windows
// it runs inside an async void handler, where an escaped exception would also skip deferral.Complete()
// and hang the request. A faulty provider must not take down static asset serving, so keep the default.
logger?.StaticContentCacheControlProviderFailed(requestUri, ex);
return null;
}

// An empty or whitespace-only string is deliberately treated like null (keep the default): such a
// Cache-Control value is non-standard and engine-dependent, and is more likely an accidental result of
// string manipulation than an intentional opt-in. Explicit directives are the supported way to enable caching.
if (string.IsNullOrWhiteSpace(cacheControl))
{
return null;
}

// Values containing CR/LF are also rejected: some platforms concatenate the value into a raw response
// header block, so a stray newline would produce a malformed response or allow header injection.
if (cacheControl.Contains('\r', StringComparison.Ordinal) || cacheControl.Contains('\n', StringComparison.Ordinal))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Null Safety and Defensive Coding — Two of the three rejection paths are silent, which leaves developers with no way to diagnose an ignored value. If an app returns a value containing CR/LF (e.g. built with Environment.NewLine or read from a config file with a trailing newline), the value is discarded and the default no-store is sent with no log entry at any level — the app author sees "my caching setting does nothing" and has nothing to grep for. Same for the Uri.TryCreate failure on line 24. The throwing path (line 39) correctly logs, so the infrastructure is already here: add a warning-level log for the rejected-value and unparsable-URI cases. Secondary: new BlazorWebViewStaticContentRequest(uri, contentType) on line 32 is constructed inside the try, so a framework-side null contentType surfaces as ArgumentNullException and is logged as "The StaticContentCacheControlProvider threw an exception", misattributing a framework bug to app code — construct the request before the try.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Logic and Correctness Verification — The CR/LF rejection (and the whitespace rejection above it) silently returns null, while the only other failure path — the provider throwing — logs via StaticContentCacheControlProviderFailed. The two silent branches are the ones a developer is most likely to hit by accident.

Concrete scenario: a provider builds its value from configuration and returns "max-age=3600\n" (trailing newline from a file read) or $"max-age={ReadSetting()}" where the setting is empty. The request is served with no-cache, ..., no-store, the app still flickers, and there is nothing in the log to distinguish "my provider was never called" from "my value was rejected". Since the CR/LF branch is a security guard against header injection, dropping it without a trace also hides a genuine attack signal.

Add a log call (e.g. a LogWarning/new LoggerMessage carrying requestUri and the reason) before each return null; in these two validation branches.

{
return null;
}

return cacheControl;
}
}
}
Loading
Loading