refactor: unify the two state node description methods and keep describe() cheap - #25528
Open
totally-not-ai[bot] wants to merge 2 commits into
Open
refactor: unify the two state node description methods and keep describe() cheap#25528totally-not-ai[bot] wants to merge 2 commits into
totally-not-ai[bot] wants to merge 2 commits into
Conversation
StateNode had two methods for describing a node in a message to a developer: formatOwnerComponentToString, used in the error thrown when a component is moved between UIs, and describe, used in log messages. Fold everything the former had into describe: the attach location, the markup of an element that has no component, the text of a text node, and the fallback to the component's own toString when component tracking is disabled, which is the case in production mode. Application-provided parts are truncated since the outer HTML of an element covers its whole subtree. The error message for a component moved between UIs now also starts with the node id, which is redundant there but keeps a single description format.
describe() is called on log paths that run per RPC message, so it must not serialize the outer HTML of the whole subtree, and it should not write application content such as the text of a text node into a server log. Identify an element that has no component by its id and class attributes instead, and report a text node without its text. Also pin that a component whose toString() throws does not turn a description into an error, which is the situation the removed formatOwnerComponentToString was originally written to guard against.
|
Contributor
Legioth
self-requested a review
September 9, 2026 07:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



StateNodehad two separate ways of describing a node in a message to a developer:formatOwnerComponentToString()— used only in theIllegalStateExceptionthrown when a component is moved between UIsdescribe()— used in log messagesTwo formats for the same job meant two places to maintain and two sets of quirks. This folds everything the former did into
describe()and deletes it:toString()when component tracking is disabled, which is the case in production mode — without a create/attach location it is the only way to tell instances of the same class apartThe error message for a component moved between UIs now also starts with the node id. That is redundant in an exception about one specific node, but it keeps a single description format everywhere.
Keeping it structural and cheap
describe()is called on log paths that run per RPC message, so the second commit tightens what it is allowed to do:formatOwnerComponentToString()usedownerElement.toString(), which serializes the markup of the entire subtree. An element that has no component is now identified by itsidandclassattributes instead.text node, without its text — application content does not belong in a server log.toString()) are truncated at 200 characters.describe()runs application code — an overriddengetParent(),hashCode(), ortoString()— so it still catchesRuntimeExceptionand reports the failure with the details gathered so far, rather than turning a log message into an error. That guarantee is whatformatOwnerComponentToString()was originally written for, and it is now pinned by tests for both thegetParent()and thetoString()case.No public or protected API changed:
describe()already existed and only its Javadoc was updated;formatOwnerComponentToString()was private.Test summary
idandclass, and the description contains neither a child's text nor<spandescribe()must not serialize the subtree's outer HTML on a per-message log path...and the whole description stays under 500 charstext node, without its text, and without athrewmarkercreated at <file>:<line>andattached at <file>:<line>, and not the component'stoString()toString()would be noise when locations existtoString()getParent()that throws yields a description naming the component class and the exception type instead of propagatingdescribe()is called from log/error paths; a throwing description would replace the real problemtoString()that throws is handled the same wayformatOwnerComponentToString()guarded, now reached through thetoString()fallback in row 5Offending component: node id=and still contains the component classdescribe()without losing the information a developer needsStateNodeTest.describe_element_nodeIdTagAndAttributesIncludedWithoutMarkup— 1StateNodeTest.describe_longAttributeValue_truncated— 2StateNodeTest.describe_textNode_reportedAsTextNodeWithoutContent— 3StateNodeDescribeTest.describe_trackingEnabled_createAndAttachLocationIncluded— 4StateNodeTest.describe_componentWithoutTrackingInformation_classAndToStringIncluded— 5StateNodeTest.describe_getParentThrows_failureDescribedWithDetailsSoFar— 6StateNodeTest.describe_toStringThrows_failureDescribedWithDetailsSoFar— 7ComponentTest.cannotMoveComponentsToOtherUI— 8StateNodeDescribeTestis a new class deliberately placed incom.vaadin.flowrather thancom.vaadin.flow.internal, becauseComponentTrackerskips frames from the latter when looking for the relevant stack frame — a test there could never observe a create location.Deliberately left untested: the
used in '<route class>'branch, which this change only moves into the new privatedescribeElementhelper without altering its behaviour, and the truncation of a very long componenttoString(), which shares the sametruncatehelper already pinned by row 2. Thenode id=only case for a node without element features is still covered by the pre-existingStateNodeTest.describe_nodeWithoutElementFeatures_onlyNodeIdIncluded, unchanged on this branch.