Vaadin Flow 25.3.0-alpha8
Pre-releaseChanges since 25.3.0-alpha7
Fixes
-
Validate URL schemes against the application's own configuration on attach (#25086)
Commit · Pull requestProblem
UrlUtil.isSafeUrl(String)resolved thevaadin.url.safe-schemesconfiguration fromVaadinService.getCurrent(), which is only defined while a session is locked on the current thread. A component tree built in a background thread and attached to the UI later was therefore validated against the framework defaults instead of the application's own configuration — and in the versions where that default is a wildcard, validation was silently skipped altogether. Conversely, a URL using a scheme that the application had explicitly allowed could be rejected because the current thread's service was a different (or no) application. ## What changed -AnchorandIFramedefer the check to attach. When a URL is set while the component isn't attached, a one-shot attach listener is registered. Attaching is the first point where the right configuration is guaranteed to be known, and also the point where the value would first be sent to the browser. Setting an unsafe scheme on a detached component now throws when the component is attached rather than immediately; the setter Javadoc describes this, and the constructors that set a URL no longer documentIllegalArgumentExceptionsince their check is always deferred. - The value is cleared before the exception is thrown, so an unsafe URL isn't sent to the client even if the application catches the exception. - No more fallbacks in the component-aware path. The check resolves the configuration from the component's own UI only — never fromVaadinService.getCurrent()or the framework defaults. If no configuration can be found through an attached component, anIllegalStateExceptionnaming the property and the component is thrown instead of silently skipping the check.Page#openuses the UI it belongs to. - Bookkeeping moved intoUrlUtil.validateUrl(...)checks the URL against the best available configuration, throws with a consistent message and schedules the re-check on attach when needed;cancelUrlValidation(...)is used by the methods that replace the value without validating it (setUnsafeHref,removeHref, the stream-resource andDownloadHandlersetters). The pending registration is stored as component data, so the components need neither a field nor the related null handling, and the error-message strings are no longer duplicated. -ComponentEventBuslistener removal is now idempotent regardless of whether it happens through the returnedRegistrationor throughComponentEvent#unregisterListener, since both now share the same registration instance. This removes the need to track elsewhere whether a self-removing listener has already fired. -UrlUtil.isSafeUrl(String)is deprecated because it relies on the current thread having a locked session, and it now logs at warning level when it falls back to the default schemes — nothing will re-check the URL in that case. The component-aware checks stay silent when a check on attach is scheduled. ## TestsUrlUtilTestcovers the new helpers and the component-aware check directly fromflow-server(previously this logic was only exercised throughflow-html-components, which left the Sonar new-code coverage at 67%),IFrameTestcovers attaching an already validatedsrcand the deprecated stream-resource setter, andPageTest/AnchorTestcover the UI-based resolution and the deferred check. The component tests build their UI fromMockUIand the shared mock service and session instead of hand-written mocks. ## API Changes ### com.vaadin.flow.internal.UrlUtiljava // Added public static void validateUrl(Component component, String type, String url, String unsafeMethod) // for components expected to be attached; throws IllegalStateException if no configuration is reachable public static void validateUrl(Component component, String type, String url, String unsafeMethod, SerializableRunnable urlClearer) // defers the check to attach when the component isn't attached yet public static void cancelUrlValidation(Component component, String type) // cancels a check scheduled for attach // Changed - public static boolean isSafeUrl(String url) + @Deprecated(since = "25.3") public static boolean isSafeUrl(String url) // relies on the current thread having a locked session; use validateUrl(Component, ...) instead--------- -
Avoid NPE when canceling a JavaScript invocation of a closed UI
Commit · Pull request · IssueInvocations owned by an invisible component are retained in the UI's queue and get a detach listener registered for them. Registering that listener installs a handler on the invocation itself, and the handler stays attached to the invocation for the rest of its lifetime, since there is no way to unsubscribe it. A component that keeps the PendingJavaScriptResult and cancels it after being reused in another UI therefore runs the handler installed by the closed UI, which dereferences its cleared session. Return early when the UI no longer has a session, as its invocation queue has already been released by then. Also release the retained invocations when the UI is closed, so that the queue and the detach listener registrations on the state nodes do not outlive the UI when a detach listener fails and prevents the ones after it from running.
-
Use a relative tsconfig root for the TypeScript checker
Commit · Pull requestvite-plugin-checker builds the tsc command line as a string and splits it on spaces, so the absolute "-p " argument breaks type checking when the project directory contains a space. Vite is always started with the project root as its working directory, both for the production build and for the dev server, so a relative "." resolves to the same tsconfig.json without introducing a splittable argument.
-
Cancel a deferred id resolution superseded by a later value
Commit · Pull request · Issues 25119, 25118setAriaLabelledBy(Component) and NativeLabel.setFor(Component) resolve the target's id before the next client response. The pending resolution could not be cancelled, so it overwrote any value set afterwards in the same request, and a target that nothing referenced any more was still assigned a generated id. The id that the target is going to have is now passed to the caller right away, and the pending resolution assigns a generated id to the target only while that value is still in effect. Setting an explicit value, clearing it, or referencing another component thus cancels the resolution, leaving the superseded target's id untouched. Since the value follows the target's id if that changes before the next client response, it must not be cached within the same request. resolveOrGenerateIdLater takes a getter for the current value to detect this, and both the getter and the consumer are serializable, so the pending resolution kept in the state tree, or as an attach listener while the source element is detached, no longer makes the session fail to serialize.
-
Avoid CachedSignal lock-order inversion with the SignalTree lock
Commit · Pull request · IssueProblem
CachedSignalcould deadlock permanently against a thread committing a change to one of its dependencies: -CachedSignalheld its own monitor while calling operations that acquire a dependency'sSignalTreelock — removing/adding the internal dependency listener inrevalidateAndListen, and in the un-count callback. - A committing thread does the opposite: it holds the tree lock and, while re-running an effect inline, re-reads the cached signal and enters theCachedSignalmonitor. These opposite acquire orders are a classic ABBA inversion, and once both threads got there the deadlock was unrecoverable. ## Fix Mirror the approach already used inEffectfor the same problem: capture/clear the dependency registration under the monitor, but performremove()/onNextChange()(the calls that grab the tree lock) with the monitor released. To keep that safe against concurrency, a generation counter is bumped under the monitor by every revalidation and by the teardown of the last external listener. Only the attempt whose captured generation still matches installs its registration, so concurrent attempts neither double-register nor leak listeners. ## Testing Adds a deterministic regression test inComputedSignalTestthat reproduces the inversion and detects it viaThreadMXBeandeadlock detection — it fails on the old code and passes with the fix. -
Decode percent-encoded paths when looking up translation files
Commit · Pull request · IssueTranslations were silently ignored when any folder in the path to the application contained a space. A class loader returns percent-encoded URLs, so a space arrives as %20, and building a File straight from URL.getFile() produced a path that does not exist on disk. The folder lookup then found nothing, the default I18N provider was created with an empty list of locales, and the session never switched away from the default language. Nothing was logged. Resolve the folder through URL.toURI() and the jar location through Paths.get(URI.create(...)), so both are decoded before use. Where the URL is not an absolute file: URI, fall back to UrlUtil.decodeURIComponent, which unlike URLDecoder does not turn '+' into a space. Also close the JarFile that was left open while reading its entries, and log at debug level when the translation folder does not resolve to an existing directory, so this kind of failure is visible next time.
-
Keep replaced children until the new ones are in place
Commit · Pull request · IssueWhen the server clears a container and refills it in the same round trip, the client applied the two changes as separate steps: the clear emptied the container, and only then were the replacements inserted. While the container is empty the scrollable range around it collapses, and a layout in that window makes the browser reduce the scroll offset and keep the reduced value once the contents are back. Firefox runs into this with content that gets its size asynchronously, such as a FormLayout, so a surrounding Scroller jumps back towards the top after a rebuild. The children of a cleared node are now removed once the whole change set has been applied, so the replacements are attached while the old nodes are still there and the container is never empty. Nodes that the server moved to another parent and nodes that it added back to the same parent are left alone. A clear with no replacements still empties the container right away.