Skip to content

Feature/webcomponents support#362

Open
IlianaB wants to merge 8 commits into
masterfrom
feature/webcomponents-support
Open

Feature/webcomponents support#362
IlianaB wants to merge 8 commits into
masterfrom
feature/webcomponents-support

Conversation

@IlianaB

@IlianaB IlianaB commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Extends the UI5 Inspector to detect and inspect UI5 Web Components-based applications, alongside the existing OpenUI5/SAPUI5 support. Pages using either framework — or both — show a merged control tree with per-framework introspection.


What's implemented

Detection. Pages using @ui5/webcomponents are detected automatically alongside classic UI5 detection. Mixed pages report both.

Control tree. Web components are listed alongside classic UI5 controls. On mixed pages the two are merged under a single tree, with the WebC portion grouped under a synthetic root labelled with the framework name and version. Private members (names beginning with _) are filtered from properties, slots, and events to keep the panel focused on the public API. Tree updates on DOM changes are debounced (150 ms) to collapse rapid bursts.

Properties tab. Reads property metadata via element.constructor.getMetadata() and current values from the live element. Editable inline (writes back via element[name] = value).

Slots tab. On pure WebC pages the "Aggregations" tab label is renamed to "Slots". Slotted content is resolved via element[slotData.propertyName || slotName] (how the framework itself exposes slot assignments), so nested elements are surfaced with their _ids.

Events tab. Lists declared events; when an event's metadata declares a detail shape, param types are reported.

Actions tab. Focus, Copy to Console, Copy HTML to Console. Invalidate is intentionally omitted — web components re-render automatically on property/attribute changes.

App Info tab (pure WebC). General, Configuration (theme, language, timezone, animation mode, calendar type, etc.), Registered tags, Registered features, Interop (OpenUI5 detection), Runtimes (when more than one is active). Data comes from meta.Runtimes[0] (packages/base/src/Runtimes.ts). Long lists (registered tags, features) are alphabetised and emitted as arrays so DataView preserves order.

Right-click "Inspect UI5 element" in the page selects the corresponding row in the tree. The context menu label was renamed from "Inspect UI5 control" since "control" is SAPUI5 terminology.

Tree hover highlight overlays the visible element on the page. Some components (e.g. ui5-breadcrumbs-item) are light-DOM data carriers with zero rect; the highlighter walks up to the shadow root and resolves the visible representation by the framework's - convention.


Architecture

The existing 4-layer communication (panel ↔ background ↔ content script ↔ page-injected script) was fully reusable. New pieces:

  • app/vendor/WebComponentsToolsAPI.js — introspection API. Exposes getFrameworkInformation, getRenderedControlTree, getControlProperties, getControlAggregations, getControlEvents, getElementById. Aligned with the framework source (UI5Element.ts, UI5ElementMetadata.ts): identifies web components via element.isUI5Element === true, reads the framework version from meta.Runtimes[0].version, uses element._id as the canonical id.
  • app/scripts/injected/detectWebComponents.js — page-context detection.
  • app/scripts/content/detectWebComponents.js — content-script that injects the detector and, once detection succeeds, acts as the message bridge between the background and the MAIN-world script.
  • app/scripts/injected/webcMain.js — MAIN-world script that handles all runtime interactions (tree updates, property edits, focus, copy, right-click, tree hover). A top-of-file block comment documents the communication path and action-name conventions for future maintainers.

Script injection uses chrome.scripting.executeScript({world: 'MAIN'}) for the WebC scripts, bypassing page CSP that would block <script src> injection. This was required to support strict-CSP hosts (e.g. Fiori shell). Classic UI5 still uses the older <script src> approach.


Panel changes

  • New message handlers: on-webc-detected, on-webc-not-detected, on-webc-script-injection, on-receiving-initial-data-webc, on-application-dom-update-webc.
  • Tree merging (_getMergedControlTree) combines classic + WebC trees into one view.
  • The "Aggregations" tab label is renamed to "Slots" dynamically when the current frame is pure WebC.
  • App Info precedence rule on mixed pages: classic UI5 wins, since it provides richer info (loaded libraries, modules, bootstrap config, URL parameters); documented inline where the rule is applied.
  • The synthetic WebC group root (webc_root) is non-selectable and non-hoverable — its handlers no-op when the id matches.
  • Overlay text mentions UI5 Web Components alongside OpenUI5/SAPUI5.

Shared highlighter refactor

modules/content/highLighter.ts was refactored from a singleton with id-based lookup to a factory taking a resolved element:

  • Classic content script: highLighter.setDimensions(document.getElementById(id)).
  • WebC script: highLighter.setDimensions(WebCToolsAPI.getElementById(id)).
  • Two instances coexist on mixed pages via distinct wrapperId options (ui5-highlighter and ui5-highlighter-webc) so their overlays don't collide.
  • The existing highLighter.spec.js was updated to cover the new API (factory, element param, custom wrapper id).

