Skip to content

Commit 5529520

Browse files
authored
Merge pull request #687 from gn-adin-b/adin/property-access-tracing
Add property access tracing to JSTracing
1 parent 3eff48a commit 5529520

File tree

3 files changed

+97
-3
lines changed

3 files changed

+97
-3
lines changed

Sources/JavaScriptKit/FundamentalObjects/JSObject.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,18 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
153153
public subscript(_ name: String) -> JSValue {
154154
get {
155155
assertOnOwnerThread(hint: "reading '\(name)' property")
156+
#if Tracing
157+
let traceEnd = JSTracingHooks.beginJSCall(.propertyGet(receiver: self, propertyName: name))
158+
defer { traceEnd?() }
159+
#endif
156160
return getJSValue(this: self, name: JSString(name))
157161
}
158162
set {
159163
assertOnOwnerThread(hint: "writing '\(name)' property")
164+
#if Tracing
165+
let traceEnd = JSTracingHooks.beginJSCall(.propertySet(receiver: self, propertyName: name, value: newValue))
166+
defer { traceEnd?() }
167+
#endif
160168
setJSValue(this: self, name: JSString(name), value: newValue)
161169
}
162170
}
@@ -167,10 +175,20 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
167175
public subscript(_ name: JSString) -> JSValue {
168176
get {
169177
assertOnOwnerThread(hint: "reading '<<JSString>>' property")
178+
#if Tracing
179+
let traceEnd = JSTracingHooks.beginJSCall(.propertyGet(receiver: self, propertyName: String(name)))
180+
defer { traceEnd?() }
181+
#endif
170182
return getJSValue(this: self, name: name)
171183
}
172184
set {
173185
assertOnOwnerThread(hint: "writing '<<JSString>>' property")
186+
#if Tracing
187+
let traceEnd = JSTracingHooks.beginJSCall(
188+
.propertySet(receiver: self, propertyName: String(name), value: newValue)
189+
)
190+
defer { traceEnd?() }
191+
#endif
174192
setJSValue(this: self, name: name, value: newValue)
175193
}
176194
}
@@ -181,10 +199,20 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
181199
public subscript(_ index: Int) -> JSValue {
182200
get {
183201
assertOnOwnerThread(hint: "reading '\(index)' property")
202+
#if Tracing
203+
let traceEnd = JSTracingHooks.beginJSCall(.propertyGet(receiver: self, propertyName: String(index)))
204+
defer { traceEnd?() }
205+
#endif
184206
return getJSValue(this: self, index: Int32(index))
185207
}
186208
set {
187209
assertOnOwnerThread(hint: "writing '\(index)' property")
210+
#if Tracing
211+
let traceEnd = JSTracingHooks.beginJSCall(
212+
.propertySet(receiver: self, propertyName: String(index), value: newValue)
213+
)
214+
defer { traceEnd?() }
215+
#endif
188216
setJSValue(this: self, index: Int32(index), value: newValue)
189217
}
190218
}
@@ -195,10 +223,20 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
195223
public subscript(_ name: JSSymbol) -> JSValue {
196224
get {
197225
assertOnOwnerThread(hint: "reading '<<JSSymbol>>' property")
226+
#if Tracing
227+
let traceEnd = JSTracingHooks.beginJSCall(.propertyGet(receiver: self, propertyName: "<<JSSymbol>>"))
228+
defer { traceEnd?() }
229+
#endif
198230
return getJSValue(this: self, symbol: name)
199231
}
200232
set {
201233
assertOnOwnerThread(hint: "writing '<<JSSymbol>>' property")
234+
#if Tracing
235+
let traceEnd = JSTracingHooks.beginJSCall(
236+
.propertySet(receiver: self, propertyName: "<<JSSymbol>>", value: newValue)
237+
)
238+
defer { traceEnd?() }
239+
#endif
202240
setJSValue(this: self, symbol: name, value: newValue)
203241
}
204242
}

Sources/JavaScriptKit/JSTracing.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public struct JSTracing: Sendable {
77
public enum JSCallInfo {
88
case function(function: JSObject, arguments: [JSValue])
99
case method(receiver: JSObject, methodName: String?, arguments: [JSValue])
10+
case propertyGet(receiver: JSObject, propertyName: String)
11+
case propertySet(receiver: JSObject, propertyName: String, value: JSValue)
1012
}
1113

1214
/// Register a hook for Swift to JavaScript calls.

Tests/JavaScriptKitTests/JSTracingTests.swift

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,69 @@ final class JSTracingTests: XCTestCase {
1616
let prop5 = try XCTUnwrap(globalObject1.prop_5.object)
1717
_ = prop5.func6!(true, 1, 2)
1818

19-
XCTAssertEqual(startInfo.count, 1)
20-
guard case let .method(receiver, methodName, arguments) = startInfo.first else {
19+
let methodEvents = startInfo.filter {
20+
if case .method = $0 { return true }
21+
return false
22+
}
23+
XCTAssertEqual(methodEvents.count, 1)
24+
guard case let .method(receiver, methodName, arguments) = methodEvents.first else {
2125
XCTFail("Expected method info")
2226
return
2327
}
2428
XCTAssertEqual(receiver.id, prop5.id)
2529
XCTAssertEqual(methodName, "func6")
2630
XCTAssertEqual(arguments, [.boolean(true), .number(1), .number(2)])
27-
XCTAssertEqual(ended, 1)
31+
XCTAssertEqual(ended, startInfo.count)
32+
}
33+
34+
func testJSCallHookReportsPropertyAccess() throws {
35+
var startInfo: [JSTracing.JSCallInfo] = []
36+
var ended = 0
37+
let remove = JSTracing.default.addJSCallHook { info in
38+
startInfo.append(info)
39+
return { ended += 1 }
40+
}
41+
defer { remove() }
42+
43+
let obj = JSObject()
44+
obj.foo = .number(42)
45+
46+
// Reset after setup so we only capture the reads/writes below.
47+
startInfo.removeAll()
48+
ended = 0
49+
50+
// Read a property (triggers propertyGet)
51+
let _: JSValue = obj.foo
52+
53+
// Write a property (triggers propertySet)
54+
obj.foo = .number(999)
55+
56+
let propEvents = startInfo.filter {
57+
switch $0 {
58+
case .propertyGet(_, let name) where name == "foo": return true
59+
case .propertySet(_, let name, _) where name == "foo": return true
60+
default: return false
61+
}
62+
}
63+
64+
XCTAssertEqual(propEvents.count, 2)
65+
66+
guard case .propertyGet(let getReceiver, let getName) = propEvents[0] else {
67+
XCTFail("Expected propertyGet info")
68+
return
69+
}
70+
XCTAssertEqual(getReceiver.id, obj.id)
71+
XCTAssertEqual(getName, "foo")
72+
73+
guard case .propertySet(let setReceiver, let setName, let setValue) = propEvents[1] else {
74+
XCTFail("Expected propertySet info")
75+
return
76+
}
77+
XCTAssertEqual(setReceiver.id, obj.id)
78+
XCTAssertEqual(setName, "foo")
79+
XCTAssertEqual(setValue, .number(999))
80+
81+
XCTAssertEqual(ended, startInfo.count)
2882
}
2983

3084
func testJSClosureCallHookReportsMetadata() throws {

0 commit comments

Comments
 (0)