Skip to content

[pull] master from google:master#171

Open
pull[bot] wants to merge 298 commits into
actnwit:masterfrom
google:master
Open

[pull] master from google:master#171
pull[bot] wants to merge 298 commits into
actnwit:masterfrom
google:master

Conversation

@pull

@pull pull Bot commented Oct 12, 2022

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot]

Can you help keep this open source service alive? 💖 Please sponsor : )

@pull pull Bot added the ⤵️ pull label Oct 12, 2022
elalish and others added 29 commits February 1, 2023 14:01
* fix firefox video framerate

* refactor
* fix video texture on Firefox

* fix Safari import maps
* fixed unload VideoTexture

* fix lottie update
* Fix how "target" is handled

* Support lights

* Update config

* Update screenshots

* Update for lights again
* switch three to peer dep

* update readme

* updated faq
* 0.0.1

* bump version

* revert error
* Update Filament to 1.31.5 and re-render goldens

* Run npm i

* Bump Filament version and npm i
* updated three.js to r150

* update types
* updated to three.js r151

* r151.2

* updated goldens
* updated to three.js r151

* r151.2

* updated goldens

* DO NOT USE LEGACY LIGHTS

* fix two goldens
* Make peer dependency relationship clear in readme

* Update README.md
diegoteran and others added 30 commits May 21, 2026 14:10
)

* prevent iOS WebGL memory leak by converting gainmap to DataTexture

Converts the decoded gainmap from QuadRenderer to a standalone DataTexture
using `result.toDataTexture()`. This allows the intermediate WebGLRenderTarget
instances and their underlying WebGL framebuffers/textures to be completely
disposed of immediately via `result.dispose(true)`.
Includes a try-catch fallback to return the raw render target texture via
`result.dispose(false)` if the environment does not support the conversion.
This resolves iOS-specific WebGL context loss crashes and browser tab
reloads occurring when decoding multiple UltraHDR / Gainmap environments
in the "Lighting & Skybox" section.

* fix(texture-utils): prevent iOS WebGL memory leak by converting gainmap to DataTexture

Converts the decoded gainmap from QuadRenderer to a standalone DataTexture
using `result.toDataTexture()`. This allows the intermediate WebGLRenderTarget
instances and their underlying WebGL framebuffers/textures to be completely
disposed of immediately via `result.dispose(true)`.

Includes a try-catch fallback to return the raw render target texture via
`result.dispose(false)` if the environment does not support the conversion.

This resolves iOS-specific WebGL context loss crashes and browser tab
reloads occurring when decoding multiple UltraHDR / Gainmap environments
in the "Lighting & Skybox" section.

Also adds:
- A regression unit test in `TextureUtils-spec.ts` validating proper disposal of intermediate WebGLRenderTargets during gainmap decoding.

* fix(texture-utils): resolve iOS WebGL memory leak on LDR/gainmap env map loading

Converts the decoded gainmap from QuadRenderer to a standalone DataTexture
using `result.toDataTexture()`, explicitly setting `needsUpdate = true`
to trigger GPU WebGL texture upload of the pixel buffer. This allows
the intermediate WebGLRenderTarget framebuffers and render buffers to be
completely disposed of immediately via `result.dispose(true)`.

This resolves a critical iOS-specific WebGL context loss crash loop when
scrolling through galleries with multiple environment maps (the "Lighting & Skybox" examples).

Implements a robust verification blank-check fallback:
- Dynamically inspects the decoded DataTexture pixel buffer on the CPU.
- Transparently falls back to the original render target texture if the converted DataTexture is completely black (frequent in headless testing environments with GPU-less software rendering).
- Prevents overriding of the colorSpace to SRGBColorSpace on half-float decoded textures, which is invalid under standard WebGL.

