webkit_remote 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ module WebkitRemote
2
+
3
+ class Event
4
+
5
+ # Emitted when a page's load event is triggerred.
6
+ class PageLoaded < WebkitRemote::Event
7
+ register 'Page.loadEventFired'
8
+
9
+ # @return [Number] the event timestamp
10
+ attr_reader :timestamp
11
+
12
+ # @private Use Event#for instead of calling this constructor directly.
13
+ def initialize(rpc_event)
14
+ super
15
+ @timestamp = raw_data['timestamp']
16
+ end
17
+
18
+ # @private Use Event#can_receive instead of calling this directly.
19
+ def self.can_reach?(client)
20
+ !!client.page_events
21
+ end
22
+ end # class WebkitRemote::Event::PageLoaded
23
+
24
+ # Emitted when a page's DOMcontent event is triggerred.
25
+ class PageDomReady < WebkitRemote::Event
26
+ register 'Page.domContentEventFired'
27
+
28
+ # @return [Number] the event timestamp
29
+ attr_reader :timestamp
30
+
31
+ # @private Use Event#for instead of calling this constructor directly.
32
+ def initialize(rpc_event)
33
+ super
34
+ @timestamp = raw_data['timestamp']
35
+ end
36
+
37
+ # @private Use Event#can_receive instead of calling this directly.
38
+ def self.can_reach?(client)
39
+ !!client.page_events
40
+ end
41
+ end # class WebkitRemote::Event::PageDomReady
42
+
43
+ end # namespace WebkitRemote::Event
44
+
45
+ end # namepspace WebkitRemote
@@ -0,0 +1,406 @@
1
+ module WebkitRemote
2
+
3
+ class Client
4
+
5
+ # API for the Runtime domain.
6
+ module Runtime
7
+ # Evals a JavaScript expression.
8
+ #
9
+ # @param [String] expression the JavaScript expression to be evaluated
10
+ # @param [Hash] opts tweaks
11
+ # @option opts [String, Symbol] group the name of an object group (think
12
+ # memory pools); the objects in a group can be released together by one
13
+ # call to WebkitRemote::Client::RemoteObjectGroup#release
14
+ # @return [WebkitRemote::Client::RemoteObject, Boolean, Number, String] the
15
+ # result of evaluating the expression
16
+ def remote_eval(expression, opts = {})
17
+ group_name = opts[:group] || '_'
18
+ # NOTE: returnByValue is always set to false to avoid some extra complexity
19
+ result = @rpc.call 'Runtime.evaluate', expression: expression,
20
+ objectGroup: group_name
21
+ object = WebkitRemote::Client::RemoteObject.for result['result'], self,
22
+ group_name
23
+ if result['wasThrown']
24
+ # TODO(pwnall): some wrapper for exceptions?
25
+ object
26
+ else
27
+ object
28
+ end
29
+ end
30
+
31
+ # Retrieves a group of remote objects by its name.
32
+ #
33
+ # @param [String, Symbol] group_name name given to remote_eval when the
34
+ # object was created
35
+ # @param [Boolean] create if true, fetching a group that does not exist will
36
+ # create the group; this parameter should only be used internally
37
+ # @return [WebkitRemote::Client::RemoteObject, Boolean, Number, String, nil]
38
+ # a Ruby wrapper for the evaluation result; primitives get wrapped by
39
+ # standard Ruby classes, and objects get wrapped by RemoteObject
40
+ # instances
41
+ def object_group(group_name, create = false)
42
+ group_name = group_name.to_s
43
+ group = @runtime_groups[group_name]
44
+ return group if group
45
+ if create
46
+ @runtime_groups[group_name] =
47
+ WebkitRemote::Client::RemoteObjectGroup.new(group_name, self)
48
+ else
49
+ nil
50
+ end
51
+ end
52
+
53
+ # Removes a group from the list of tracked groups.
54
+ #
55
+ # @private Use WebkitRemote::Client::RemoteObjectGroup#release instead of
56
+ # calling this directly.
57
+ # @return [WebkitRemote::Client] self
58
+ def object_group_remove(group)
59
+ @runtime_groups.delete group.name
60
+ self
61
+ end
62
+
63
+ # @private Called by the WebkitRemote::Client constructor.
64
+ def initialize_runtime()
65
+ @runtime_groups = {}
66
+ end
67
+ end # module WebkitRemote::Client::Runtime
68
+
69
+ initializer :initialize_runtime
70
+ include Runtime
71
+
72
+ # Mirrors a RemoteObject, defined in the Runtime domain.
73
+ class RemoteObject
74
+ # @return [String] the class name computed by WebKit for this object
75
+ attr_reader :js_class_name
76
+
77
+ # @return [String] the return value of the JavaScript typeof operator
78
+ attr_reader :js_type
79
+
80
+ # @return [Symbol] an additional type hint for this object; documented values
81
+ # are :array, :date, :node, :null, :regexp
82
+ attr_reader :js_subtype
83
+
84
+ # @return [String] string that would be displayed in the Webkit console to
85
+ # represent this object
86
+ attr_reader :description
87
+
88
+ # @return [Object] primitive value for this object, if available
89
+ attr_reader :value
90
+
91
+ # @return [Hash<String, Object>] the raw info provided by the remote debugger
92
+ # RPC call; might be useful for accessing extended metadata that is not
93
+ # (yet) recognized by WebkitRemote
94
+ attr_reader :raw_data
95
+
96
+ # @return [Boolean] true if the objects in this group were already released
97
+ attr_reader :released
98
+ alias_method :released?, :released
99
+
100
+ # @return [WebkitRemote::Client] remote debugging client for the browser tab
101
+ # that owns the objects in this group
102
+ attr_reader :client
103
+
104
+ # @return [WebkitRemote::Client::RemoteObjectGroup] the group that contains
105
+ # this object; the object can be released by calling release_all on the
106
+ # group
107
+ attr_reader :group
108
+
109
+ # @return [String] identifies this object in the remote debugger
110
+ # @private Use the RemoteObject methods instead of calling this directly.
111
+ attr_reader :remote_id
112
+
113
+ # Releases this remote object on the browser side.
114
+ #
115
+ # @return [Webkit::Client::RemoteObject] self
116
+ def release
117
+ return if @released
118
+ @rpc.call 'Runtime.releaseObject', objectId: @remote_id
119
+ @group.remove self
120
+ released!
121
+ end
122
+
123
+ # This object's properties.
124
+ #
125
+ # If the object's properties have not been retrieved, this method retrieves
126
+ # them via a RPC call.
127
+ #
128
+ # @return [Hash<Symbol, Webkit::Client::RemoteProperty>] frozen Hash containg
129
+ # the object's properties
130
+ def properties
131
+ @properties || properties!
132
+ end
133
+
134
+ # This object's properties, guaranteed to be fresh.
135
+ #
136
+ # This method always reloads the object's properties via a RPC call.
137
+ #
138
+ # @return [Hash<Symbol, Webkit::Client::RemoteProperty>] frozen Hash containg
139
+ # the object's properties
140
+ def properties!
141
+ result = @rpc.call 'Runtime.getProperties', objectId: @remote_id
142
+ @properties = Hash[
143
+ result['result'].map do |raw_property|
144
+ property = WebkitRemote::Client::RemoteProperty.new raw_property, self
145
+ [property.name, property]
146
+ end
147
+ ].freeze
148
+ end
149
+
150
+ # Calls a function with "this" bound to this object.
151
+ #
152
+ # @param [String] function_expression a JavaScript expression that should
153
+ # evaluate to a function
154
+ # @param [Array<WebkitRemote::Client::Object, String, Number, Boolean, nil>]
155
+ # args the arguments passed to the function
156
+ # @return [WebkitRemote::Client::RemoteObject, Boolean, Number, String, nil]
157
+ # a Ruby wrapper for the given raw object; primitives get wrapped by
158
+ # standard Ruby classes, and objects get wrapped by RemoteObject
159
+ # instances
160
+ def bound_call(function_expression, *args)
161
+ call_args = args.map do |arg|
162
+ if arg.kind_of? WebkitRemote::Client::RemoteObject
163
+ { objectId: arg.remote_id }
164
+ else
165
+ { value: arg }
166
+ end
167
+ end
168
+ result = @rpc.call 'Runtime.callFunctionOn', objectId: @remote_id,
169
+ functionDeclaration: function_expression, arguments: call_args,
170
+ returnByValue: false
171
+ object = WebkitRemote::Client::RemoteObject.for result['result'], @client,
172
+ @group.name
173
+ if result['wasThrown']
174
+ # TODO(pwnall): some wrapper for exceptions?
175
+ object
176
+ else
177
+ object
178
+ end
179
+ end
180
+
181
+ # Wraps a raw object returned by the Webkit remote debugger RPC protocol.
182
+ #
183
+ # @private Use WebkitRemote::Client::Runtime#remote_eval instead of calling
184
+ # this directly.
185
+ #
186
+ # @param [Hash<String, Object>] raw_object a RemoteObject instance, according
187
+ # to the Webkit remote debugging protocol; this is the return value of a
188
+ # 'Runtime.evaluate' RPC call
189
+ # @param [WebkitRemote::Client::Runtime] client remote debugging client for
190
+ # the browser tab that owns this object
191
+ # @param [String] group_name name of the object group that will hold this
192
+ # object; object groups work like memory pools
193
+ # @return [WebkitRemote::Client::RemoteObject, Boolean, Number, String] a
194
+ # Ruby wrapper for the given raw object; primitives get wrapped by
195
+ # standard Ruby classes, and objects get wrapped by RemoteObject
196
+ # instances
197
+ def self.for(raw_object, client, group_name)
198
+ if remote_id = raw_object['objectId']
199
+ group = client.object_group group_name, true
200
+ return group.get(remote_id) ||
201
+ WebkitRemote::Client::RemoteObject.new(raw_object, group)
202
+ else
203
+ # primitive types
204
+ case raw_object['type'] ? raw_object['type'].to_sym : nil
205
+ when :boolean, :number, :string
206
+ return raw_object['value']
207
+ when :undefined
208
+ # TODO(pwnall): Not sure what to do here.
209
+ when :function
210
+ # TODO(pwnall): Not sure what to do here.
211
+ when :object
212
+ case raw_object['subtype'] ? raw_object['subtype'].to_sym : nil
213
+ when :null
214
+ return nil
215
+ end
216
+ # TODO(pwnall): Figure this out.
217
+ end
218
+ end
219
+ raise RuntimeError, "Unable to parse #{raw_object.inspect}"
220
+ end
221
+
222
+ # Wraps a remote JavaScript object
223
+ #
224
+ # @private RemoteObject#for should be used instead of this, as it handles
225
+ # some edge cases
226
+ def initialize(raw_object, group)
227
+ @group = group
228
+ @client = group.client
229
+ @rpc = client.rpc
230
+ @released = false
231
+
232
+ @raw_data = raw_object
233
+ @remote_id = raw_object['objectId']
234
+ @js_class_name = raw_object['className']
235
+ @description = raw_object['description']
236
+ @js_type = raw_object['type'].to_sym
237
+ if raw_object['subtype']
238
+ @js_subtype = raw_object['subtype'].to_sym
239
+ else
240
+ @js_subtype = nil
241
+ end
242
+ @value = raw_object['value']
243
+
244
+ group.add self
245
+ end
246
+
247
+ # Informs this object that it was released as part of a group release.
248
+ #
249
+ # @private Called by RemoteObjectGroup#release_all.
250
+ def released!
251
+ @released = true
252
+ @group = nil
253
+ end
254
+ end # class WebkitRemote::Client::RemoteObject
255
+
256
+ # Tracks the remote objects in a group (think memory pool).
257
+ class RemoteObjectGroup
258
+ # @return [String] the name of the group of remote objects
259
+ attr_reader :name
260
+
261
+ # @return [WebkitRemote::Client] remote debugging client for the browser tab
262
+ # that owns the objects in this group
263
+ attr_reader :client
264
+
265
+ # @return [Boolean] true if the objects in this group were already released
266
+ attr_reader :released
267
+ alias_method :released?, :released
268
+
269
+ # Releases all the remote objects in this group.
270
+ #
271
+ # @return [Webkit::Client::RemoteObjectGroup] self
272
+ def release_all
273
+ return if @objects.empty?
274
+ @rpc.call 'Runtime.releaseObjectGroup', objectGroup: name
275
+ @released = true
276
+ @objects.each_value { |object| object.released! }
277
+ @objects.clear
278
+ @client.object_group_remove self
279
+ self
280
+ end
281
+
282
+ # Checks if a remote object was allocated in this group.
283
+ #
284
+ # @param [WebkitRemote::Client] object
285
+ # @return [Boolean] true if the object belongs to this group, so releasing
286
+ # the group would get the object released
287
+ def include?(object)
288
+ @objects[object.remote_id] == object
289
+ end
290
+
291
+ # Creates a wrapper for a group of remote objects.
292
+ #
293
+ # @private Use WebkitRemote::Client::Runtime#remote_eval instead of calling
294
+ # this directly.
295
+ #
296
+ # @param [String] name name of this group of remote objects
297
+ # @param [WebkitRemote::Client] client remote debugging client for the
298
+ # browser tab that owns the objects in this group
299
+ def initialize(name, client)
300
+ @name = name
301
+ @client = client
302
+ @rpc = client.rpc
303
+ # TODO(pwnall): turn @objects into a set once equality is working
304
+ @objects = {}
305
+ @released = false
306
+ end
307
+
308
+ # Registers a remote object that belongs to this group.
309
+ #
310
+ # @private Use WebkitRemote::Client::Runtime#remote_eval instead of calling
311
+ # this directly.
312
+ #
313
+ # @param [WebkitRemote::Client::RemoteObject] object the object to be added
314
+ # to this group
315
+ # @return [WebkitRemote::Client::RemoteObjectGroup] self
316
+ def add(object)
317
+ if @released
318
+ raise RuntimeError, 'Remote object group already released'
319
+ end
320
+ @objects[object.remote_id] = object
321
+ self
322
+ end
323
+
324
+ # Removes a remote object that was individually released.
325
+ #
326
+ # @private Use WebkitRemote::Client::RemoteObject#release instead of calling
327
+ # this directly
328
+ #
329
+ # @param [WebkitRemote::Client::RemoteObject] object the object that will be
330
+ # removed from the group
331
+ # @return [WebkitRemote::Client::RemoteObjectGroup] self
332
+ def remove(object)
333
+ @objects.delete object.remote_id
334
+ if @objects.empty?
335
+ @released = true
336
+ @client.object_group_remove self
337
+ end
338
+ self
339
+ end
340
+
341
+ # Returns the object in this group with a given id.
342
+ #
343
+ # This helps avoid creating multiple wrappers for the same object.
344
+ #
345
+ # @param [String] remote_id the id to look for
346
+ # @return [WebkitRemote::Client::RemoteObject, nil] nil if there is no object
347
+ # whose remote_id matches the method's parameter
348
+ def get(remote_id)
349
+ @objects.fetch remote_id, nil
350
+ end
351
+ end # class WebkitRemote::Client::RemoteObjectGroup
352
+
353
+ # A property of a remote JavaScript object.
354
+ class RemoteProperty
355
+ # @return [Symbol] the
356
+ attr_reader :name
357
+
358
+ # @return [WebkitRemote::Client::RemoteObject, Boolean, Number, String, nil]
359
+ # a Ruby wrapper for the property's value; primitives get wrapped by
360
+ # standard Ruby classes, and objects get wrapped by RemoteObject
361
+ # instances
362
+ attr_reader :value
363
+
364
+ # @return [Boolean] true if JavaScript code can remove this property
365
+ attr_reader :configurable
366
+ alias_method :configurable?, :configurable
367
+
368
+ # @return [Boolean] true if JavaScript code can enumerate this property
369
+ attr_reader :enumerable
370
+ alias_method :enumerable?, :enumerable
371
+
372
+ # @return [Boolean] true if JavaScript code can change this property's value
373
+ attr_reader :writable
374
+ alias_method :writable?, :writable
375
+
376
+ # @return [WebkitRemote::RemoteObject] the object that this property belongs
377
+ # to
378
+ attr_reader :owner
379
+
380
+ # @param [Hash<String, Object>] raw_property a PropertyDescriptor instance,
381
+ # according to the Webkit remote debugging protocol; this is an item in
382
+ # the array returned by the 'Runtime.getProperties' RPC call
383
+ # @param [WebkitRemote::Client::RemoteObject] owner the object that this
384
+ # property belongs to
385
+ def initialize(raw_property, owner)
386
+ # NOTE: these are only used at construction time
387
+ client = owner.client
388
+ group_name = owner.group.name
389
+
390
+ @owner = owner
391
+ @name = raw_property['name'].to_sym
392
+ @configurable = !!raw_property['configurable']
393
+ @enumerable = !!raw_property['enumerable']
394
+ @writable = !!raw_property['writable']
395
+ @js_getter = raw_property['get'] && WebkitRemote::Client::RemoteObject.for(
396
+ raw_property['get'], client, group_name)
397
+ @js_setter = raw_property['set'] && WebkitRemote::Client::RemoteObject.for(
398
+ raw_property['set'], client, group_name)
399
+ @value = raw_property['value'] && WebkitRemote::Client::RemoteObject.for(
400
+ raw_property['value'], client, group_name)
401
+ end
402
+ end # class WebkitRemote::Client::RemoteProperty
403
+
404
+ end # namespace WebkitRemote::Client
405
+
406
+ end # namespace WebkitRemote