{"record":{"id":"fee52fb750d45826","repo":"dotnet/wpf","slug":"sr-automation-recursivepubliccall-automationpeer","errorCode":null,"errorMessage":"SR.Automation_RecursivePublicCall","messagePattern":"SR\\.Automation_RecursivePublicCall","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs","lineNumber":742,"sourceCode":"        protected virtual AutomationHeadingLevel GetHeadingLevelCore()\n        {\n            return AutomationHeadingLevel.None;\n        }\n\n        //\n        // INTERNAL STUFF - NOT OVERRIDABLE\n        //\n        internal virtual Rect GetVisibleBoundingRectCore()\n        {\n            // Too late to add abstract methods, since this class has already shipped(using default definition)!\n            return GetBoundingRectangle();\n        }\n\n        ///\n        public Rect GetBoundingRectangle()\n        {\n            if (_publicCallInProgress)\n                throw new InvalidOperationException(SR.Automation_RecursivePublicCall);\n\n            try\n            {\n                _publicCallInProgress = true;\n                _boundingRectangle = GetBoundingRectangleCore();\n            }\n            finally\n            {\n                _publicCallInProgress = false;\n            }\n            return _boundingRectangle;\n        }\n\n        ///\n        public bool IsOffscreen()\n        {\n            if (_publicCallInProgress)\n                throw new InvalidOperationException(SR.Automation_RecursivePublicCall);","sourceCodeStart":724,"sourceCodeEnd":760,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs#L724-L760","documentation":"AutomationPeer.GetBoundingRectangle() is a public UIA entry point that must never re-enter while another public AutomationPeer call is in progress on the same peer. The peer sets _publicCallInProgress for the duration of the Core override; if GetBoundingRectangle is called again (directly or via an override calling back into public APIs), the guard throws InvalidOperationException with SR.Automation_RecursivePublicCall.","triggerScenarios":"Calling GetBoundingRectangle() from inside an override of GetBoundingRectangleCore() (or another Core override like IsOffscreenCore/AncestorsInvalid work) on the same peer, e.g. via UIA client callbacks re-entering during the call.","commonSituations":"Custom peer overrides that call public peer APIs instead of the Core methods; automation properties (FocusChanged/LayoutUpdated handlers) re-querying the same element synchronously during a UIA property fetch; re-entrancy from AncestorsInvalid invalidation callbacks.","solutions":["Inside overrides, call GetBoundingRectangleCore()/protected virtuals instead of the public GetBoundingRectangle().","Defer work triggered by automation events to the dispatcher queue (Dispatcher.BeginInvoke) so it runs after the in-progress call completes.","Cache the rect or compute it via UIElement APIs (TransformToVisual/PointToScreen) directly rather than re-entering the peer.","Guard custom code with a re-entrancy flag or check _publicCallInProgress before calling public APIs."],"exampleFix":"// before\nprotected override Rect GetBoundingRectangleCore()\n{\n    Rect r = GetBoundingRectangle(); // re-enters public API -> throw\n    return r;\n}\n// after\nprotected override Rect GetBoundingRectangleCore()\n{\n    return PointToScreen(new Point(0, 0)) != default ? ComputeRectFromOwner() : new Rect();\n}","handlingStrategy":"try-catch","validationCode":"// C# — check for re-entrancy before calling\nbool isPeerBusy(AutomationPeer peer) =>\n    (bool?)typeof(AutomationPeer)\n        .GetField(\"_publicCallInProgress\", BindingFlags.NonPublic | BindingFlags.Instance)\n        ?.GetValue(peer) == true;\nif (isPeerBusy(peer)) { /* defer call via Dispatcher.BeginInvoke */ }","typeGuard":"static bool CanQueryPeerSafely(AutomationPeer peer) =>\n    peer != null && !(bool?)typeof(AutomationPeer)\n        .GetField(\"_publicCallInProgress\", BindingFlags.NonPublic | BindingFlags.Instance)\n        ?.GetValue(peer) == true;","tryCatchPattern":"try\n{\n    var rect = peer.GetBoundingRectangle();\n}\ncatch (InvalidOperationException) when (ex.Message.Contains(\"recursive\") || ex.InnerException == null)\n{\n    // deferred re-query: run again after the current call completes\n    Dispatcher.CurrentDispatcher.BeginInvoke(() => QueryBoundingRectangleLater(peer));\n}","preventionTips":["Never call public AutomationPeer wrappers (GetBoundingRectangle, IsEnabled, etc.) from within *Core overrides — call the Core/protected methods or read owner element state directly.","Queue automation-event handler work with Dispatcher.BeginInvoke instead of re-querying the peer synchronously.","Cache automation property values on the owner instead of re-querying the peer.","Test custom peers under UIInspect/acccheck to force concurrent UIA property queries and catch re-entrancy early."],"tags":["wpf","automation","uia","reentrancy","invalidoperationexception"],"backgroundTag":"unsupported-operation","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}