Also adds a regression unit test in `TextureUtils-spec.ts` to verify proper disposal of intermediate WebGLRenderTargets during gainmap decoding.
Mobile WebKit/Blink engines (such as Safari and Chrome on iOS) automatically
zoom in the entire page viewport when a text or numeric input is focused if
the computed font-size is strictly less than 16px. This page-level zoom remains
locked even after the input is blurred, disrupting the layout and forcing
users to manually pinch-to-zoom out to return to normal dimensions.
This change fixes the issue globally for the documentation examples and
provides educational guidelines for developers copying the patterns:
1. Systemic Styles Guard: Added a scoped style rule in examples.css setting
   'font-size: 16px' for all inputs and selects inside floating '.controls'
   containers. This covers active widgets (e.g. Model Transformations,
   Post-Processing, variant swapping) across almost all example pages
   without impacting other standard utility forms.

2. Developer Guidance Tip: Appended an educational "Mobile WebUX Tip" callout
   under the Model Transformations demo text block inside scenegraph/index.html,
   explaining the 16px browser threshold quirk to developers who copy-paste
   the custom markup blocks for their own standalone applications.
Verified layout presentation and viewport locking (locked at stable 1.0x scale)
under small-screen interaction models. Playwright test suite passes cleanly.
This commit resolves separate layout overlap issues where large, multi-row control panels block the default bottom-right system AR button on narrow and high-density viewports (e.g., Samsung Galaxy S22 and Pixel 9 Pro Fold).

Following a responsive custom AR button strategy, this change:
1. Adds a custom slotted 'View in AR' button to both the 'Model Transformations' (scenegraph/index.html) and 'Dynamic Scaling' (loading/index.html) examples.
2. Keeps their respective numeric/slider controls positioned at the default bottom-left.
3. Uses CSS Container Queries with a 700px width threshold to smoothly translate the custom AR button to the top-right corner on compact or high-density mobile viewports, ensuring complete visual separation and easy physical access.

