Skip to content

Commit 19229a9

Browse files
authored
Add (Offscreen)RenderingContext protocols (#45)
Previously, `RenderingContext` and `OffscreenRenderingContext` were enums, which required the (only) module containing these enum declarations to know all possible context types upfront. When these types are declared as protocols, separate modules can add new types and add conformances to this protocol on case-by-case basis. In a future PR this allows us to split Canvas, WebGL1, WebGL2, and WebGPU APIs into separate modules. I've also made `getContext` calls type safe, where users now pass the type of desired context instead of their IDs.
1 parent 8328d41 commit 19229a9

File tree

4 files changed

+80
-200
lines changed

4 files changed

+80
-200
lines changed

Sources/WebAPIKit/Generated.swift

+2-198
Original file line numberDiff line numberDiff line change
@@ -11886,10 +11886,7 @@ public class HTMLCanvasElement: HTMLElement {
1188611886
@ReadWriteAttribute
1188711887
public var height: UInt32
1188811888

11889-
@inlinable public func getContext(contextId: String, options: JSValue? = nil) -> RenderingContext? {
11890-
let this = jsObject
11891-
return this[Strings.getContext].function!(this: this, arguments: [contextId.jsValue, options?.jsValue ?? .undefined]).fromJSValue()!
11892-
}
11889+
// XXX: member 'getContext' is ignored
1189311890

1189411891
@inlinable public func toDataURL(type: String? = nil, quality: JSValue? = nil) -> String {
1189511892
let this = jsObject
@@ -18099,10 +18096,7 @@ public class OffscreenCanvas: EventTarget {
1809918096
@ReadWriteAttribute
1810018097
public var height: UInt64
1810118098

18102-
@inlinable public func getContext(contextId: OffscreenRenderingContextId, options: JSValue? = nil) -> OffscreenRenderingContext? {
18103-
let this = jsObject
18104-
return this[Strings.getContext].function!(this: this, arguments: [contextId.jsValue, options?.jsValue ?? .undefined]).fromJSValue()!
18105-
}
18099+
// XXX: member 'getContext' is ignored
1810618100

1810718101
@inlinable public func transferToImageBitmap() -> ImageBitmap {
1810818102
let this = jsObject
@@ -18147,27 +18141,6 @@ public class OffscreenCanvasRenderingContext2D: JSBridgedClass, CanvasState, Can
1814718141
public var canvas: OffscreenCanvas
1814818142
}
1814918143

18150-
public enum OffscreenRenderingContextId: JSString, JSValueCompatible {
18151-
case _2d = "2d"
18152-
case bitmaprenderer = "bitmaprenderer"
18153-
case webgl = "webgl"
18154-
case webgl2 = "webgl2"
18155-
case webgpu = "webgpu"
18156-
18157-
@inlinable public static func construct(from jsValue: JSValue) -> Self? {
18158-
if let string = jsValue.jsString {
18159-
return Self(rawValue: string)
18160-
}
18161-
return nil
18162-
}
18163-
18164-
@inlinable public init?(string: String) {
18165-
self.init(rawValue: JSString(string))
18166-
}
18167-
18168-
@inlinable public var jsValue: JSValue { rawValue.jsValue }
18169-
}
18170-
1817118144
public class OptionalEffectTiming: BridgedDictionary {
1817218145
public convenience init(delay: Double, endDelay: Double, fill: FillMode, iterationStart: Double, iterations: Double, duration: Double_or_String, direction: PlaybackDirection, easing: String) {
1817318146
let object = JSObject.global[Strings.Object].function!.new()
@@ -28619,7 +28592,6 @@ public enum console {
2861928592
@usableFromInline static let getClientRects: JSString = "getClientRects"
2862028593
@usableFromInline static let getComputedTiming: JSString = "getComputedTiming"
2862128594
@usableFromInline static let getConstraints: JSString = "getConstraints"
28622-
@usableFromInline static let getContext: JSString = "getContext"
2862328595
@usableFromInline static let getContextAttributes: JSString = "getContextAttributes"
2862428596
@usableFromInline static let getCueById: JSString = "getCueById"
2862528597
@usableFromInline static let getCurrentTexture: JSString = "getCurrentTexture"
@@ -32066,90 +32038,6 @@ public enum Node_or_String: JSValueCompatible, Any_Node_or_String {
3206632038
}
3206732039
}
3206832040

32069-
public protocol Any_OffscreenRenderingContext: ConvertibleToJSValue {}
32070-
extension GPUCanvasContext: Any_OffscreenRenderingContext {}
32071-
extension ImageBitmapRenderingContext: Any_OffscreenRenderingContext {}
32072-
extension OffscreenCanvasRenderingContext2D: Any_OffscreenRenderingContext {}
32073-
extension WebGL2RenderingContext: Any_OffscreenRenderingContext {}
32074-
extension WebGLRenderingContext: Any_OffscreenRenderingContext {}
32075-
32076-
public enum OffscreenRenderingContext: JSValueCompatible, Any_OffscreenRenderingContext {
32077-
case gpuCanvasContext(GPUCanvasContext)
32078-
case imageBitmapRenderingContext(ImageBitmapRenderingContext)
32079-
case offscreenCanvasRenderingContext2D(OffscreenCanvasRenderingContext2D)
32080-
case webGL2RenderingContext(WebGL2RenderingContext)
32081-
case webGLRenderingContext(WebGLRenderingContext)
32082-
32083-
public var gpuCanvasContext: GPUCanvasContext? {
32084-
switch self {
32085-
case let .gpuCanvasContext(gpuCanvasContext): return gpuCanvasContext
32086-
default: return nil
32087-
}
32088-
}
32089-
32090-
public var imageBitmapRenderingContext: ImageBitmapRenderingContext? {
32091-
switch self {
32092-
case let .imageBitmapRenderingContext(imageBitmapRenderingContext): return imageBitmapRenderingContext
32093-
default: return nil
32094-
}
32095-
}
32096-
32097-
public var offscreenCanvasRenderingContext2D: OffscreenCanvasRenderingContext2D? {
32098-
switch self {
32099-
case let .offscreenCanvasRenderingContext2D(offscreenCanvasRenderingContext2D): return offscreenCanvasRenderingContext2D
32100-
default: return nil
32101-
}
32102-
}
32103-
32104-
public var webGL2RenderingContext: WebGL2RenderingContext? {
32105-
switch self {
32106-
case let .webGL2RenderingContext(webGL2RenderingContext): return webGL2RenderingContext
32107-
default: return nil
32108-
}
32109-
}
32110-
32111-
public var webGLRenderingContext: WebGLRenderingContext? {
32112-
switch self {
32113-
case let .webGLRenderingContext(webGLRenderingContext): return webGLRenderingContext
32114-
default: return nil
32115-
}
32116-
}
32117-
32118-
public static func construct(from value: JSValue) -> Self? {
32119-
if let gpuCanvasContext: GPUCanvasContext = value.fromJSValue() {
32120-
return .gpuCanvasContext(gpuCanvasContext)
32121-
}
32122-
if let imageBitmapRenderingContext: ImageBitmapRenderingContext = value.fromJSValue() {
32123-
return .imageBitmapRenderingContext(imageBitmapRenderingContext)
32124-
}
32125-
if let offscreenCanvasRenderingContext2D: OffscreenCanvasRenderingContext2D = value.fromJSValue() {
32126-
return .offscreenCanvasRenderingContext2D(offscreenCanvasRenderingContext2D)
32127-
}
32128-
if let webGL2RenderingContext: WebGL2RenderingContext = value.fromJSValue() {
32129-
return .webGL2RenderingContext(webGL2RenderingContext)
32130-
}
32131-
if let webGLRenderingContext: WebGLRenderingContext = value.fromJSValue() {
32132-
return .webGLRenderingContext(webGLRenderingContext)
32133-
}
32134-
return nil
32135-
}
32136-
32137-
public var jsValue: JSValue {
32138-
switch self {
32139-
case let .gpuCanvasContext(gpuCanvasContext):
32140-
return gpuCanvasContext.jsValue
32141-
case let .imageBitmapRenderingContext(imageBitmapRenderingContext):
32142-
return imageBitmapRenderingContext.jsValue
32143-
case let .offscreenCanvasRenderingContext2D(offscreenCanvasRenderingContext2D):
32144-
return offscreenCanvasRenderingContext2D.jsValue
32145-
case let .webGL2RenderingContext(webGL2RenderingContext):
32146-
return webGL2RenderingContext.jsValue
32147-
case let .webGLRenderingContext(webGLRenderingContext):
32148-
return webGLRenderingContext.jsValue
32149-
}
32150-
}
32151-
}
32152-
3215332041
public protocol Any_Path2D_or_String: ConvertibleToJSValue {}
3215432042
extension Path2D: Any_Path2D_or_String {}
3215532043
extension String: Any_Path2D_or_String {}
@@ -32276,90 +32164,6 @@ public enum ReadableStreamReader: JSValueCompatible, Any_ReadableStreamReader {
3227632164
}
3227732165
}
3227832166

32279-
public protocol Any_RenderingContext: ConvertibleToJSValue {}
32280-
extension CanvasRenderingContext2D: Any_RenderingContext {}
32281-
extension GPUCanvasContext: Any_RenderingContext {}
32282-
extension ImageBitmapRenderingContext: Any_RenderingContext {}
32283-
extension WebGL2RenderingContext: Any_RenderingContext {}
32284-
extension WebGLRenderingContext: Any_RenderingContext {}
32285-
32286-
public enum RenderingContext: JSValueCompatible, Any_RenderingContext {
32287-
case canvasRenderingContext2D(CanvasRenderingContext2D)
32288-
case gpuCanvasContext(GPUCanvasContext)
32289-
case imageBitmapRenderingContext(ImageBitmapRenderingContext)
32290-
case webGL2RenderingContext(WebGL2RenderingContext)
32291-
case webGLRenderingContext(WebGLRenderingContext)
32292-
32293-
public var canvasRenderingContext2D: CanvasRenderingContext2D? {
32294-
switch self {
32295-
case let .canvasRenderingContext2D(canvasRenderingContext2D): return canvasRenderingContext2D
32296-
default: return nil
32297-
}
32298-
}
32299-
32300-
public var gpuCanvasContext: GPUCanvasContext? {
32301-
switch self {
32302-
case let .gpuCanvasContext(gpuCanvasContext): return gpuCanvasContext
32303-
default: return nil
32304-
}
32305-
}
32306-
32307-
public var imageBitmapRenderingContext: ImageBitmapRenderingContext? {
32308-
switch self {
32309-
case let .imageBitmapRenderingContext(imageBitmapRenderingContext): return imageBitmapRenderingContext
32310-
default: return nil
32311-
}
32312-
}
32313-
32314-
public var webGL2RenderingContext: WebGL2RenderingContext? {
32315-
switch self {
32316-
case let .webGL2RenderingContext(webGL2RenderingContext): return webGL2RenderingContext
32317-
default: return nil
32318-
}
32319-
}
32320-
32321-
public var webGLRenderingContext: WebGLRenderingContext? {
32322-
switch self {
32323-
case let .webGLRenderingContext(webGLRenderingContext): return webGLRenderingContext
32324-
default: return nil
32325-
}
32326-
}
32327-
32328-
public static func construct(from value: JSValue) -> Self? {
32329-
if let canvasRenderingContext2D: CanvasRenderingContext2D = value.fromJSValue() {
32330-
return .canvasRenderingContext2D(canvasRenderingContext2D)
32331-
}
32332-
if let gpuCanvasContext: GPUCanvasContext = value.fromJSValue() {
32333-
return .gpuCanvasContext(gpuCanvasContext)
32334-
}
32335-
if let imageBitmapRenderingContext: ImageBitmapRenderingContext = value.fromJSValue() {
32336-
return .imageBitmapRenderingContext(imageBitmapRenderingContext)
32337-
}
32338-
if let webGL2RenderingContext: WebGL2RenderingContext = value.fromJSValue() {
32339-
return .webGL2RenderingContext(webGL2RenderingContext)
32340-
}
32341-
if let webGLRenderingContext: WebGLRenderingContext = value.fromJSValue() {
32342-
return .webGLRenderingContext(webGLRenderingContext)
32343-
}
32344-
return nil
32345-
}
32346-
32347-
public var jsValue: JSValue {
32348-
switch self {
32349-
case let .canvasRenderingContext2D(canvasRenderingContext2D):
32350-
return canvasRenderingContext2D.jsValue
32351-
case let .gpuCanvasContext(gpuCanvasContext):
32352-
return gpuCanvasContext.jsValue
32353-
case let .imageBitmapRenderingContext(imageBitmapRenderingContext):
32354-
return imageBitmapRenderingContext.jsValue
32355-
case let .webGL2RenderingContext(webGL2RenderingContext):
32356-
return webGL2RenderingContext.jsValue
32357-
case let .webGLRenderingContext(webGLRenderingContext):
32358-
return webGLRenderingContext.jsValue
32359-
}
32360-
}
32361-
}
32362-
3236332167
public protocol Any_RequestInfo: ConvertibleToJSValue {}
3236432168
extension Request: Any_RequestInfo {}
3236532169
extension String: Any_RequestInfo {}
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import JavaScriptKit
2+
3+
public protocol RenderingContext: JSValueCompatible {
4+
/// Textual identifier of this context type, passed to `getContext` under the hood.
5+
static var contextID: JSString { get }
6+
}
7+
8+
extension GPUCanvasContext: RenderingContext {
9+
public static var contextID: JSString { "webgpu" }
10+
}
11+
12+
extension ImageBitmapRenderingContext: RenderingContext {
13+
public static var contextID: JSString { "bitmaprenderer" }
14+
}
15+
16+
extension CanvasRenderingContext2D: RenderingContext {
17+
public static var contextID: JSString { "2d" }
18+
}
19+
20+
extension WebGL2RenderingContext: RenderingContext {
21+
public static var contextID: JSString { "webgl2" }
22+
}
23+
24+
extension WebGLRenderingContext: RenderingContext {
25+
public static var contextID: JSString { "webgl" }
26+
}
27+
28+
public protocol OffscreenRenderingContext: JSValueCompatible {
29+
static var contextID: JSString { get }
30+
}
31+
32+
extension GPUCanvasContext: OffscreenRenderingContext {}
33+
extension ImageBitmapRenderingContext: OffscreenRenderingContext {}
34+
extension CanvasRenderingContext2D: OffscreenRenderingContext {}
35+
extension WebGL2RenderingContext: OffscreenRenderingContext {}
36+
extension WebGLRenderingContext: OffscreenRenderingContext {}
37+
extension OffscreenCanvasRenderingContext2D: OffscreenRenderingContext {
38+
public static var contextID: JSString { "2d" }
39+
}
40+
41+
extension Strings {
42+
@usableFromInline static let getContext: JSString = "getContext"
43+
}
44+
45+
public extension HTMLCanvasElement {
46+
@inlinable func getContext<Context: RenderingContext>(
47+
_ contextType: Context.Type,
48+
options: JSValue? = nil
49+
) -> Context? {
50+
let this = jsObject
51+
return this[Strings.getContext].function!(
52+
this: this,
53+
arguments: [contextType.contextID.jsValue, options?.jsValue ?? .undefined]
54+
).fromJSValue()!
55+
}
56+
}
57+
58+
public extension OffscreenCanvas {
59+
@inlinable func getContext<Context: OffscreenRenderingContext>(
60+
_ contextType: Context.Type,
61+
options: JSValue? = nil
62+
) -> Context? {
63+
let this = jsObject
64+
return this[Strings.getContext].function!(
65+
this: this,
66+
arguments: [contextType.contextID.jsValue, options?.jsValue ?? .undefined]
67+
).fromJSValue()!
68+
}
69+
}

Sources/WebAPIKitDemo/WebGLDemo.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ func runWebGLDemo() {
117117
// Get A WebGL context
118118
let canvas = HTMLCanvasElement(from: document.createElement(localName: "canvas"))!
119119
_ = document.body?.appendChild(node: canvas)
120-
let context = canvas.getContext(contextId: "webgl2")!.webGL2RenderingContext!
120+
guard let context = canvas.getContext(WebGL2RenderingContext.self) else {
121+
console.error(data: "Failed to create WebGL2 rendering context")
122+
return
123+
}
121124

122125
// create GLSL shaders, upload the GLSL source, compile the shaders
123126
guard

Sources/WebIDLToSwift/IDLBuilder.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ enum IDLBuilder {
2525
"BigInt64Array_or_BigUint64Array_or_DataView_or_Float32Array_or_Float64Array_or_Int16Array_or_Int32Array_or_Int8Array_or_Uint16Array_or_Uint32Array_or_Uint8Array_or_Uint8ClampedArray",
2626
// RotationMatrixType
2727
"DOMMatrix_or_Float32Array_or_Float64Array",
28+
"RenderingContext",
29+
"OffscreenRenderingContext",
30+
"OffscreenRenderingContextId",
2831
]
2932

3033
static func writeFile(path: String, content: String) throws {
@@ -58,7 +61,7 @@ enum IDLBuilder {
5861
"FileSystemEntry": ["getParent"],
5962
"FileSystemFileEntry": ["file"],
6063
"Geolocation": ["getCurrentPosition", "watchPosition"],
61-
"HTMLCanvasElement": ["toBlob"],
64+
"HTMLCanvasElement": ["toBlob", "getContext"],
6265
"HTMLVideoElement": ["requestVideoFrameCallback"],
6366
"IntersectionObserver": ["<constructor>"],
6467
"LayoutWorkletGlobalScope": ["registerLayout"],
@@ -97,6 +100,7 @@ enum IDLBuilder {
97100
"HTMLMediaElement": ["srcObject"],
98101
"Blob": ["stream"],
99102
"Body": ["body"],
103+
"OffscreenCanvas": ["getContext"],
100104
],
101105
types: merged.types
102106
)) {

0 commit comments

Comments
 (0)