Known limitations (deferred)

Two things intentionally not implemented in this PR:

  1. No dropdown for enum-typed properties. For example, ui5-button's accessibleRole accepts only "Button" or "Link", but the panel renders it as a free-text input rather than a select. Reason: web components ship enum types as TypeScript-only annotations (e.g. accessibleRole: ${ButtonAccessibleRole} = "Button"), which are erased at compile time. The runtime metadata contains no enum information, and the framework does not expose enum members on any global. There is no runtime path to recover the valid values. Options considered:
  • Ship the @ui5/webcomponents api.json files alongside the extension and read enum members from there — adds hundreds of KB and requires updating per WebC release.
  • Ask upstream to expose enum members via a static method on UI5Element — clean fix but out of our scope.

For now the value is still editable as free text; the user just has to know the valid values.

  1. No isDefault flag for WebC properties. Classic UI5 shows a "(default)" badge next to properties still holding their default value. For WebC we omit this badge because the metadata contains no defaultValue field — defaults are TypeScript class-field initialisers that, when compiled to ES2022 target, land on the instance and cannot be reliably distinguished from user-set values through prototype-chain inspection. A previous heuristic produced misleading results (e.g. selectionMode: "None" shown as non-default). The isDefault field is simply omitted; the DataView already suppresses the badge when the field is absent. A future implementation could snapshot defaults by sampling document.createElement(tag) once per tag at startup.

Tested against

  • Pure WebC apps (DynamicPage sample from the webcomponents repo)
  • Pure classic UI5 apps (no regression)
  • Mixed apps using sap.f.gen.ui5.webcomponents_fiori.dist.* wrapper controls (both trees populate cleanly without duplication, since wrappers keep their web components inside shadow DOM)

@cla-assistant

cla-assistant Bot commented Jul 8, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@IlianaB
IlianaB force-pushed the feature/webcomponents-support branch from bced311 to 182fa6c Compare July 8, 2026 07:47
IlianaB added 4 commits July 8, 2026 10:58
Extends the UI5 Inspector to detect and inspect UI5 Web Components-based
applications, alongside existing classic OpenUI5/SAPUI5 support. Pages
using both frameworks show a merged control tree.

New files:
- app/vendor/WebComponentsToolsAPI.js: introspection API for web components
  (tree walking, properties, slots, events) using
  element.constructor.getMetadata()
- app/scripts/injected/detectWebComponents.js: page-context detection script
- app/scripts/content/detectWebComponents.js: content script for detection
  and message bridging between background and the MAIN-world injected script
- app/scripts/injected/webcMain.js: MAIN-world script handling
  get-initial-information-webc, do-control-select, do-control-property-change,
  with debounced MutationObserver for live tree updates

Modified:
- app/manifest.json: register detectWebComponents content script and add
  WebComponentsToolsAPI.js to web_accessible_resources
- app/scripts/background/main.js: add do-webc-injection handler that uses
  chrome.scripting.executeScript with world: 'MAIN' (bypasses page CSP)
- app/scripts/devtools/panel/ui5/main.js: WebC message handlers
  (on-webc-detected, on-receiving-initial-data-webc, etc.), merged tree
  rendering, dynamic 'Aggregations' to 'Slots' tab rename for pure WebC apps
- app/html/panel/ui5/index.html: overlay text mentions UI5 Web Components

Private members (prefixed with _) are filtered out of properties, slots,
and events to keep the panel focused on the public API.
- Detect web components via element.isUI5Element === true (the framework's
  own getter) instead of a tag-prefix + getMetadata check.
- Read framework version from meta.Runtimes[0].version, the JS property
  the runtime sets on the meta element.
- Resolve slotted content via element[slotData.propertyName || slotName],
  which is how the framework exposes slot assignments. Fixes the Slots
  tab, which was previously always empty.
- Walk the prototype chain to find the framework-applied default value
  for a property; fall back to type-based defaults. Improves the
  isDefault flag for non-empty default strings (e.g. 'None').
- Read event detail metadata (eventData.detail) and surface param types
  in the Events tab.
- Send applicationInformation alongside the WebC initial data so the
  Application Info tab is no longer blank for pure WebC apps. Classic UI5
  takes precedence when both frameworks are present, since it provides
  richer info.
Send the controlActions payload in the structure the DataView expects
({ actions: { data: [...] }, own: { options: { controlId } } }) so the
buttons are rendered. Add message handlers for do-control-focus,
do-copy-control-to-console, and do-control-copy-html. Invalidate is
omitted since web components re-render automatically on property and
attribute changes.

Each handler guards on WebCToolsAPI.getElementById so it no-ops on
classic UI5 ids, avoiding double-handling on mixed pages.
…hlighter