These changes have been visually validated side-by-side inside standalone simulated viewports for absolute layout robustness.
…inputs (#5156)

On Samsung and Chrome mobile platforms, simple unconfigured inputs are analyzed by standard browser credentials heuristics, causing Gboard to incorrectly display sensitive password/payment/address autofill strip buttons on measurement values.

This fix updates the model transform fields to use safe, explicit attributes:  - type="number" and step="any" to prevent general text field heuristics  - inputmode="decimal" to guarantee decimal numeric layout triggers  - autocomplete="off", autocorrect="off", autocapitalize="none", spellcheck="false" parameters to disable autofill caching pipelines

Additionally, this commit adds clean CSS overrides to suppress default browser increment/decrement spin arrows and enforce the compact 40px styling rules on desktop
…standard HTML <select> elements when active inside WebXR DOM Overlays. (#5157)

In mobile browsers (such as Chrome on Android), standard <select> tags attempt to open OS-native dialog wheels or bottom sheets. Because these platform-native widgets reside outside the DOM tree, they cannot render correctly over the WebXR context. This results in the element's actual hit-test bounds shifting to the bottom center of the screen, leaving the visual dropdown field unresponsive to direct clicks, while tapping empty space at the bottom triggers the option wheel.
To solve this, we created and integrated a standard-compliant, safe, and ultra-lightweight <custom-select> Web Component (custom-select.js) across all scene-graph examples:
- Renders dropdown option items as custom DOM elements floating entirely within the DOM Overlay hierarchy, avoiding native platform widgets.
- Safe Event Interception: Catches 'beforexrselect' on the component and triggers 'event.preventDefault()' to block touches from bubbling up as unintended background WebXR 3D scene gesture inputs.
- High Performance: Leverages standard Shadow DOM <slot> elements with a native 'slotchange' listener to dynamically sync light-DOM option children (replacing heavy recursive MutationObservers).
- Perfect Compatibility: Exposes native getters/setters for '.value', '.selectedIndex', and a native mapped array for the '.options' collection. This allows drop-in slot swaps with zero modifications to existing scripts.
- Adjusts parent '.controls' container selectors to 'overflow: visible' to let floating option panels display cleanly without clipping.
- Added an interactive desktop phone simulator page (select-repro.html) that models the touch bound displacement side-by-side with our custom fix.
Files Added/Modified:
- packages/modelviewer.dev/examples/scenegraph/custom-select.js (Added lightweight select component)
- packages/modelviewer.dev/examples/scenegraph/select-repro.html (Added dual-state visual smartphone simulator)
- packages/modelviewer.dev/examples/scenegraph/index.html (Integrated custom-select and updated controls overflow styles)
…s on Chrome 142 (#5158)

In recent versions of Google Chrome on Android (specifically version 142.0.7444.159 with ARCore 1.51), the default exit (X) button in the upper right corner of the screen is completely unresponsive to direct screen taps while in AR mode. Touches on the button are bypassed, passing straight through as 3D WebXR target-select gestures (which trigger background model placement/movements) instead of exiting the AR session.
Root Cause:
In the Shadow DOM template styling, the absolute container `.slot.exit-webxr-ar-button` is set to `pointer-events: none` (to keep empty screen coordinates transparent to 3D room actions). It relies on a generic `.slot > *` rule to restore pointer events. However, because the default close anchor `#default-exit-webxr-ar-button` is nested inside a `<slot>` element, it is a grandchild rather than a direct child of the host container.
In Chrome 142's updated event hit-testing pipeline, grandchild elements nested inside a parent with `pointer-events: none` are completely skipped if intermediate structural tags (such as the slot element itself) do not explicitly declare `pointer-events: auto`. This makes the visually visible exit button a physical "ghost" target to touch hit-testing.
Solution:
Apply explicit `pointer-events: auto` overrides to the exit button's slot structure:
1. Enable it directly on the intermediate `<slot>` tag to open the hit-test chain.
2. Target any custom user-slotted elements with a `::slotted(*)` pseudo-element rule.
3. Assert it directly on the default fallback close button `#default-exit-webxr-ar-button`.
This guarantees that both the default exit button and custom user-defined close button slots are always interactive, while keeping the rest of the absolute overlay transparent to background 3D interactions.
- Enforce explicit trailing slashes on dynamic sidebar and documentation links to prevent "Please follow this link" redirect pages from loading on clashing path structures (Annotations and Animation).
- Update IS_AR_QUICKLOOK_CANDIDATE to evaluate known iOS third-party browsers (Chrome, Firefox, Edge, etc.) via User Agent first, correcting an invalid WKWebView message-handler check and enabling AR options on standard iOS Chrome tabs.
When a hotspot's light DOM element is shifted in the children list (e.g., because another hotspot is deleted in a reactive UI loop), the browser detaches and re-attaches (re-slots) the element.
While the core AnnotationMixin correctly reuses the existing Three.js Hotspot instance and increments its reference count, the newly attached DOM button element never receives the current visibility attributes (such as `data-visible`) because the hotspot's general visibility state has not transitioned. This leaves the re-slotted buttons in a hollow, invisible, or un-annotated state depending on CSS selectors matching :not([data-visible]).
This patch updates the addHotspot path in the AnnotationMixin to immediately set the visibility attribute and dispatch the target `hotspot-visibility` custom event on the incoming node when its associated Hotspot object already exists. This guarantees that re-slotted elements inherit and render with the correct Three.js visibility states instantly.
`updateModelTransforms` accepted an `orientation` parameter but never
used it, so `<extra-model orientation="...">` had no effect. Parse the
value with `parseExpressions`/`normalizeUnit` (supporting `deg`/`rad`
units) and apply it as an Euler rotation on the model's quaternion,
using the same `$roll $pitch $yaw` → `Euler(pitch, yaw, roll, 'YXZ')`
convention as `applyTransform` and the documented `orientation` attribute.

Also fix the multimodel example: bare numbers are treated as radians by
the unit parser, and the axis ordering follows roll/pitch/yaw, so update
the orientation attributes to `"0deg 0deg 30deg"` / `"0deg 0deg -30deg"`
(yaw = third term) to produce the intended 30° Y-axis rotation.

Add a test covering both initial application and dynamic updates of the
orientation attribute.
…: Infinity' for valid modelViewer.appendAnimation(animName, { repetitions: 1 }) (#5178)

It actually just emitted the warning and kept repetitionCount to 1 anyway, the else branch was added in 305e07c
* fix: emit finished event for one-time animations

Two separate bugs prevented the 'finished' event from being dispatched
when an animation was played a single time (repetitions: 1):

1. Mixer event subscriptions were lost on model load (regression from
   4.3.0). The multi-model refactor recreates the animation mixers on
   every setSource(), but the mixer event subscriptions are registered
   only once, when the animation feature is constructed — before any
   model is loaded, while there are no mixers yet. They were never
   re-applied to the mixers created later, so 'finished' (and 'loop')
   were never dispatched. ModelScene now remembers its subscriptions and
   attaches them to every mixer it creates.

2. Setting the animationName property and then calling play() looped
   forever (present in all versions). Assigning animationName schedules
   an asynchronous reactive update whose updated() re-triggers playback;
   it did so with the default options (repetitions: Infinity /
   LoopRepeat), overwriting a play({repetitions: 1}) issued in the same
   tick, so the clip looped and never finished. Using the declarative
   animation-name attribute avoided this only because the property never
   changed. play() now records its options and the re-trigger reuses them
   instead of resetting to an infinite loop.

Both paths are covered by regression tests in animation-spec.ts.

* docs: add "Play Once" animation example

Demonstrate playing an animation a single time on demand: set animationName, reset currentTime to 0, call play({repetitions: 1}), and re-enable the control on the 'finished' event. Doubles as a live test of the one-time-animation finished fix.
Several debug `console.log` calls were left in and ship in 4.3.x, spamming
the browser console on normal use: on every page load the model-viewer base
logs `[onExtraModelChanged]`, `IntersectionObserver fired!`,
`[$updateSource] called!` and `[$updateSource] BAILING OUT EARLY!`, the AR
flow logs `Attempting to present in AR with ...`, and ARRenderer logs
`[processInput] Setting goalPosition.y ...`.

Remove these developer debug logs. The genuine error diagnostic in
Renderer.ts ("could not acquire 2d context") is left untouched.
`updateModelTransforms` parsed `<extra-model offset="...">` with a plain
`split`/`Number`, so only unitless numbers (meters) worked. Parse the
value with `parseExpressions`/`normalizeUnit` instead, mirroring the
`orientation` handling, so each component may carry a length unit
(`m`/`cm`/`mm`) while bare numbers continue to be treated as meters
(e.g. `offset="-2.0 0 0"` and `offset="-200cm 0 0"` are equivalent).

Document offset unit support and the existing roll/pitch/yaw orientation
convention on the multimodel example, and update the property JSDoc.

Add a test covering unit-suffixed offsets and that bare numbers stay in
meters.
* prevent parallel deployment collisions in documentation workflow

* ci: cache Playwright browsers and add concurrency to unit-tests
Fixes #5186.
### Summary & Root Cause
When an `<extra-model>` child element was removed from `<model-viewer>` (e.g. via `element.remove()`), the DOM child change triggered a `slotchange` event, which invoked `this[$updateSource]()`. However, because the primary model had already finished loading, `this.loaded` remained `true`. Inside `[$updateSource]()`, an early return check (`if (this.loaded || ...)`) caused the update to bail out before invoking `scene.setSource()`. As a result, the removed `<extra-model>`'s 3D mesh was orphaned and remained rendered inside the Three.js scene graph.
### Changes Made
* **`model-viewer-base.ts`**:
  * Updated `[$onSlotChange]` to reset `this[$loaded] = false;` and `this[$loadedTime] = 0;` whenever the list of `<extra-model>` URLs or element count changes. This allows `[$updateSource]()` to proceed past the load guard and properly synchronize the Three.js scene graph with the DOM.
  * Added explicit handling for the edge case where all primary and extra models are removed (`this.src == null && extraModels.length === 0`), directly invoking `this[$scene].reset()` and clearing loaded state.
* **`ModelScene.ts`**:
  * Updated `reset()` to ensure `this.extraUrls = [];` is properly cleared during scene resets.
* **`extra-model-spec.ts`**:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.