using XFESystem.Collections; namespace XFESystem.Browser; public sealed partial class BrowserService { public BrowserDomLifetime? ReadLifetimeDiagnostics()=>WithGate(()=>dom?.Lifetime); public WeakCollectionLifetime? ReadWeakCollectionLifetime()=>WithGate(()=>runtime?.WeakCollectionLifetime); /// Only the current owner calls this. Background proxies schedule /// it on their worker after any indivisible script callback returns. public void Release()=>WithGate(()=> { ReleasePageOwners("Release"); dom=null;runtime=null;document=null;displayList=null;metricCanvas=null;textMetrics=new BrowserFallbackTextMetrics(); layoutEngine.ReleaseDocument(); linkedStyles.Clear();pendingResources.Clear();scheduledResources.Clear();executedScripts.Clear(); hoveredNode=pointerDownNode=null;resourceLoader=null;pageDiagnostics=null;ResetDocumentLoading(); return 0; }); // All callers already execute on the page's exclusive owner. Keep this // common path explicit: native cleanup cannot depend on finally/finalizers. private void ReleasePageOwners(string reason) { var previousDom=dom;var previousRuntime=runtime; var domBefore=previousDom?.Lifetime; var weakBefore=previousRuntime?.WeakCollectionLifetime; previousDom?.ReleaseRetainedState();previousRuntime?.Release(); var domAfter=previousDom?.Lifetime; var weakAfter=previousRuntime?.WeakCollectionLifetime; // Drop each released owner before diagnostics. A throwing observer // must not leave a dead engine published or interrupt navigation. dom=null;runtime=null; images.Clear(); imageFormats.Clear(); imagePixels=0; scriptPolicy=null; styleAddresses.Clear(); pendingNavigation=null; string records=""; if(domBefore is not null&&domAfter is not null) records="XFE_BROWSER_RELEASE|HandlesBefore|"+domBefore.OutstandingHandles+ "|HandlesAfter|"+domAfter.OutstandingHandles+"\n"; if(weakBefore is not null&&weakAfter is not null) records+="XFE_BROWSER_WEAK_RELEASE|TablesBefore|"+weakBefore.Tables+ "|EntriesBefore|"+weakBefore.Entries+"|TablesAfter|"+weakAfter.Tables+ "|EntriesAfter|"+weakAfter.Entries+"|Created|"+weakAfter.Created+ "|Disposed|"+weakAfter.Disposed+"|OwnerHandlesCreated|"+weakAfter.TableHandlesCreated+ "|OwnerHandlesDisposed|"+weakAfter.TableHandlesDisposed+"|Page|"+paintIdentity+"|Reason|"+reason+"\n"; if(records.Length!=0)TraceReleasedOwner(records); } private void TraceReleasedOwner(string text) { try { Trace?.Invoke(text); } catch(Exception error)when(error is not OutOfMemoryException and not StackOverflowException) { /* Diagnostic observers do not own page cleanup. */ } } }