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.
- data/.document +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +114 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/lib/webkit_remote/browser.rb +150 -0
- data/lib/webkit_remote/client/page.rb +59 -0
- data/lib/webkit_remote/client/page_events.rb +45 -0
- data/lib/webkit_remote/client/runtime.rb +406 -0
- data/lib/webkit_remote/client.rb +114 -0
- data/lib/webkit_remote/event.rb +138 -0
- data/lib/webkit_remote/process.rb +160 -0
- data/lib/webkit_remote/rpc.rb +205 -0
- data/lib/webkit_remote/top_level.rb +31 -0
- data/lib/webkit_remote.rb +12 -0
- data/test/fixtures/config.ru +12 -0
- data/test/fixtures/html/load.html +7 -0
- data/test/fixtures/html/runtime.html +31 -0
- data/test/helper.rb +44 -0
- data/test/webkit_remote/browser_test.rb +80 -0
- data/test/webkit_remote/client/page_test.rb +31 -0
- data/test/webkit_remote/client/remote_object_group_test.rb +81 -0
- data/test/webkit_remote/client/remote_object_test.rb +133 -0
- data/test/webkit_remote/client/runtime_test.rb +109 -0
- data/test/webkit_remote/client_test.rb +99 -0
- data/test/webkit_remote/event_test.rb +83 -0
- data/test/webkit_remote/process_test.rb +52 -0
- data/test/webkit_remote/rpc_test.rb +55 -0
- data/test/webkit_remote_test.rb +63 -0
- metadata +274 -0
@@ -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
|