Right-click + 'Inspect UI5 element' did not select web components in the
tree, and hovering a tree row did not highlight the element on the page.

Both stem from the same root cause: the classic mousedown handler walks
up looking for data-sap-ui (which web components don't have) and the
classic highlighter resolves elements via document.getElementById (which
only matches DOM ids, not the framework-assigned _id).

- Add a parallel mousedown handler in webcMain.js that walks up looking
  for isUI5Element === true and reports element._id.
- Add an on-control-tree-hover handler in webcMain.js that resolves
  the id via WebCToolsAPI and positions a highlighter overlay.
- Refactor modules/content/highLighter.ts from a singleton-with-id-lookup
  to a factory-with-element. Each caller resolves the id itself
  (document.getElementById for classic, WebCToolsAPI for webc) and passes
  an Element. The factory accepts a wrapperId option so multiple
  instances on the same page do not collide. Update the highLighter spec
  to cover the new API.
- Some web components (e.g. ui5-breadcrumbs-item) are light-DOM data
  carriers with zero rect; their visible representation is rendered
  inside the parent's shadow root with id '<itemId>-<suffix>'. Add a
  helper that walks up to the shadow host and resolves a target with a
  real layout box.
- Rename the context menu label from 'Inspect UI5 control' to
  'Inspect UI5 element', since 'control' is SAPUI5 terminology while
  the menu now also covers web components.
@IlianaB
IlianaB force-pushed the feature/webcomponents-support branch from 182fa6c to a2e3ad8 Compare July 8, 2026 08:10
IlianaB added 4 commits July 8, 2026 11:28
- Tighten shadow-root id match in _findVisibleInShadow: previously
  '[id^=ui5wc_1]' also matched ui5wc_10, ui5wc_100, etc.; now requires
  an exact match or a '-<suffix>' continuation.
- Make the synthetic '__webc_root__' tree node non-selectable: its
  on-select and on-hover handlers in the panel no-op when the id is
  the WebC group root, since it has no element on the page.
- Drop the dead 'target._id || target.id' fallback in the right-click
  handler. UI5Element's _id is a guaranteed getter, so the fallback
  is unreachable; using target._id directly makes the assumption
  explicit.
- Document the precedence rule used to populate the App Info tab on
  mixed pages (classic UI5 wins because it provides richer info than
  the WebC equivalent).
- Add an orientation block comment at the top of webcMain.js
  describing the MAIN-world placement, message-bridging path, and
  action-name conventions, so a reviewer can navigate the file
  without piecing the flow together.
Web Components has no declared defaultValue in metadata. Class field
initializers compiled to ES2022 land on the instance, so we cannot
reliably distinguish the framework default from a user-set value via
prototype-chain inspection. The previous heuristic produced misleading
results (e.g. selectionMode='None' shown as non-default; ''
unconditionally treated as the string default).

Stop emitting isDefault and remove the helper that approximated it.
DataView already omits the '(default)' badge when the field is absent,
so the UI silently loses the indicator rather than displaying wrong
information. The structure is reversible: a future implementation can
snapshot defaults by sampling document.createElement(tag) per tag at
startup.
Previously the App Info tab for pure WebC pages only showed a single
'General' section (framework name, version, user agent, URL). The
framework exposes far more through meta.Runtimes[0] (see
packages/base/src/Runtimes.ts).

Surface what is available, in dedicated sections:

- General: framework name, version, runtime description, user agent,
  application URL.
- Configuration: theme, themeRoot, baseTheme, language, timezone,
  animationMode, calendarType, secondaryCalendarType, noConflict,
  defaultFontLoading, enableDefaultTooltips, firstDayOfWeek,
  legacyDateCalendarCustomizing, ignoreUrlParams.
- Registered tags (collapsed by default): all ui5-* tags the framework
  knows about, with a count in the section title.
- Registered features (collapsed by default): feature module names.
- Interop: openUI5Detected and openUI5LoadedFirst, when reported.
- Runtimes: shown when more than one runtime is active on the page,
  with alias and description per entry.

Each section is built by a small standalone function that returns
null when there's nothing to report, so the orchestrator stays simple
and the section list is open for extension.

Add a comment in webcMain.js noting that message payload shapes
(event.detail.target vs event.detail.data.controlId etc.) vary per
action because they're determined by the panel side, mirroring
injected/main.js.
The previous implementation built the section data as an object with
numeric string keys (1, 2, 3, ...). DataView sorts object keys
alphabetically by default, which placed '10' before '2', '100' before
'20', etc. — visually scrambling the list.

Emit the data as an array instead: DataView preserves array index
order. Also sort entries alphabetically before emission so a 124-tag
list is easy to scan. Applies to registered tags, registered features,
and the (rarely populated) runtimes section.
@IlianaB
IlianaB force-pushed the feature/webcomponents-support branch from a2e3ad8 to 2f215c1 Compare July 8, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant