diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41f50dee..508c7662 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.1-dev
+
+- Updated types to account for union types.
+
 ## 0.3.0
 
 - Updated source IDL to `v3.39.1`.
diff --git a/lib/src/dom/credential_management.dart b/lib/src/dom/credential_management.dart
index 555835b1..74a971c6 100644
--- a/lib/src/dom/credential_management.dart
+++ b/lib/src/dom/credential_management.dart
@@ -9,7 +9,7 @@ import 'fedcm.dart';
 import 'web_otp.dart';
 import 'webauthn.dart';
 
-typedef PasswordCredentialInit = JSAny?;
+typedef PasswordCredentialInit = JSObject;
 typedef CredentialMediationRequirement = String;
 
 @JS('Credential')
@@ -113,7 +113,7 @@ extension CredentialCreationOptionsExtension on CredentialCreationOptions {
 @JS('PasswordCredential')
 @staticInterop
 class PasswordCredential implements Credential, CredentialUserData {
-  external factory PasswordCredential(JSAny? dataOrForm);
+  external factory PasswordCredential(JSObject dataOrForm);
 }
 
 extension PasswordCredentialExtension on PasswordCredential {
diff --git a/lib/src/dom/css_animation_worklet.dart b/lib/src/dom/css_animation_worklet.dart
index e1158cc9..ec04e2a0 100644
--- a/lib/src/dom/css_animation_worklet.dart
+++ b/lib/src/dom/css_animation_worklet.dart
@@ -36,7 +36,7 @@ extension WorkletAnimationEffectExtension on WorkletAnimationEffect {
 class WorkletAnimation implements Animation {
   external factory WorkletAnimation(
     String animatorName, [
-    JSAny? effects,
+    JSObject? effects,
     AnimationTimeline? timeline,
     JSAny? options,
   ]);
diff --git a/lib/src/dom/css_font_loading.dart b/lib/src/dom/css_font_loading.dart
index 5ce1dc36..fd206a71 100644
--- a/lib/src/dom/css_font_loading.dart
+++ b/lib/src/dom/css_font_loading.dart
@@ -7,7 +7,7 @@ import 'dart:js_interop';
 import 'dom.dart';
 import 'html.dart';
 
-typedef BinaryData = JSAny?;
+typedef BinaryData = JSObject;
 typedef FontFaceLoadStatus = String;
 typedef FontFaceSetLoadStatus = String;
 
diff --git a/lib/src/dom/css_pseudo.dart b/lib/src/dom/css_pseudo.dart
index 78436df4..36f0f8f7 100644
--- a/lib/src/dom/css_pseudo.dart
+++ b/lib/src/dom/css_pseudo.dart
@@ -15,5 +15,5 @@ extension CSSPseudoElementExtension on CSSPseudoElement {
   external CSSPseudoElement? pseudo(String type);
   external String get type;
   external Element get element;
-  external JSAny? get parent;
+  external JSObject get parent;
 }
diff --git a/lib/src/dom/css_typed_om.dart b/lib/src/dom/css_typed_om.dart
index e4ad5fa1..fa55b416 100644
--- a/lib/src/dom/css_typed_om.dart
+++ b/lib/src/dom/css_typed_om.dart
@@ -419,7 +419,7 @@ class CSSImageValue implements CSSStyleValue {}
 @JS('CSSColorValue')
 @staticInterop
 class CSSColorValue implements CSSStyleValue {
-  external static JSAny? parse(String cssText);
+  external static JSObject parse(String cssText);
 }
 
 @JS('CSSRGB')
diff --git a/lib/src/dom/cssom.dart b/lib/src/dom/cssom.dart
index 3c0b7cd5..8e63db2d 100644
--- a/lib/src/dom/cssom.dart
+++ b/lib/src/dom/cssom.dart
@@ -30,7 +30,7 @@ class StyleSheet implements JSObject {}
 extension StyleSheetExtension on StyleSheet {
   external String get type;
   external String? get href;
-  external JSAny? get ownerNode;
+  external JSObject? get ownerNode;
   external CSSStyleSheet? get parentStyleSheet;
   external String? get title;
   external MediaList get media;
diff --git a/lib/src/dom/cssom_view.dart b/lib/src/dom/cssom_view.dart
index 98a79d43..ad6dc375 100644
--- a/lib/src/dom/cssom_view.dart
+++ b/lib/src/dom/cssom_view.dart
@@ -9,7 +9,7 @@ import 'geometry.dart';
 import 'html.dart';
 import 'screen_orientation.dart';
 
-typedef GeometryNode = JSAny?;
+typedef GeometryNode = JSObject;
 typedef ScrollBehavior = String;
 typedef ScrollLogicalPosition = String;
 typedef CSSBoxType = String;
diff --git a/lib/src/dom/dom.dart b/lib/src/dom/dom.dart
index aa0db2a9..ebe2abf3 100644
--- a/lib/src/dom/dom.dart
+++ b/lib/src/dom/dom.dart
@@ -512,7 +512,7 @@ extension DocumentExtension on Document {
   );
   external JSPromise exitFullscreen();
   external NodeList getElementsByName(String elementName);
-  external JSAny? open([
+  external JSObject? open([
     String unused1OrUrl,
     String nameOrUnused2,
     String features,
diff --git a/lib/src/dom/html.dart b/lib/src/dom/html.dart
index 9cfd6a7b..507e0b97 100644
--- a/lib/src/dom/html.dart
+++ b/lib/src/dom/html.dart
@@ -82,18 +82,18 @@ import 'webxr.dart';
 import 'window_controls_overlay.dart';
 import 'xhr.dart';
 
-typedef HTMLOrSVGScriptElement = JSAny?;
-typedef MediaProvider = JSAny?;
-typedef RenderingContext = JSAny?;
-typedef HTMLOrSVGImageElement = JSAny?;
-typedef CanvasImageSource = JSAny?;
-typedef OffscreenRenderingContext = JSAny?;
+typedef HTMLOrSVGScriptElement = JSObject;
+typedef MediaProvider = JSObject;
+typedef RenderingContext = JSObject;
+typedef HTMLOrSVGImageElement = JSObject;
+typedef CanvasImageSource = JSObject;
+typedef OffscreenRenderingContext = JSObject;
 typedef EventHandler = EventHandlerNonNull?;
 typedef OnErrorEventHandler = OnErrorEventHandlerNonNull?;
 typedef OnBeforeUnloadEventHandler = OnBeforeUnloadEventHandlerNonNull?;
 typedef TimerHandler = JSAny?;
-typedef ImageBitmapSource = JSAny?;
-typedef MessageEventSource = JSAny?;
+typedef ImageBitmapSource = JSObject;
+typedef MessageEventSource = JSObject;
 typedef BlobCallback = JSFunction;
 typedef CustomElementConstructor = JSFunction;
 typedef FunctionStringCallback = JSFunction;
@@ -138,8 +138,8 @@ typedef WorkerType = String;
 class HTMLAllCollection implements JSObject {}
 
 extension HTMLAllCollectionExtension on HTMLAllCollection {
-  external JSAny? namedItem(String name);
-  external JSAny? item([String nameOrIndex]);
+  external JSObject? namedItem(String name);
+  external JSObject? item([String nameOrIndex]);
   external int get length;
 }
 
@@ -148,7 +148,7 @@ extension HTMLAllCollectionExtension on HTMLAllCollection {
 class HTMLFormControlsCollection implements HTMLCollection {}
 
 extension HTMLFormControlsCollectionExtension on HTMLFormControlsCollection {
-  external JSAny? namedItem(String name);
+  external JSObject? namedItem(String name);
 }
 
 @JS('RadioNodeList')
@@ -166,7 +166,7 @@ class HTMLOptionsCollection implements HTMLCollection {}
 
 extension HTMLOptionsCollectionExtension on HTMLOptionsCollection {
   external JSVoid add(
-    JSAny? element, [
+    JSObject element, [
     JSAny? before,
   ]);
   external JSVoid remove(int index);
@@ -1182,19 +1182,19 @@ class TrackEvent implements Event {
 }
 
 extension TrackEventExtension on TrackEvent {
-  external JSAny? get track;
+  external JSObject? get track;
 }
 
 @JS()
 @staticInterop
 @anonymous
 class TrackEventInit implements EventInit {
-  external factory TrackEventInit({JSAny? track});
+  external factory TrackEventInit({JSObject? track});
 }
 
 extension TrackEventInitExtension on TrackEventInit {
-  external set track(JSAny? value);
-  external JSAny? get track;
+  external set track(JSObject? value);
+  external JSObject? get track;
 }
 
 @JS('HTMLMapElement')
@@ -1606,7 +1606,7 @@ extension HTMLSelectElementExtension on HTMLSelectElement {
   external HTMLOptionElement? item(int index);
   external HTMLOptionElement? namedItem(String name);
   external JSVoid add(
-    JSAny? element, [
+    JSObject element, [
     JSAny? before,
   ]);
   external JSVoid remove([int index]);
@@ -2002,7 +2002,7 @@ class HTMLSlotElement implements HTMLElement {
 extension HTMLSlotElementExtension on HTMLSlotElement {
   external JSArray assignedNodes([AssignedNodesOptions options]);
   external JSArray assignedElements([AssignedNodesOptions options]);
-  external JSVoid assign(JSAny? nodes);
+  external JSVoid assign(JSObject nodes);
   external set name(String value);
   external String get name;
 }
@@ -2281,7 +2281,7 @@ class CanvasUserInterface implements JSObject {}
 
 extension CanvasUserInterfaceExtension on CanvasUserInterface {
   external JSVoid drawFocusIfNeeded(
-    JSAny? elementOrPath, [
+    JSObject elementOrPath, [
     Element element,
   ]);
   external JSVoid scrollPathIntoView([Path2D path]);
@@ -2554,7 +2554,7 @@ class ImageBitmapRenderingContext implements JSObject {}
 
 extension ImageBitmapRenderingContextExtension on ImageBitmapRenderingContext {
   external JSVoid transferFromImageBitmap(ImageBitmap? bitmap);
-  external JSAny? get canvas;
+  external JSObject get canvas;
 }
 
 @JS()
@@ -4228,7 +4228,7 @@ class MessagePort implements EventTarget {}
 extension MessagePortExtension on MessagePort {
   external JSVoid postMessage(
     JSAny? message, [
-    JSAny? optionsOrTransfer,
+    JSObject optionsOrTransfer,
   ]);
   external JSVoid start();
   external JSVoid close();
@@ -4298,7 +4298,7 @@ class DedicatedWorkerGlobalScope
 extension DedicatedWorkerGlobalScopeExtension on DedicatedWorkerGlobalScope {
   external JSVoid postMessage(
     JSAny? message, [
-    JSAny? optionsOrTransfer,
+    JSObject optionsOrTransfer,
   ]);
   external JSVoid close();
   external String get name;
@@ -4343,7 +4343,7 @@ extension WorkerExtension on Worker {
   external JSVoid terminate();
   external JSVoid postMessage(
     JSAny? message, [
-    JSAny? optionsOrTransfer,
+    JSObject optionsOrTransfer,
   ]);
   external set onmessage(EventHandler value);
   external EventHandler get onmessage;
diff --git a/lib/src/dom/image_capture.dart b/lib/src/dom/image_capture.dart
index 6103ef28..166ecf0d 100644
--- a/lib/src/dom/image_capture.dart
+++ b/lib/src/dom/image_capture.dart
@@ -6,7 +6,7 @@ import 'dart:js_interop';
 
 import 'mediacapture_streams.dart';
 
-typedef ConstrainPoint2D = JSAny?;
+typedef ConstrainPoint2D = JSObject;
 typedef RedEyeReduction = String;
 typedef FillLightMode = String;
 typedef MeteringMode = String;
diff --git a/lib/src/dom/indexeddb.dart b/lib/src/dom/indexeddb.dart
index fbf7a0f9..64feffcc 100644
--- a/lib/src/dom/indexeddb.dart
+++ b/lib/src/dom/indexeddb.dart
@@ -20,7 +20,7 @@ class IDBRequest implements EventTarget {}
 extension IDBRequestExtension on IDBRequest {
   external JSAny? get result;
   external DOMException? get error;
-  external JSAny? get source;
+  external JSObject? get source;
   external IDBTransaction? get transaction;
   external IDBRequestReadyState get readyState;
   external set onsuccess(EventHandler value);
@@ -303,7 +303,7 @@ extension IDBCursorExtension on IDBCursor {
   );
   external IDBRequest update(JSAny? value);
   external IDBRequest delete();
-  external JSAny? get source;
+  external JSObject get source;
   external IDBCursorDirection get direction;
   external JSAny? get key;
   external JSAny? get primaryKey;
diff --git a/lib/src/dom/intersection_observer.dart b/lib/src/dom/intersection_observer.dart
index a039d4cd..ee0a531a 100644
--- a/lib/src/dom/intersection_observer.dart
+++ b/lib/src/dom/intersection_observer.dart
@@ -24,7 +24,7 @@ extension IntersectionObserverExtension on IntersectionObserver {
   external JSVoid unobserve(Element target);
   external JSVoid disconnect();
   external JSArray takeRecords();
-  external JSAny? get root;
+  external JSObject? get root;
   external String get rootMargin;
   external String get scrollMargin;
   external JSArray get thresholds;
@@ -85,7 +85,7 @@ extension IntersectionObserverEntryInitExtension
 @anonymous
 class IntersectionObserverInit implements JSObject {
   external factory IntersectionObserverInit({
-    JSAny? root,
+    JSObject? root,
     String rootMargin,
     String scrollMargin,
     JSAny? threshold,
@@ -93,8 +93,8 @@ class IntersectionObserverInit implements JSObject {
 }
 
 extension IntersectionObserverInitExtension on IntersectionObserverInit {
-  external set root(JSAny? value);
-  external JSAny? get root;
+  external set root(JSObject? value);
+  external JSObject? get root;
   external set rootMargin(String value);
   external String get rootMargin;
   external set scrollMargin(String value);
diff --git a/lib/src/dom/mediacapture_streams.dart b/lib/src/dom/mediacapture_streams.dart
index aaae668b..6145d940 100644
--- a/lib/src/dom/mediacapture_streams.dart
+++ b/lib/src/dom/mediacapture_streams.dart
@@ -29,7 +29,7 @@ typedef MediaDeviceKind = String;
 @JS('MediaStream')
 @staticInterop
 class MediaStream implements EventTarget {
-  external factory MediaStream([JSAny? streamOrTracks]);
+  external factory MediaStream([JSObject streamOrTracks]);
 }
 
 extension MediaStreamExtension on MediaStream {
diff --git a/lib/src/dom/orientation_sensor.dart b/lib/src/dom/orientation_sensor.dart
index c244546e..0a93b56b 100644
--- a/lib/src/dom/orientation_sensor.dart
+++ b/lib/src/dom/orientation_sensor.dart
@@ -6,7 +6,7 @@ import 'dart:js_interop';
 
 import 'generic_sensor.dart';
 
-typedef RotationMatrixType = JSAny?;
+typedef RotationMatrixType = JSObject;
 typedef OrientationSensorLocalCoordinateSystem = String;
 
 @JS('OrientationSensor')
diff --git a/lib/src/dom/sanitizer_api.dart b/lib/src/dom/sanitizer_api.dart
index 9b876905..5459b708 100644
--- a/lib/src/dom/sanitizer_api.dart
+++ b/lib/src/dom/sanitizer_api.dart
@@ -17,7 +17,7 @@ class Sanitizer implements JSObject {
 }
 
 extension SanitizerExtension on Sanitizer {
-  external DocumentFragment sanitize(JSAny? input);
+  external DocumentFragment sanitize(JSObject input);
   external Element? sanitizeFor(
     String element,
     String input,
diff --git a/lib/src/dom/service_workers.dart b/lib/src/dom/service_workers.dart
index 3241bbd8..649458b0 100644
--- a/lib/src/dom/service_workers.dart
+++ b/lib/src/dom/service_workers.dart
@@ -29,7 +29,7 @@ class ServiceWorker implements EventTarget, AbstractWorker {}
 extension ServiceWorkerExtension on ServiceWorker {
   external JSVoid postMessage(
     JSAny? message, [
-    JSAny? optionsOrTransfer,
+    JSObject optionsOrTransfer,
   ]);
   external String get scriptURL;
   external ServiceWorkerState get state;
@@ -193,7 +193,7 @@ class Client implements JSObject {}
 extension ClientExtension on Client {
   external JSVoid postMessage(
     JSAny? message, [
-    JSAny? optionsOrTransfer,
+    JSObject optionsOrTransfer,
   ]);
   external ClientLifecycleState get lifecycleState;
   external String get url;
@@ -323,7 +323,7 @@ extension ExtendableMessageEventExtension on ExtendableMessageEvent {
   external JSAny? get data;
   external String get origin;
   external String get lastEventId;
-  external JSAny? get source;
+  external JSObject? get source;
   external JSArray get ports;
 }
 
@@ -335,7 +335,7 @@ class ExtendableMessageEventInit implements ExtendableEventInit {
     JSAny? data,
     String origin,
     String lastEventId,
-    JSAny? source,
+    JSObject? source,
     JSArray ports,
   });
 }
@@ -347,8 +347,8 @@ extension ExtendableMessageEventInitExtension on ExtendableMessageEventInit {
   external String get origin;
   external set lastEventId(String value);
   external String get lastEventId;
-  external set source(JSAny? value);
-  external JSAny? get source;
+  external set source(JSObject? value);
+  external JSObject? get source;
   external set ports(JSArray value);
   external JSArray get ports;
 }
diff --git a/lib/src/dom/streams.dart b/lib/src/dom/streams.dart
index d354df5f..43c99e52 100644
--- a/lib/src/dom/streams.dart
+++ b/lib/src/dom/streams.dart
@@ -7,8 +7,8 @@ import 'dart:js_interop';
 import 'dom.dart';
 import 'webidl.dart';
 
-typedef ReadableStreamReader = JSAny?;
-typedef ReadableStreamController = JSAny?;
+typedef ReadableStreamReader = JSObject;
+typedef ReadableStreamController = JSObject;
 typedef UnderlyingSourceStartCallback = JSFunction;
 typedef UnderlyingSourcePullCallback = JSFunction;
 typedef UnderlyingSourceCancelCallback = JSFunction;
diff --git a/lib/src/dom/svg.dart b/lib/src/dom/svg.dart
index c375bfcc..b6faef77 100644
--- a/lib/src/dom/svg.dart
+++ b/lib/src/dom/svg.dart
@@ -437,7 +437,7 @@ extension SVGElementInstanceExtension on SVGElementInstance {
 class ShadowAnimation implements Animation {
   external factory ShadowAnimation(
     Animation source,
-    JSAny? newTarget,
+    JSObject newTarget,
   );
 }
 
diff --git a/lib/src/dom/trusted_types.dart b/lib/src/dom/trusted_types.dart
index e4b88bfe..09ec71a3 100644
--- a/lib/src/dom/trusted_types.dart
+++ b/lib/src/dom/trusted_types.dart
@@ -7,7 +7,7 @@ import 'dart:js_interop';
 typedef HTMLString = String;
 typedef ScriptString = String;
 typedef ScriptURLString = String;
-typedef TrustedType = JSAny?;
+typedef TrustedType = JSObject;
 typedef CreateHTMLCallback = JSFunction;
 typedef CreateScriptCallback = JSFunction;
 typedef CreateScriptURLCallback = JSFunction;
diff --git a/lib/src/dom/url.dart b/lib/src/dom/url.dart
index a6be4e9f..21b4e108 100644
--- a/lib/src/dom/url.dart
+++ b/lib/src/dom/url.dart
@@ -12,7 +12,7 @@ class URL implements JSObject {
     String base,
   ]);
 
-  external static String createObjectURL(JSAny? obj);
+  external static String createObjectURL(JSObject obj);
   external static JSVoid revokeObjectURL(String url);
   external static bool canParse(
     String url, [
diff --git a/lib/src/dom/wasm_js_api.dart b/lib/src/dom/wasm_js_api.dart
index be69af02..69b57c3a 100644
--- a/lib/src/dom/wasm_js_api.dart
+++ b/lib/src/dom/wasm_js_api.dart
@@ -39,7 +39,7 @@ extension $WebAssemblyExtension on $WebAssembly {
   external bool validate(BufferSource bytes);
   external JSPromise compile(BufferSource bytes);
   external JSPromise instantiate(
-    JSAny? bytesOrModuleObject, [
+    JSObject bytesOrModuleObject, [
     JSObject importObject,
   ]);
   external JSPromise compileStreaming(JSPromise source);
diff --git a/lib/src/dom/web_animations.dart b/lib/src/dom/web_animations.dart
index 7afa0c04..0a0398c1 100644
--- a/lib/src/dom/web_animations.dart
+++ b/lib/src/dom/web_animations.dart
@@ -214,7 +214,7 @@ extension ComputedEffectTimingExtension on ComputedEffectTiming {
 @staticInterop
 class KeyframeEffect implements AnimationEffect {
   external factory KeyframeEffect(
-    JSAny? sourceOrTarget, [
+    JSObject? sourceOrTarget, [
     JSObject? keyframes,
     JSAny? options,
   ]);
diff --git a/lib/src/dom/web_locks.dart b/lib/src/dom/web_locks.dart
index e2680f77..f3eb6606 100644
--- a/lib/src/dom/web_locks.dart
+++ b/lib/src/dom/web_locks.dart
@@ -24,7 +24,7 @@ class LockManager implements JSObject {}
 extension LockManagerExtension on LockManager {
   external JSPromise request(
     String name,
-    JSAny? callbackOrOptions, [
+    JSObject callbackOrOptions, [
     LockGrantedCallback callback,
   ]);
   external JSPromise query();
diff --git a/lib/src/dom/webaudio.dart b/lib/src/dom/webaudio.dart
index ed459a75..b292d48c 100644
--- a/lib/src/dom/webaudio.dart
+++ b/lib/src/dom/webaudio.dart
@@ -349,7 +349,7 @@ class AudioNode implements EventTarget {}
 
 extension AudioNodeExtension on AudioNode {
   external JSAny? connect(
-    JSAny? destinationNodeOrDestinationParam, [
+    JSObject destinationNodeOrDestinationParam, [
     int output,
     int input,
   ]);
diff --git a/lib/src/dom/webcodecs.dart b/lib/src/dom/webcodecs.dart
index f8c38a3c..a4c927eb 100644
--- a/lib/src/dom/webcodecs.dart
+++ b/lib/src/dom/webcodecs.dart
@@ -17,7 +17,7 @@ import 'webcodecs_opus_codec_registration.dart';
 import 'webcodecs_vp9_codec_registration.dart';
 import 'webidl.dart';
 
-typedef ImageBufferSource = JSAny?;
+typedef ImageBufferSource = JSObject;
 typedef AudioDataOutputCallback = JSFunction;
 typedef VideoFrameOutputCallback = JSFunction;
 typedef EncodedAudioChunkOutputCallback = JSFunction;
@@ -633,8 +633,8 @@ extension AudioDataCopyToOptionsExtension on AudioDataCopyToOptions {
 @staticInterop
 class VideoFrame implements JSObject {
   external factory VideoFrame(
-    JSAny? dataOrImage, [
-    JSAny? init,
+    JSObject dataOrImage, [
+    JSObject init,
   ]);
 }
 
diff --git a/lib/src/dom/webcryptoapi.dart b/lib/src/dom/webcryptoapi.dart
index 32707288..ab5f544c 100644
--- a/lib/src/dom/webcryptoapi.dart
+++ b/lib/src/dom/webcryptoapi.dart
@@ -108,7 +108,7 @@ extension SubtleCryptoExtension on SubtleCrypto {
   );
   external JSPromise importKey(
     KeyFormat format,
-    JSAny? keyData,
+    JSObject keyData,
     AlgorithmIdentifier algorithm,
     bool extractable,
     JSArray keyUsages,
diff --git a/lib/src/dom/webgl1.dart b/lib/src/dom/webgl1.dart
index d67618d2..7cd64607 100644
--- a/lib/src/dom/webgl1.dart
+++ b/lib/src/dom/webgl1.dart
@@ -22,9 +22,9 @@ typedef GLushort = int;
 typedef GLuint = int;
 typedef GLfloat = num;
 typedef GLclampf = num;
-typedef TexImageSource = JSAny?;
-typedef Float32List = JSAny?;
-typedef Int32List = JSAny?;
+typedef TexImageSource = JSObject;
+typedef Float32List = JSObject;
+typedef Int32List = JSObject;
 typedef WebGLPowerPreference = String;
 
 @JS()
@@ -805,7 +805,7 @@ extension WebGLRenderingContextBaseExtension on WebGLRenderingContextBase {
     GLsizei height,
   );
   external JSPromise makeXRCompatible();
-  external JSAny? get canvas;
+  external JSObject get canvas;
   external GLsizei get drawingBufferWidth;
   external GLsizei get drawingBufferHeight;
   external set drawingBufferColorSpace(PredefinedColorSpace value);
diff --git a/lib/src/dom/webgl2.dart b/lib/src/dom/webgl2.dart
index c31ee2e0..e5cb32a2 100644
--- a/lib/src/dom/webgl2.dart
+++ b/lib/src/dom/webgl2.dart
@@ -9,7 +9,7 @@ import 'webidl.dart';
 
 typedef GLint64 = int;
 typedef GLuint64 = int;
-typedef Uint32List = JSAny?;
+typedef Uint32List = JSObject;
 
 @JS('WebGLQuery')
 @staticInterop
@@ -750,7 +750,7 @@ extension WebGL2RenderingContextOverloadsExtension
   external JSVoid bufferSubData(
     GLenum target,
     GLintptr dstByteOffset,
-    JSAny? srcData, [
+    JSObject srcData, [
     int srcOffset,
     GLuint length,
   ]);
diff --git a/lib/src/dom/webgl_multi_draw.dart b/lib/src/dom/webgl_multi_draw.dart
index 62ea67ba..2e9ea48e 100644
--- a/lib/src/dom/webgl_multi_draw.dart
+++ b/lib/src/dom/webgl_multi_draw.dart
@@ -13,39 +13,39 @@ class WEBGL_multi_draw implements JSObject {}
 extension WEBGLMultiDrawExtension on WEBGL_multi_draw {
   external JSVoid multiDrawArraysWEBGL(
     GLenum mode,
-    JSAny? firstsList,
+    JSObject firstsList,
     int firstsOffset,
-    JSAny? countsList,
+    JSObject countsList,
     int countsOffset,
     GLsizei drawcount,
   );
   external JSVoid multiDrawElementsWEBGL(
     GLenum mode,
-    JSAny? countsList,
+    JSObject countsList,
     int countsOffset,
     GLenum type,
-    JSAny? offsetsList,
+    JSObject offsetsList,
     int offsetsOffset,
     GLsizei drawcount,
   );
   external JSVoid multiDrawArraysInstancedWEBGL(
     GLenum mode,
-    JSAny? firstsList,
+    JSObject firstsList,
     int firstsOffset,
-    JSAny? countsList,
+    JSObject countsList,
     int countsOffset,
-    JSAny? instanceCountsList,
+    JSObject instanceCountsList,
     int instanceCountsOffset,
     GLsizei drawcount,
   );
   external JSVoid multiDrawElementsInstancedWEBGL(
     GLenum mode,
-    JSAny? countsList,
+    JSObject countsList,
     int countsOffset,
     GLenum type,
-    JSAny? offsetsList,
+    JSObject offsetsList,
     int offsetsOffset,
-    JSAny? instanceCountsList,
+    JSObject instanceCountsList,
     int instanceCountsOffset,
     GLsizei drawcount,
   );
diff --git a/lib/src/dom/webgl_multi_draw_instanced_base_vertex_base_instance.dart b/lib/src/dom/webgl_multi_draw_instanced_base_vertex_base_instance.dart
index 2af16804..440dc8e6 100644
--- a/lib/src/dom/webgl_multi_draw_instanced_base_vertex_base_instance.dart
+++ b/lib/src/dom/webgl_multi_draw_instanced_base_vertex_base_instance.dart
@@ -15,28 +15,28 @@ extension WEBGLMultiDrawInstancedBaseVertexBaseInstanceExtension
     on WEBGL_multi_draw_instanced_base_vertex_base_instance {
   external JSVoid multiDrawArraysInstancedBaseInstanceWEBGL(
     GLenum mode,
-    JSAny? firstsList,
+    JSObject firstsList,
     int firstsOffset,
-    JSAny? countsList,
+    JSObject countsList,
     int countsOffset,
-    JSAny? instanceCountsList,
+    JSObject instanceCountsList,
     int instanceCountsOffset,
-    JSAny? baseInstancesList,
+    JSObject baseInstancesList,
     int baseInstancesOffset,
     GLsizei drawcount,
   );
   external JSVoid multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(
     GLenum mode,
-    JSAny? countsList,
+    JSObject countsList,
     int countsOffset,
     GLenum type,
-    JSAny? offsetsList,
+    JSObject offsetsList,
     int offsetsOffset,
-    JSAny? instanceCountsList,
+    JSObject instanceCountsList,
     int instanceCountsOffset,
-    JSAny? baseVerticesList,
+    JSObject baseVerticesList,
     int baseVerticesOffset,
-    JSAny? baseInstancesList,
+    JSObject baseInstancesList,
     int baseInstancesOffset,
     GLsizei drawcount,
   );
diff --git a/lib/src/dom/webgpu.dart b/lib/src/dom/webgpu.dart
index e6129c1e..5b4be46e 100644
--- a/lib/src/dom/webgpu.dart
+++ b/lib/src/dom/webgpu.dart
@@ -12,10 +12,10 @@ typedef GPUBufferUsageFlags = int;
 typedef GPUMapModeFlags = int;
 typedef GPUTextureUsageFlags = int;
 typedef GPUShaderStageFlags = int;
-typedef GPUBindingResource = JSAny?;
+typedef GPUBindingResource = JSObject;
 typedef GPUPipelineConstantValue = num;
 typedef GPUColorWriteFlags = int;
-typedef GPUImageCopyExternalImageSource = JSAny?;
+typedef GPUImageCopyExternalImageSource = JSObject;
 typedef GPUBufferDynamicOffset = int;
 typedef GPUStencilValue = int;
 typedef GPUSampleMask = int;
@@ -29,10 +29,10 @@ typedef GPUSize64Out = int;
 typedef GPUIntegerCoordinateOut = int;
 typedef GPUSize32Out = int;
 typedef GPUFlagsConstant = int;
-typedef GPUColor = JSAny?;
-typedef GPUOrigin2D = JSAny?;
-typedef GPUOrigin3D = JSAny?;
-typedef GPUExtent3D = JSAny?;
+typedef GPUColor = JSObject;
+typedef GPUOrigin2D = JSObject;
+typedef GPUOrigin3D = JSObject;
+typedef GPUExtent3D = JSObject;
 typedef GPUPowerPreference = String;
 typedef GPUFeatureName = String;
 typedef GPUBufferMapState = String;
@@ -434,15 +434,15 @@ class GPUExternalTexture implements GPUObjectBase {}
 @anonymous
 class GPUExternalTextureDescriptor implements GPUObjectDescriptorBase {
   external factory GPUExternalTextureDescriptor({
-    required JSAny? source,
+    required JSObject source,
     PredefinedColorSpace colorSpace,
   });
 }
 
 extension GPUExternalTextureDescriptorExtension
     on GPUExternalTextureDescriptor {
-  external set source(JSAny? value);
-  external JSAny? get source;
+  external set source(JSObject value);
+  external JSObject get source;
   external set colorSpace(PredefinedColorSpace value);
   external PredefinedColorSpace get colorSpace;
 }
@@ -1300,7 +1300,7 @@ extension GPUBindingCommandsMixinExtension on GPUBindingCommandsMixin {
   external JSVoid setBindGroup(
     GPUIndex32 index,
     GPUBindGroup? bindGroup, [
-    JSAny? dynamicOffsetsOrDynamicOffsetsData,
+    JSObject dynamicOffsetsOrDynamicOffsetsData,
     GPUSize64 dynamicOffsetsDataStart,
     GPUSize32 dynamicOffsetsDataLength,
   ]);
@@ -1693,7 +1693,7 @@ extension GPUCanvasContextExtension on GPUCanvasContext {
   external JSVoid configure(GPUCanvasConfiguration configuration);
   external JSVoid unconfigure();
   external GPUTexture getCurrentTexture();
-  external JSAny? get canvas;
+  external JSObject get canvas;
 }
 
 @JS()
diff --git a/lib/src/dom/webidl.dart b/lib/src/dom/webidl.dart
index 7927d145..3060b498 100644
--- a/lib/src/dom/webidl.dart
+++ b/lib/src/dom/webidl.dart
@@ -4,9 +4,9 @@
 
 import 'dart:js_interop';
 
-typedef ArrayBufferView = JSAny?;
-typedef BufferSource = JSAny?;
-typedef AllowSharedBufferSource = JSAny?;
+typedef ArrayBufferView = JSObject;
+typedef BufferSource = JSObject;
+typedef AllowSharedBufferSource = JSObject;
 typedef VoidFunction = JSFunction;
 
 @JS('DOMException')
diff --git a/lib/src/dom/webnn.dart b/lib/src/dom/webnn.dart
index 6651a161..303b0911 100644
--- a/lib/src/dom/webnn.dart
+++ b/lib/src/dom/webnn.dart
@@ -7,10 +7,10 @@ import 'dart:js_interop';
 import 'webgpu.dart';
 
 typedef MLNamedArrayBufferViews = JSAny?;
-typedef MLGPUResource = JSAny?;
+typedef MLGPUResource = JSObject;
 typedef MLNamedGPUResources = JSAny?;
 typedef MLNamedOperands = JSAny?;
-typedef MLBufferView = JSAny?;
+typedef MLBufferView = JSObject;
 typedef MLDeviceType = String;
 typedef MLPowerPreference = String;
 typedef MLInputOperandLayout = String;
@@ -55,8 +55,8 @@ extension MLContextOptionsExtension on MLContextOptions {
 class ML implements JSObject {}
 
 extension MLExtension on ML {
-  external JSPromise createContext([JSAny? gpuDeviceOrOptions]);
-  external MLContext createContextSync([JSAny? gpuDeviceOrOptions]);
+  external JSPromise createContext([JSObject gpuDeviceOrOptions]);
+  external MLContext createContextSync([JSObject gpuDeviceOrOptions]);
 }
 
 @JS('MLGraph')
@@ -180,8 +180,8 @@ extension MLGraphBuilderExtension on MLGraphBuilder {
     MLOperand variance, [
     MLBatchNormalizationOptions options,
   ]);
-  external JSAny? clamp([
-    JSAny? operandOrOptions,
+  external JSObject clamp([
+    JSObject operandOrOptions,
     MLClampOptions options,
   ]);
   external MLOperand concat(
@@ -235,8 +235,8 @@ extension MLGraphBuilderExtension on MLGraphBuilder {
   external MLOperand neg(MLOperand input);
   external MLOperand sin(MLOperand input);
   external MLOperand tan(MLOperand input);
-  external JSAny? elu([
-    JSAny? inputOrOptions,
+  external JSObject elu([
+    JSObject inputOrOptions,
     MLEluOptions options,
   ]);
   external MLOperand gemm(
@@ -260,21 +260,21 @@ extension MLGraphBuilderExtension on MLGraphBuilder {
     int hiddenSize, [
     MLGruCellOptions options,
   ]);
-  external JSAny? hardSigmoid([
-    JSAny? inputOrOptions,
+  external JSObject hardSigmoid([
+    JSObject inputOrOptions,
     MLHardSigmoidOptions options,
   ]);
-  external JSAny? hardSwish([MLOperand input]);
+  external JSObject hardSwish([MLOperand input]);
   external MLOperand instanceNormalization(
     MLOperand input, [
     MLInstanceNormalizationOptions options,
   ]);
-  external JSAny? leakyRelu([
-    JSAny? inputOrOptions,
+  external JSObject leakyRelu([
+    JSObject inputOrOptions,
     MLLeakyReluOptions options,
   ]);
-  external JSAny? linear([
-    JSAny? inputOrOptions,
+  external JSObject linear([
+    JSObject inputOrOptions,
     MLLinearOptions options,
   ]);
   external JSArray lstm(
@@ -360,7 +360,7 @@ extension MLGraphBuilderExtension on MLGraphBuilder {
     MLOperand input, [
     MLReduceOptions options,
   ]);
-  external JSAny? relu([MLOperand input]);
+  external JSObject relu([MLOperand input]);
   external MLOperand resample2d(
     MLOperand input, [
     MLResample2dOptions options,
@@ -369,18 +369,18 @@ extension MLGraphBuilderExtension on MLGraphBuilder {
     MLOperand input,
     JSArray newShape,
   );
-  external JSAny? sigmoid([MLOperand input]);
+  external JSObject sigmoid([MLOperand input]);
   external MLOperand slice(
     MLOperand input,
     JSArray starts,
     JSArray sizes,
   );
-  external JSAny? softmax([MLOperand input]);
-  external JSAny? softplus([
-    JSAny? inputOrOptions,
+  external JSObject softmax([MLOperand input]);
+  external JSObject softplus([
+    JSObject inputOrOptions,
     MLSoftplusOptions options,
   ]);
-  external JSAny? softsign([MLOperand input]);
+  external JSObject softsign([MLOperand input]);
   external JSArray split(
     MLOperand input,
     JSAny? splits, [
@@ -390,7 +390,7 @@ extension MLGraphBuilderExtension on MLGraphBuilder {
     MLOperand input, [
     MLSqueezeOptions options,
   ]);
-  external JSAny? tanh([MLOperand input]);
+  external JSObject tanh([MLOperand input]);
   external MLOperand transpose(
     MLOperand input, [
     MLTransposeOptions options,
diff --git a/lib/src/dom/webrtc.dart b/lib/src/dom/webrtc.dart
index 20e65d0b..0180c9e4 100644
--- a/lib/src/dom/webrtc.dart
+++ b/lib/src/dom/webrtc.dart
@@ -144,12 +144,12 @@ extension RTCPeerConnectionExtension on RTCPeerConnection {
   ]);
   external JSPromise getIdentityAssertion();
   external JSPromise createOffer([
-    JSAny? optionsOrSuccessCallback,
+    JSObject optionsOrSuccessCallback,
     RTCPeerConnectionErrorCallback failureCallback,
     RTCOfferOptions options,
   ]);
   external JSPromise createAnswer([
-    JSAny? optionsOrSuccessCallback,
+    JSObject optionsOrSuccessCallback,
     RTCPeerConnectionErrorCallback failureCallback,
   ]);
   external JSPromise setLocalDescription([
diff --git a/lib/src/dom/webrtc_encoded_transform.dart b/lib/src/dom/webrtc_encoded_transform.dart
index 2feb9ebe..f08e8e61 100644
--- a/lib/src/dom/webrtc_encoded_transform.dart
+++ b/lib/src/dom/webrtc_encoded_transform.dart
@@ -9,7 +9,7 @@ import 'html.dart';
 import 'streams.dart';
 import 'webcryptoapi.dart';
 
-typedef RTCRtpTransform = JSAny?;
+typedef RTCRtpTransform = JSObject;
 typedef SmallCryptoKeyID = int;
 typedef CryptoKeyID = JSAny?;
 typedef SFrameTransformRole = String;
diff --git a/lib/src/dom/webxr.dart b/lib/src/dom/webxr.dart
index 08924dc4..59982e8b 100644
--- a/lib/src/dom/webxr.dart
+++ b/lib/src/dom/webxr.dart
@@ -21,7 +21,7 @@ import 'webxr_hand_input.dart';
 import 'webxr_hit_test.dart';
 import 'webxr_lighting_estimation.dart';
 
-typedef XRWebGLRenderingContext = JSAny?;
+typedef XRWebGLRenderingContext = JSObject;
 typedef XRFrameRequestCallback = JSFunction;
 typedef XRSessionMode = String;
 typedef XRVisibilityState = String;
diff --git a/lib/src/dom/webxr_hit_test.dart b/lib/src/dom/webxr_hit_test.dart
index 672c1835..f014f880 100644
--- a/lib/src/dom/webxr_hit_test.dart
+++ b/lib/src/dom/webxr_hit_test.dart
@@ -113,7 +113,7 @@ extension XRRayDirectionInitExtension on XRRayDirectionInit {
 @staticInterop
 class XRRay implements JSObject {
   external factory XRRay([
-    JSAny? originOrTransform,
+    JSObject originOrTransform,
     XRRayDirectionInit direction,
   ]);
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index d6f65f87..ddaeb680 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
 name: web
 description: >-
   Lightweight DOM and JS bindings built around JS static interop.
-version: 0.3.0
+version: 0.3.1-dev
 
 repository: https://github.com/dart-lang/web
 
@@ -9,6 +9,7 @@ environment:
   sdk: ">=3.2.0-194.0.dev <4.0.0"
 
 dev_dependencies:
+  analyzer: ^6.2.0
   args: ^2.4.0
   code_builder: ^4.4.0
   dart_flutter_team_lints: ^2.0.0
diff --git a/tool/bindings_generator/generate_bindings.dart b/tool/bindings_generator/generate_bindings.dart
index 498c91a2..6efea3b2 100644
--- a/tool/bindings_generator/generate_bindings.dart
+++ b/tool/bindings_generator/generate_bindings.dart
@@ -55,5 +55,6 @@ Future<TranslationResult> generateBindings(
     final ast = entry[1] as JSArray;
     translator.collect(shortname, ast);
   }
+  translator.setOrUpdateInterfacelikes();
   return translator.translate();
 }
diff --git a/tool/bindings_generator/js_type_hierarchy.dart b/tool/bindings_generator/js_type_hierarchy.dart
new file mode 100644
index 00000000..0fb095f7
--- /dev/null
+++ b/tool/bindings_generator/js_type_hierarchy.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Updated by /tool/update_bindings.dart. Do not modify by hand.
+
+const Map<String, Set<String>> jsTypeHierarchy = {
+  'JSAny': {},
+  'JSObject': {'JSAny'},
+  'JSFunction': {'JSObject'},
+  'JSExportedDartFunction': {'JSFunction'},
+  'JSPromise': {'JSObject'},
+  'JSArray': {'JSObject'},
+  'JSBoxedDartObject': {'JSObject'},
+  'JSArrayBuffer': {'JSObject'},
+  'JSDataView': {'JSObject'},
+  'JSTypedArray': {'JSObject'},
+  'JSInt8Array': {'JSTypedArray'},
+  'JSUint8Array': {'JSTypedArray'},
+  'JSUint8ClampedArray': {'JSTypedArray'},
+  'JSInt16Array': {'JSTypedArray'},
+  'JSUint16Array': {'JSTypedArray'},
+  'JSInt32Array': {'JSTypedArray'},
+  'JSUint32Array': {'JSTypedArray'},
+  'JSFloat32Array': {'JSTypedArray'},
+  'JSFloat64Array': {'JSTypedArray'},
+  'JSNumber': {'JSAny'},
+  'JSBoolean': {'JSAny'},
+  'JSString': {'JSAny'},
+  'JSSymbol': {'JSAny'},
+  'JSBigInt': {'JSAny'}
+};
diff --git a/tool/bindings_generator/translator.dart b/tool/bindings_generator/translator.dart
index f880ec4b..cce30c05 100644
--- a/tool/bindings_generator/translator.dart
+++ b/tool/bindings_generator/translator.dart
@@ -8,6 +8,7 @@ import 'package:code_builder/code_builder.dart' as code;
 import 'package:path/path.dart' as p;
 
 import 'banned_names.dart';
+import 'js_type_hierarchy.dart';
 import 'singletons.dart';
 import 'type_aliases.dart';
 import 'util.dart';
@@ -19,6 +20,7 @@ class _Library {
   final Translator translator;
   final String url;
   final List<idl.Interfacelike> interfacelikes = [];
+  final List<idl.Interfacelike> partialInterfacelikes = [];
   final List<idl.Typedef> typedefs = [];
   final List<idl.Enum> enums = [];
   final List<idl.Callback> callbacks = [];
@@ -31,11 +33,14 @@ class _Library {
     final name = named.name.toDart;
     assert(!translator._typeToLibrary.containsKey(name));
     translator._typeToLibrary[named.name.toDart] = this;
+    translator._typeToDeclaration[named.name.toDart] = node;
     list.add(named);
   }
 
   void add(idl.Node node) {
     final type = node.type.toDart;
+    // TODO(srujzs): We may want an enum here, but that would be slower due to
+    // a string lookup in the set of enums.
     switch (type) {
       case 'interface mixin':
       case 'interface':
@@ -48,8 +53,9 @@ class _Library {
         final interfacelike = node as idl.Interfacelike;
         if (!node.partial.toDart) {
           _addNamed<idl.Interfacelike>(node, interfacelikes);
+        } else {
+          partialInterfacelikes.add(interfacelike);
         }
-        translator.setOrUpdateInterfacelike(interfacelike);
         break;
       case 'typedef':
         _addNamed<idl.Typedef>(node, typedefs);
@@ -80,19 +86,112 @@ class _Library {
   }
 }
 
-String _typeRaw(idl.IDLType idlType) {
-  final String type;
+(String, bool) _computeJsTypeUnion(
+    (String, bool) rawType1, (String, bool) rawType2) {
+  if (rawType1.$1 == rawType2.$1) {
+    return (rawType1.$1, rawType1.$2 || rawType2.$2);
+  }
+  // In the case of unions, we should try and get a JS type-able type to get a
+  // better LUB.
+  (String, bool) getTypeForUnionCalculation((String, bool) rawType) {
+    var (type, nullable) = rawType;
+    final decl = Translator.instance!._typeToDeclaration[type];
+    if (decl != null) {
+      final nodeType = decl.type.toDart;
+      switch (nodeType) {
+        case 'interface':
+        case 'dictionary':
+          // TODO(srujzs): We can do better if we do a LUB of the interfaces (so
+          // we get a possible interface name instead of `JSObject`), but that
+          // might be too much effort for too little reward. This would entail
+          // caching the hierarchy of IDL types.
+          type = 'JSObject';
+          break;
+        case 'typedef':
+          final desugared = _typeRaw((decl as idl.Typedef).idlType);
+          type = desugared.$1;
+          nullable = desugared.$2;
+          break;
+        case 'callback':
+          type = 'JSFunction';
+          break;
+        case 'enum':
+          type = 'JSString';
+          break;
+        default:
+          throw Exception('Unhandled type $type with node type: $nodeType');
+      }
+    }
+    return (type, nullable);
+  }
+
+  var (type1, nullable1) = getTypeForUnionCalculation(rawType1);
+  var (type2, nullable2) = getTypeForUnionCalculation(rawType2);
+  final nullable = nullable1 || nullable2;
+  if (!jsTypeHierarchy.containsKey(type1) ||
+      !jsTypeHierarchy.containsKey(type2)) {
+    return ('JSAny', nullable);
+  }
+  // Compute path from root and find the last type that exists in both paths for
+  // a least upper bound.
+  final t1Supertypes = [type1];
+  final t2Supertypes = [type2];
+  while (jsTypeHierarchy[type1]!.isNotEmpty) {
+    type1 = jsTypeHierarchy[type1]!.single;
+    t1Supertypes.add(type1);
+  }
+  while (jsTypeHierarchy[type2]!.isNotEmpty) {
+    type2 = jsTypeHierarchy[type2]!.single;
+    t2Supertypes.add(type2);
+  }
+  var t1i = t1Supertypes.length - 1;
+  var t2i = t2Supertypes.length - 1;
+  while (t1i >= 0 && t2i >= 0) {
+    if (t1Supertypes[t1i] != t2Supertypes[t2i]) break;
+    t1i--;
+    t2i--;
+  }
+  return (t1Supertypes[t1i + 1], nullable);
+}
+
+/// Returns a record containing the Dart type for the given [idlType] and a bool
+/// indicating whether the type is nullable.
+(String, bool) _typeRaw(idl.IDLType idlType) {
+  // For union types, we take the possible union of all the types using a LUB.
   if (idlType.union.toDart) {
-    type = 'union';
-  } else if (idlType.generic.toDart.isNotEmpty) {
+    final types = (idlType.idlType as JSArray).toDart;
+    String? unionType;
+    var nullable = idlType.nullable.toDart;
+    for (final type in types) {
+      final rawType = _typeRaw(type as idl.IDLType);
+      if (unionType == null) {
+        unionType = rawType.$1;
+        nullable |= rawType.$2;
+      } else {
+        final union = _computeJsTypeUnion((unionType, nullable), rawType);
+        unionType = union.$1;
+        nullable = union.$2;
+      }
+    }
+    return (unionType!, nullable);
+  }
+  final String type;
+  final nullable = idlType.nullable.toDart;
+  if (idlType.generic.toDart.isNotEmpty) {
+    // TODO(srujzs): Once we have a generic `JSArray` and `JSPromise`, we should
+    // add these type parameters in. We need to be careful, however, as we
+    // should only add the type parameter if the type is a subtype of `JSAny?`
+    // either because it is an interface or a typedef. We also need to make sure
+    // to convert type aliases that are Dart types back to JS types e.g.
+    // `String` should be `JSString`.
     type = idlType.generic.toDart;
   } else {
     type = (idlType.idlType as JSString).toDart;
   }
   if (typeAliases.containsKey(type)) {
-    return typeAliases[type]!;
+    return (typeAliases[type]!, nullable);
   } else {
-    return type;
+    return (type, nullable);
   }
 }
 
@@ -102,19 +201,15 @@ class _Type {
 
   _Type._(this.type, this.isNullable);
 
-  factory _Type(idl.IDLType type) =>
-      _Type._(_typeRaw(type), type.nullable.toDart);
+  factory _Type(idl.IDLType type) {
+    final (rawType, nullable) = _typeRaw(type);
+    return _Type._(rawType, nullable);
+  }
 
   void update(idl.IDLType idlType) {
-    final thatType = _typeRaw(idlType);
-    if (type != thatType) {
-      // TODO(joshualitt): In some cases we could probably find a better upper
-      // bound.
-      type = 'JSAny';
-    }
-    if (idlType.nullable.toDart) {
-      isNullable = true;
-    }
+    final union = _computeJsTypeUnion((type, isNullable), _typeRaw(idlType));
+    type = union.$1;
+    isNullable = union.$2;
   }
 }
 
@@ -318,6 +413,7 @@ class _MemberName {
 
 class Translator {
   final _libraries = <String, _Library>{};
+  final _typeToDeclaration = <String, idl.Node>{};
   final _typeToLibrary = <String, _Library>{};
   final _interfacelikes = <String, _PartialInterfacelike>{};
   final _includes = <idl.Includes>[];
@@ -325,14 +421,32 @@ class Translator {
   late String _currentlyTranslatingUrl;
   final List<String> _cssStyleDeclarations;
 
-  Translator(this._librarySubDir, this._cssStyleDeclarations);
+  /// Singleton so that various helper methods can access info about the AST.
+  static Translator? instance;
 
-  void setOrUpdateInterfacelike(idl.Interfacelike interfacelike) {
-    final name = interfacelike.name.toDart;
-    if (_interfacelikes.containsKey(name)) {
-      _interfacelikes[name]!.update(interfacelike);
-    } else {
-      _interfacelikes[name] = _PartialInterfacelike(interfacelike);
+  Translator(this._librarySubDir, this._cssStyleDeclarations) {
+    instance = this;
+  }
+
+  /// Set or update partial interfaces so we can have a unified interface
+  /// representation.
+  ///
+  /// Note that this is done after the initial pass on the AST. This is because
+  /// this step resolves unions and therefore can't be done until we record all
+  /// types.
+  void setOrUpdateInterfacelikes() {
+    for (final library in _libraries.values) {
+      for (final interfacelike in [
+        ...library.interfacelikes,
+        ...library.partialInterfacelikes
+      ]) {
+        final name = interfacelike.name.toDart;
+        if (_interfacelikes.containsKey(name)) {
+          _interfacelikes[name]!.update(interfacelike);
+        } else {
+          _interfacelikes[name] = _PartialInterfacelike(interfacelike);
+        }
+      }
     }
   }
 
@@ -346,10 +460,10 @@ class Translator {
     }
   }
 
-  code.TypeDef _typedef(String name, String type, bool nullable) =>
+  code.TypeDef _typedef(String name, (String, bool) rawType) =>
       code.TypeDef((b) => b
         ..name = name
-        ..definition = _typeReference(type, isNullable: nullable));
+        ..definition = _typeReference(rawType.$1, isNullable: rawType.$2));
 
   code.Method _topLevelGetter(String dartName, String getterName) =>
       code.Method((b) => b
@@ -399,9 +513,10 @@ class Translator {
   }
 
   code.TypeReference _idlTypeToTypeReference(idl.IDLType idlType,
-          {required bool isReturn}) =>
-      _typeReference(_typeRaw(idlType),
-          isNullable: idlType.nullable.toDart, isReturn: isReturn);
+      {required bool isReturn}) {
+    final type = _typeRaw(idlType);
+    return _typeReference(type.$1, isNullable: type.$2, isReturn: isReturn);
+  }
 
   code.TypeReference _typeToTypeReference(_Type type,
           {required bool isReturn}) =>
@@ -675,19 +790,18 @@ class Translator {
     ..comments.addAll(licenseHeader)
     ..body.addAll([
       for (final typedef in library.typedefs)
-        _typedef(typedef.name.toDart, _typeRaw(typedef.idlType),
-            typedef.idlType.nullable.toDart),
+        _typedef(typedef.name.toDart, _typeRaw(typedef.idlType)),
       // TODO(joshualitt): We should lower callbacks and callback interfaces to
       // a Dart function that takes a typed Dart function, and returns an
       // JSFunction.
       for (final callback in library.callbacks)
-        _typedef(callback.name.toDart, 'JSFunction', false),
+        _typedef(callback.name.toDart, ('JSFunction', false)),
       for (final callbackInterface in library.callbackInterfaces)
-        _typedef(callbackInterface.name.toDart, 'JSFunction', false),
+        _typedef(callbackInterface.name.toDart, ('JSFunction', false)),
       // TODO(joshualitt): Enums in the WebIDL are just strings, but we could
       // make them easier to work with on the Dart side.
       for (final enum_ in library.enums)
-        _typedef(enum_.name.toDart, 'String', false),
+        _typedef(enum_.name.toDart, ('String', false)),
       for (final interfacelike in library.interfacelikes)
         ..._interfacelike(interfacelike),
     ]));
diff --git a/tool/bindings_generator/type_aliases.dart b/tool/bindings_generator/type_aliases.dart
index b70ade87..cc187f04 100644
--- a/tool/bindings_generator/type_aliases.dart
+++ b/tool/bindings_generator/type_aliases.dart
@@ -4,7 +4,7 @@
 
 const typeAliases = <String, String>{
   'any': 'JSAny',
-  'union': 'JSAny',
+  'bigint': 'JSBigInt',
   'record': 'JSAny',
   'object': 'JSObject',
   'Promise': 'JSPromise',
@@ -12,6 +12,7 @@ const typeAliases = <String, String>{
   'undefined': 'JSUndefined',
   'Function': 'JSFunction',
   'WindowProxy': 'Window',
+  'SharedArrayBuffer': 'JSObject',
 
   'ArrayBuffer': 'JSArrayBuffer',
   'DataView': 'JSDataView',
@@ -24,12 +25,18 @@ const typeAliases = <String, String>{
   'Uint8ClampedArray': 'JSUint8ClampedArray',
   'Float32Array': 'JSFloat32Array',
   'Float64Array': 'JSFloat64Array',
+  // TODO(srujzs): Change these aliases if we add these two as JS types.
+  'BigInt64Array': 'JSTypedArray',
+  'BigUint64Array': 'JSTypedArray',
 
   // Array aliases.
   'sequence': 'JSArray',
   'FrozenArray': 'JSArray',
   'ObservableArray': 'JSArray',
 
+  // TODO(srujzs): We should ideally use JS types everywhere, and only change
+  // to Dart types when we are translating. However, we need to figure out what
+  // to do for `int` vs `num` as they both map to `JSNumber`.
   // Number aliases.
   'byte': 'int',
   'octet': 'int',
diff --git a/tool/update_bindings.dart b/tool/update_bindings.dart
index 05652a26..740b4ac2 100644
--- a/tool/update_bindings.dart
+++ b/tool/update_bindings.dart
@@ -1,6 +1,14 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 import 'package:args/args.dart';
 import 'package:io/ansi.dart' as ansi;
 import 'package:io/io.dart';
@@ -34,6 +42,9 @@ $_usage''');
     await _runProc('npm', ['install'], _bindingsGeneratorPath);
   }
 
+  // Compute JS type hierarchy for union calculation in translator.
+  await _generateJsTypeHierarchy();
+
   if (argResult['compile'] as bool) {
     // Compile Dart to Javascript.
     await _runProc(
@@ -131,6 +142,68 @@ Future<void> _runProc(
   }
 }
 
+Future<void> _generateJsTypeHierarchy() async {
+  // Use a file that uses `dart:js_interop` for analysis.
+  final contextCollection = AnalysisContextCollection(
+      includedPaths: [p.fromUri(Platform.script.resolve('../lib/web.dart'))]);
+  final dartJsInterop = await contextCollection.contexts.single.currentSession
+      .getLibraryByUri('dart:js_interop') as LibraryElementResult;
+  final definedNames = dartJsInterop.element.exportNamespace.definedNames;
+  final jsTypeHierarchy = <String, Set<String>>{};
+  for (final name in definedNames.keys) {
+    final element = definedNames[name];
+    if (element is TypeDefiningElement) {
+      void storeSupertypes(InterfaceElement element) {
+        bool isInJsTypes(InterfaceElement element) =>
+            // We only care about JS types for this calculation.
+            // TODO(srujzs): We'll likely need to change this once JS types move
+            // to extension types.
+            element.library.isInSdk && element.library.name == '_js_types';
+
+        if (!isInJsTypes(element)) return;
+        final supertypes = <String>{};
+        final immediateSupertypes = <InterfaceType>[
+          if (element.supertype != null) element.supertype!,
+          ...element.interfaces,
+        ];
+        for (final supertype in immediateSupertypes) {
+          if (isInJsTypes(supertype.element)) {
+            supertypes.add("'${supertype.element.name}'");
+          }
+        }
+        // Assert we have a tree hierarchy.
+        assert(supertypes.length == 1);
+        jsTypeHierarchy["'$name'"] = supertypes;
+      }
+
+      if (element is TypeAliasElement) {
+        final type = element.aliasedType;
+        if (type is InterfaceType) storeSupertypes(type.element);
+      } else if (element is InterfaceElement) {
+        storeSupertypes(element);
+      }
+    }
+  }
+
+  final jsTypeHierarchyScript = '''
+  // Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+  // for details. All rights reserved. Use of this source code is governed by a
+  // BSD-style license that can be found in the LICENSE file.
+
+  // Updated by $_thisScript. Do not modify by hand.
+
+  const Map<String, Set<String>> jsTypeHierarchy = $jsTypeHierarchy;
+  ''';
+  final jsTypeHierarchyPath =
+      p.join(_bindingsGeneratorPath, 'js_type_hierarchy.dart');
+  await File(jsTypeHierarchyPath).writeAsString(jsTypeHierarchyScript);
+  await _runProc(
+    Platform.executable,
+    ['format', jsTypeHierarchyPath],
+    _bindingsGeneratorPath,
+  );
+}
+
 final _usage = '''
 Usage:
 ${_parser.usage}''';