vapir-firefox 1.7.2 → 1.8.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/lib/vapir-firefox/{firefox.rb → browser.rb} +358 -145
- data/lib/vapir-firefox/clear_tracks.rb +50 -0
- data/lib/vapir-firefox/config.rb +17 -0
- data/lib/vapir-firefox/container.rb +67 -1
- data/lib/vapir-firefox/element.rb +62 -38
- data/lib/vapir-firefox/jssh_socket.rb +527 -206
- data/lib/vapir-firefox/page_container.rb +44 -19
- data/lib/vapir-firefox/version.rb +1 -1
- data/lib/vapir-firefox/window.rb +2 -2
- data/lib/vapir-firefox.rb +2 -5
- metadata +11 -32
@@ -97,16 +97,16 @@ module Vapir
|
|
97
97
|
include Firefox::Window
|
98
98
|
include Firefox::ModalDialogContainer
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
Kernel.warn "WARNING: JSSH_SOCKET RESET: resetting jssh socket. Any active javascript references will not exist on the new socket!"
|
104
|
-
end
|
100
|
+
# initializes a JsshSocket and stores in a class variable.
|
101
|
+
def self.initialize_jssh_socket # :nodoc:
|
102
|
+
uninitialize_jssh_socket
|
105
103
|
@@jssh_socket=JsshSocket.new
|
106
104
|
@@firewatir_jssh_objects=@@jssh_socket.object("Vapir").assign({})
|
107
105
|
@@jssh_socket
|
108
106
|
end
|
109
|
-
|
107
|
+
# returns a connected JsshSocket. pass :reset_if_dead => true if you suspect an existing
|
108
|
+
# socket may be dead, and you want a new one. a warning will be printed if this occurs.
|
109
|
+
def self.jssh_socket(options={}) # :nodoc:
|
110
110
|
if options[:reset] || !(class_variable_defined?('@@jssh_socket') && @@jssh_socket)
|
111
111
|
initialize_jssh_socket
|
112
112
|
end
|
@@ -114,11 +114,17 @@ module Vapir
|
|
114
114
|
begin
|
115
115
|
@@jssh_socket.assert_socket
|
116
116
|
rescue JsshConnectionError
|
117
|
+
Kernel.warn "WARNING: JsshSocket RESET: resetting jssh socket. Any active javascript references will not exist on the new socket!"
|
117
118
|
initialize_jssh_socket
|
118
119
|
end
|
119
120
|
end
|
120
121
|
@@jssh_socket
|
121
122
|
end
|
123
|
+
# unsets a the current jssh socket
|
124
|
+
def self.uninitialize_jssh_socket # :nodoc:
|
125
|
+
@@jssh_socket=nil
|
126
|
+
@@firewatir_jssh_objects=nil
|
127
|
+
end
|
122
128
|
def jssh_socket(options=nil)
|
123
129
|
options ? self.class.jssh_socket(options) : @@jssh_socket
|
124
130
|
end
|
@@ -129,19 +135,25 @@ module Vapir
|
|
129
135
|
#
|
130
136
|
# Input:
|
131
137
|
# options - Hash of any of the following options:
|
132
|
-
# :
|
138
|
+
# :timeout - Time to wait for Firefox to start. By default it waits for 2 seconds.
|
133
139
|
# This is done because if Firefox is not started and we try to connect
|
134
140
|
# to jssh on port 9997 an exception is thrown.
|
135
141
|
# :profile - The Firefox profile to use. If none is specified, Firefox will use
|
136
142
|
# the last used profile.
|
137
|
-
# :suppress_launch_process - do not create a new firefox process. Connect to an existing one.
|
138
|
-
# TODO: Start the firefox version given by user.
|
139
143
|
def initialize(options = {})
|
140
144
|
if(options.kind_of?(Integer))
|
141
|
-
options = {:
|
142
|
-
|
145
|
+
options = {:timeout => options}
|
146
|
+
if config.warn_deprecated
|
147
|
+
Kernel.warn_with_caller "DEPRECATION WARNING: #{self.class.name}.new takes an options hash - passing a number is deprecated. Please use #{self.class.name}.new(:timeout => #{options[:timeout]})"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
options = options_from_config(options, {:timeout => :attach_timeout, :binary_path => :firefox_binary_path, :profile => :firefox_profile, :wait => :wait}, [:attach, :goto, :wait_time])
|
151
|
+
if options[:wait_time]
|
152
|
+
if config.warn_deprecated
|
153
|
+
Kernel.warn_with_caller "DEPRECATION WARNING: the :wait_time option for #{self.class.name}.new has been renamed to :timeout for consistency. Please use #{self.class.name}.new(:timeout => #{options[:wait_time]})"
|
154
|
+
end
|
155
|
+
options[:timeout] = options[:wait_time]
|
143
156
|
end
|
144
|
-
options=handle_options(options, {:wait_time => 20}, [:attach, :goto, :binary_path])
|
145
157
|
if options[:binary_path]
|
146
158
|
@binary_path=options[:binary_path]
|
147
159
|
end
|
@@ -156,7 +168,7 @@ module Vapir
|
|
156
168
|
if options[:attach]
|
157
169
|
raise Vapir::Exception::NoBrowserException, "cannot attach using #{options[:attach].inspect} - could not connect to Firefox with JSSH"
|
158
170
|
else
|
159
|
-
launch_browser
|
171
|
+
launch_browser(options)
|
160
172
|
# if we just launched a the browser process, attach to the window
|
161
173
|
# that opened when we did that.
|
162
174
|
# but if options[:attach] is explicitly given as false (not nil),
|
@@ -166,7 +178,7 @@ module Vapir
|
|
166
178
|
options[:attach]=[:title, //]
|
167
179
|
end
|
168
180
|
end
|
169
|
-
::Waiter.try_for(options[:
|
181
|
+
::Waiter.try_for(options[:timeout], :exception => Vapir::Exception::NoBrowserException.new("Could not connect to the JSSH socket on the browser after #{options[:timeout]} seconds. Either Firefox did not start or JSSH is not installed and listening.")) do
|
170
182
|
begin
|
171
183
|
jssh_socket(:reset_if_dead => true).assert_socket
|
172
184
|
true
|
@@ -177,6 +189,12 @@ module Vapir
|
|
177
189
|
end
|
178
190
|
@browser_jssh_objects = jssh_socket.object('{}').store_rand_object_key(@@firewatir_jssh_objects) # this is an object that holds stuff for this browser
|
179
191
|
|
192
|
+
@pid = begin
|
193
|
+
self.pid
|
194
|
+
rescue NotImplementedError
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
180
198
|
if options[:attach]
|
181
199
|
attach(*options[:attach])
|
182
200
|
else
|
@@ -187,16 +205,9 @@ module Vapir
|
|
187
205
|
if options[:goto]
|
188
206
|
goto(options[:goto])
|
189
207
|
end
|
208
|
+
wait if options[:wait]
|
190
209
|
end
|
191
210
|
|
192
|
-
# def self.firefox_is_running?
|
193
|
-
# TODO/FIX: implement!
|
194
|
-
# true
|
195
|
-
# end
|
196
|
-
# def firefox_is_running?
|
197
|
-
# self.class.firefox_is_running?
|
198
|
-
# end
|
199
|
-
|
200
211
|
def mozilla_window_class_name
|
201
212
|
'MozillaUIWindowClass'
|
202
213
|
end
|
@@ -207,7 +218,7 @@ module Vapir
|
|
207
218
|
|
208
219
|
def exists?
|
209
220
|
# jssh_socket may be nil if the window has closed
|
210
|
-
jssh_socket && browser_window_object &&
|
221
|
+
jssh_socket && browser_window_object && self.class.browser_window_objects.include?(browser_window_object)
|
211
222
|
end
|
212
223
|
|
213
224
|
# Launches firebox browser
|
@@ -216,22 +227,15 @@ module Vapir
|
|
216
227
|
def launch_browser(options = {})
|
217
228
|
ff_options = []
|
218
229
|
if(options[:profile])
|
219
|
-
ff_options
|
230
|
+
ff_options += ['-no-remote', '-P', options[:profile]]
|
220
231
|
end
|
221
232
|
|
222
233
|
bin = path_to_bin()
|
223
|
-
@
|
234
|
+
@self_launched_browser = true
|
235
|
+
@t = Thread.new { system(bin, '-jssh', *ff_options) } # TODO: launch process in such a way that @pid can be noted
|
224
236
|
end
|
225
237
|
private :launch_browser
|
226
238
|
|
227
|
-
# Creates a new instance of Firefox. Loads the URL and return the instance.
|
228
|
-
# Input:
|
229
|
-
# url - url of the page to be loaded.
|
230
|
-
def self.start(url)
|
231
|
-
new(:goto => url)
|
232
|
-
end
|
233
|
-
|
234
|
-
|
235
239
|
# Loads the given url in the browser. Waits for the page to get loaded.
|
236
240
|
def goto(url)
|
237
241
|
assert_exists
|
@@ -239,6 +243,41 @@ module Vapir
|
|
239
243
|
wait
|
240
244
|
end
|
241
245
|
|
246
|
+
# Performs a HTTP POST action to an arbitrary URL with the given data. The data are represented
|
247
|
+
# to this method as a Hash, which is converted to the standard form of &-separated key=value
|
248
|
+
# strings POST data use.
|
249
|
+
#
|
250
|
+
# The data hash should be keyed with strings or symbols (which are converted to strings before
|
251
|
+
# being sent along), and its values should all be strings.
|
252
|
+
#
|
253
|
+
# If no post_data_hash is given, the body of the POST is empty.
|
254
|
+
def post_to(url, post_data_hash={})
|
255
|
+
require 'cgi'
|
256
|
+
raise ArgumentError, "post_data_hash must be a Hash!" unless post_data_hash.is_a?(Hash)
|
257
|
+
dataString = post_data_hash.map do |(key, val)|
|
258
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
259
|
+
raise ArgumentError, "post_data_hash keys must be strings or symbols; got key #{key.inspect} in hash #{post_data_hash.inspect}"
|
260
|
+
end
|
261
|
+
unless val.is_a?(String)
|
262
|
+
raise ArgumentError, "post_data_hash values must all be string;s got value #{val.inspect} in hash #{post_data_hash.inspect}"
|
263
|
+
end
|
264
|
+
CGI.escape(key.to_s)+'='+CGI.escape(val)
|
265
|
+
end.join("&")
|
266
|
+
stringStream = jssh_socket.Components.classes["@mozilla.org/io/string-input-stream;1"].createInstance(jssh_socket.Components.interfaces.nsIStringInputStream)
|
267
|
+
if stringStream.to_hash.key?('data')
|
268
|
+
stringStream.data=dataString
|
269
|
+
else
|
270
|
+
stringStream.setData(dataString, dataString.unpack("U*").length)
|
271
|
+
end
|
272
|
+
postData = jssh_socket.Components.classes["@mozilla.org/network/mime-input-stream;1"].createInstance(jssh_socket.Components.interfaces.nsIMIMEInputStream)
|
273
|
+
postData.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
274
|
+
postData.addContentLength = true
|
275
|
+
postData.setData(stringStream)
|
276
|
+
|
277
|
+
browser_object.loadURIWithFlags(url, 0, nil, nil, postData)
|
278
|
+
wait
|
279
|
+
end
|
280
|
+
|
242
281
|
# Loads the previous page (if there is any) in the browser. Waits for the page to get loaded.
|
243
282
|
def back
|
244
283
|
if browser_object.canGoBack
|
@@ -266,8 +305,6 @@ module Vapir
|
|
266
305
|
end
|
267
306
|
|
268
307
|
private
|
269
|
-
# This function creates a new socket at port 9997 and sets the default values for instance and class variables.
|
270
|
-
# Generatesi UnableToStartJSShException if cannot connect to jssh even after 3 tries.
|
271
308
|
def set_defaults(no_of_tries = 0)
|
272
309
|
@error_checkers = []
|
273
310
|
end
|
@@ -302,16 +339,15 @@ module Vapir
|
|
302
339
|
# Details : http://zenit.senecac.on.ca/wiki/index.php/Mozilla.dev.tech.xul#What_is_an_example_of_addProgressListener.3F
|
303
340
|
@browser_jssh_objects[:listener_object]={}
|
304
341
|
listener_object=@browser_jssh_objects[:listener_object]
|
305
|
-
listener_object[:QueryInterface]=jssh_socket.
|
306
|
-
|
307
|
-
{ if(aIID.equals(Components.interfaces.nsIWebProgressListener) || aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsISupports))
|
342
|
+
listener_object[:QueryInterface]=jssh_socket.function(:aIID) do %Q(
|
343
|
+
if(aIID.equals(Components.interfaces.nsIWebProgressListener) || aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsISupports))
|
308
344
|
{ return this;
|
309
345
|
}
|
310
346
|
throw Components.results.NS_NOINTERFACE;
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
347
|
+
)
|
348
|
+
end
|
349
|
+
listener_object[:onStateChange]= jssh_socket.function(:aWebProgress, :aRequest, :aStateFlag, :aStatus) do %Q(
|
350
|
+
var requests_in_progress=#{@requests_in_progress.ref};
|
315
351
|
if(aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
|
316
352
|
{ #{@updated_at_epoch_ms.ref}=new Date().getTime();
|
317
353
|
#{browser_object.ref}=#{browser_window_object.ref}.getBrowser();
|
@@ -347,7 +383,8 @@ module Vapir
|
|
347
383
|
--i;
|
348
384
|
}
|
349
385
|
}*/
|
350
|
-
|
386
|
+
)
|
387
|
+
end
|
351
388
|
browser_object.addProgressListener(listener_object)
|
352
389
|
end
|
353
390
|
|
@@ -363,22 +400,36 @@ module Vapir
|
|
363
400
|
end
|
364
401
|
|
365
402
|
public
|
366
|
-
#
|
403
|
+
# Closes the browser window.
|
404
|
+
#
|
405
|
+
# This will also quit the browser (see #quit_browser) only if this instance of Vapir::Firefox launched the browser when
|
406
|
+
# it was created, AND there are no other windows remaining open. On Windows, closing the last browser window quits
|
407
|
+
# the browser anyway; on other operating systems it does not.
|
367
408
|
def close
|
368
409
|
assert_exists
|
410
|
+
# we expect the browser may exit if there are no windows which aren't ourself. except on mac.
|
411
|
+
expect_exit = !self.class.window_objects.any?{|other_window| other_window != self.browser_window_object } && current_os != :macosx
|
369
412
|
begin
|
370
413
|
browser_window_object.close
|
371
414
|
# TODO/fix timeout; this shouldn't be a hard-coded magic number.
|
372
415
|
::Waiter.try_for(32, :exception => Exception::WindowFailedToCloseException.new("The browser window did not close")) do
|
373
416
|
!exists?
|
374
417
|
end
|
375
|
-
|
418
|
+
if expect_exit
|
419
|
+
::Waiter.try_for(8, :exception => nil) do
|
420
|
+
jssh_socket.assert_socket # this should error, going up past the waiter to the rescue block above
|
421
|
+
false
|
422
|
+
end
|
423
|
+
else
|
424
|
+
jssh_socket.assert_socket
|
425
|
+
end
|
376
426
|
rescue JsshConnectionError # the socket may disconnect when we close the browser, causing the JsshSocket to complain
|
377
|
-
|
427
|
+
Vapir::Firefox.uninitialize_jssh_socket
|
428
|
+
wait_for_process_exit(@pid)
|
378
429
|
end
|
430
|
+
|
379
431
|
@browser_window_object=@browser_object=@document_object=@content_window_object=@body_object=nil
|
380
|
-
@
|
381
|
-
if @launched_browser_process && @@jssh_socket
|
432
|
+
if @self_launched_browser && jssh_socket && !self.class.window_objects.any?{ true }
|
382
433
|
quit_browser(:force => false)
|
383
434
|
end
|
384
435
|
end
|
@@ -388,34 +439,143 @@ module Vapir
|
|
388
439
|
quit_browser(:force => false)
|
389
440
|
end
|
390
441
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
#
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
442
|
+
module FirefoxClassAndInstanceMethods
|
443
|
+
# quits the browser.
|
444
|
+
#
|
445
|
+
# quit_browser(:force => true) will force the browser to quit.
|
446
|
+
#
|
447
|
+
# if there is no existing connection to JSSH, this will attempt to create one. If that fails, JsshUnableToStart will be raised.
|
448
|
+
def quit_browser(options={})
|
449
|
+
jssh_socket(:reset_if_dead => true).assert_socket
|
450
|
+
options=handle_options(options, :force => false)
|
451
|
+
|
452
|
+
pid = @pid || begin
|
453
|
+
self.pid
|
454
|
+
rescue NotImplementedError
|
455
|
+
nil
|
456
|
+
end
|
457
|
+
|
458
|
+
# from https://developer.mozilla.org/en/How_to_Quit_a_XUL_Application
|
459
|
+
appStartup= jssh_socket.Components.classes['@mozilla.org/toolkit/app-startup;1'].getService(jssh_socket.Components.interfaces.nsIAppStartup)
|
460
|
+
quitSeverity = options[:force] ? jssh_socket.Components.interfaces.nsIAppStartup.eForceQuit : jssh_socket.Components.interfaces.nsIAppStartup.eAttemptQuit
|
461
|
+
begin
|
462
|
+
appStartup.quit(quitSeverity)
|
463
|
+
::Waiter.try_for(8, :exception => Exception::WindowFailedToCloseException.new("The browser did not quit")) do
|
464
|
+
jssh_socket.assert_socket # this should error, going up past the waiter to the rescue block above
|
465
|
+
false
|
466
|
+
end
|
467
|
+
rescue JsshConnectionError
|
468
|
+
Vapir::Firefox.uninitialize_jssh_socket
|
469
|
+
end
|
470
|
+
|
471
|
+
wait_for_process_exit(pid)
|
472
|
+
|
473
|
+
@browser_window_object=@browser_object=@document_object=@content_window_object=@body_object=nil
|
474
|
+
nil
|
475
|
+
end
|
476
|
+
|
477
|
+
# returns the pid of the currently-attached Firefox process.
|
478
|
+
#
|
479
|
+
# This only works on Firefox >= 3.6, on platforms supported (see #current_os), and raises
|
480
|
+
# NotImplementedError if it can't get the pid.
|
481
|
+
def pid
|
482
|
+
begin
|
483
|
+
begin
|
484
|
+
ctypes = jssh_socket.Components.utils.import("resource://gre/modules/ctypes.jsm").ctypes
|
485
|
+
rescue JsshJavascriptError
|
486
|
+
raise NotImplementedError, "Firefox 3.6 or greater is required for this method.\n\nOriginal error from firefox: #{$!.class}: #{$!.message}", $!.backtrace
|
487
|
+
end
|
488
|
+
lib, pidfunction, abi = *case current_os
|
489
|
+
when :macosx
|
490
|
+
["libc.dylib", 'getpid', ctypes.default_abi]
|
491
|
+
when :linux
|
492
|
+
["libc.so.6", 'getpid', ctypes.default_abi]
|
493
|
+
when :windows
|
494
|
+
['kernel32', 'GetCurrentProcessId', ctypes.stdcall_abi]
|
495
|
+
else
|
496
|
+
raise NotImplementedError, "don't know how to get pid for #{current_os}"
|
497
|
+
end
|
498
|
+
getpid = ctypes.open(lib).declare(pidfunction, abi, ctypes.int32_t)
|
499
|
+
getpid.call()
|
403
500
|
end
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
#
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
501
|
+
end
|
502
|
+
|
503
|
+
# attempts to determine whether the given process is still running. will raise
|
504
|
+
# NotImplementedError if it can't determine this.
|
505
|
+
def process_running?(pid)
|
506
|
+
case current_os
|
507
|
+
when :windows
|
508
|
+
kernel32 = Vapir::Firefox.instance_eval do # define this on the class for reuse
|
509
|
+
@kernel32 ||= begin
|
510
|
+
require 'ffi'
|
511
|
+
kernel32 = Module.new
|
512
|
+
kernel32.send :extend, FFI::Library
|
513
|
+
kernel32.ffi_lib 'kernel32'
|
514
|
+
kernel32.ffi_convention :stdcall
|
515
|
+
kernel32.attach_function :OpenProcess, [:ulong, :char, :ulong], :pointer
|
516
|
+
kernel32.attach_function :GetExitCodeProcess, [:pointer, :pointer], :char
|
517
|
+
kernel32.const_set('PROCESS_QUERY_INFORMATION', 0x0400)
|
518
|
+
kernel32.const_set('STILL_ACTIVE', 259)
|
519
|
+
kernel32
|
520
|
+
end
|
521
|
+
end
|
522
|
+
process_handle = kernel32.OpenProcess(kernel32::PROCESS_QUERY_INFORMATION, 0, pid)
|
523
|
+
exit_code=FFI::MemoryPointer.new(:ulong)
|
524
|
+
result = kernel32.GetExitCodeProcess(process_handle, exit_code)
|
525
|
+
if result == 0
|
526
|
+
raise "GetExitCodeProcess failed"
|
527
|
+
end
|
528
|
+
return exit_code.get_ulong(0)==kernel32::STILL_ACTIVE
|
529
|
+
when :linux, :macosx
|
530
|
+
`ps -p #{pid}`
|
531
|
+
$? == 0
|
532
|
+
else
|
533
|
+
raise NotImplementedError
|
534
|
+
end
|
535
|
+
end
|
415
536
|
|
416
|
-
|
417
|
-
|
537
|
+
# waits until the Firefox process with the given pid has exited.
|
538
|
+
#
|
539
|
+
# if no pid is given, waits the configured amount of time until it is safe to assume
|
540
|
+
# the pfirefox process has exited.
|
541
|
+
def wait_for_process_exit(pid)
|
542
|
+
if pid
|
543
|
+
::Waiter.try_for(16, :exception => Exception::WindowFailedToCloseException.new("The browser did not quit")) do
|
544
|
+
!process_running?(pid)
|
545
|
+
end
|
546
|
+
else
|
547
|
+
sleep config.firefox_quit_sleep_time
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
# returns a symbol representing the platform we're currently running on - currently
|
552
|
+
# implemented platforms are :windows, :macosx, and :linux. raises NotImplementedError if the
|
553
|
+
# current platform isn't one of those.
|
554
|
+
def current_os
|
555
|
+
@current_os ||= begin
|
556
|
+
platform= if RUBY_PLATFORM =~ /java/
|
557
|
+
require 'java'
|
558
|
+
java.lang.System.getProperty("os.name")
|
559
|
+
else
|
560
|
+
RUBY_PLATFORM
|
561
|
+
end
|
562
|
+
case platform
|
563
|
+
when /mswin|windows|mingw32/i
|
564
|
+
:windows
|
565
|
+
when /darwin|mac os/i
|
566
|
+
:macosx
|
567
|
+
when /linux/i
|
568
|
+
:linux
|
569
|
+
else
|
570
|
+
raise NotImplementedError, "Unidentified platform #{platform}"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
418
574
|
end
|
575
|
+
include FirefoxClassAndInstanceMethods
|
576
|
+
extend FirefoxClassAndInstanceMethods
|
577
|
+
|
578
|
+
|
419
579
|
|
420
580
|
# Used for attaching pop up window to an existing Firefox window, either by url or title.
|
421
581
|
# ff.attach(:url, 'http://www.google.com')
|
@@ -425,7 +585,7 @@ module Vapir
|
|
425
585
|
# Instance of newly attached window.
|
426
586
|
def attach(how, what)
|
427
587
|
@browser_window_object = case how
|
428
|
-
when :
|
588
|
+
when :browser_window_object
|
429
589
|
what
|
430
590
|
else
|
431
591
|
find_window(how, what)
|
@@ -437,24 +597,15 @@ module Vapir
|
|
437
597
|
set_browser_document
|
438
598
|
self
|
439
599
|
end
|
440
|
-
|
441
|
-
# Class method to return a browser object if a window matches for how
|
442
|
-
# and what. Window can be referenced by url or title.
|
443
|
-
# The second argument can be either a string or a regular expression.
|
444
|
-
# Vapir::Browser.attach(:url, 'http://www.google.com')
|
445
|
-
# Vapir::Browser.attach(:title, 'Google')
|
446
|
-
def self.attach how, what
|
447
|
-
new(:attach => [how, what])
|
448
|
-
end
|
600
|
+
private :attach
|
449
601
|
|
450
602
|
# loads up a new window in an existing process
|
451
603
|
# Vapir::Browser.attach() with no arguments passed the attach method will create a new window
|
452
604
|
# this will only be called one time per instance we're only ever going to run in 1 window
|
453
|
-
|
454
605
|
def open_window
|
455
606
|
begin
|
456
607
|
@browser_window_name="firewatir_window_%.16x"%rand(2**64)
|
457
|
-
end while
|
608
|
+
end while self.class.browser_window_objects.any?{|browser_window_object| browser_window_object.name == @browser_window_name }
|
458
609
|
watcher=jssh_socket.Components.classes["@mozilla.org/embedcomp/window-watcher;1"].getService(jssh_socket.Components.interfaces.nsIWindowWatcher)
|
459
610
|
# nsIWindowWatcher is used to launch new top-level windows. see https://developer.mozilla.org/en/Working_with_windows_in_chrome_code
|
460
611
|
|
@@ -463,47 +614,44 @@ module Vapir
|
|
463
614
|
end
|
464
615
|
private :open_window
|
465
616
|
|
466
|
-
|
467
|
-
|
468
|
-
|
617
|
+
class << self
|
618
|
+
def each_browser
|
619
|
+
each_browser_window_object do |win|
|
620
|
+
yield self.attach(:browser_window_object, win)
|
621
|
+
end
|
469
622
|
end
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
|
474
|
-
enumerator=mediator.getEnumerator("navigator:browser")
|
475
|
-
while enumerator.hasMoreElements
|
476
|
-
win=enumerator.getNext
|
477
|
-
yield win
|
623
|
+
alias each each_browser
|
624
|
+
def browsers
|
625
|
+
Enumerator.new(self, :each_browser)
|
478
626
|
end
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
627
|
+
def each_browser_window_object
|
628
|
+
mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
|
629
|
+
enumerator=mediator.getEnumerator("navigator:browser")
|
630
|
+
while enumerator.hasMoreElements
|
631
|
+
win=enumerator.getNext
|
632
|
+
yield win
|
633
|
+
end
|
634
|
+
nil
|
485
635
|
end
|
486
|
-
|
487
|
-
|
488
|
-
def self.each_window_object
|
489
|
-
mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
|
490
|
-
enumerator=mediator.getEnumerator(nil)
|
491
|
-
while enumerator.hasMoreElements
|
492
|
-
win=enumerator.getNext
|
493
|
-
yield win
|
636
|
+
def browser_window_objects
|
637
|
+
Enumerator.new(self, :each_browser_window_object)
|
494
638
|
end
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
639
|
+
def each_window_object
|
640
|
+
mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
|
641
|
+
enumerator=mediator.getEnumerator(nil)
|
642
|
+
while enumerator.hasMoreElements
|
643
|
+
win=enumerator.getNext
|
644
|
+
yield win
|
645
|
+
end
|
646
|
+
nil
|
647
|
+
end
|
648
|
+
def window_objects
|
649
|
+
Enumerator.new(self, :each_window_object)
|
501
650
|
end
|
502
|
-
window_objects
|
503
651
|
end
|
504
652
|
|
505
653
|
# return the window jssh object for the browser window with the given title or url.
|
506
|
-
# how - :url or :
|
654
|
+
# how - :url, :title, or :name
|
507
655
|
# what - string or regexp
|
508
656
|
#
|
509
657
|
# Start searching windows in reverse order so that we attach/find the latest opened window.
|
@@ -511,6 +659,7 @@ module Vapir
|
|
511
659
|
orig_how=how
|
512
660
|
hows={ :title => proc{|content_window| content_window.title },
|
513
661
|
:URL => proc{|content_window| content_window.location.href },
|
662
|
+
:name => proc{|content_window| content_window.name },
|
514
663
|
}
|
515
664
|
how=hows.keys.detect{|h| h.to_s.downcase==orig_how.to_s.downcase}
|
516
665
|
raise ArgumentError, "how should be one of: #{hows.keys.inspect} (was #{orig_how.inspect})" unless how
|
@@ -538,6 +687,16 @@ module Vapir
|
|
538
687
|
body_object.textContent
|
539
688
|
end
|
540
689
|
|
690
|
+
# the HTTP response status code for the currently loaded document
|
691
|
+
def response_status_code
|
692
|
+
channel = nil
|
693
|
+
::Waiter.try_for(8, :exception => nil) do
|
694
|
+
channel=browser.browser_object.docShell.currentDocumentChannel
|
695
|
+
channel.is_a?(JsshObject) && channel.instanceof(browser.jssh_socket.Components.interfaces.nsIHttpChannel) && channel.respond_to?(:responseStatus)
|
696
|
+
end || raise(RuntimeError, "expected currentDocumentChannel to exist and be a nsIHttpChannel but it wasn't; was #{channel.is_a?(JsshObject) ? channel.toString : channel.inspect}")
|
697
|
+
status = channel.responseStatus
|
698
|
+
end
|
699
|
+
|
541
700
|
# Maximize the current browser window.
|
542
701
|
def maximize()
|
543
702
|
browser_window_object.maximize
|
@@ -556,11 +715,8 @@ module Vapir
|
|
556
715
|
end
|
557
716
|
options={:sleep => false, :last_url => nil, :timeout => 120}.merge(options)
|
558
717
|
started=Time.now
|
559
|
-
|
560
|
-
|
561
|
-
if Time.now - started > options[:timeout]
|
562
|
-
raise "Page Load Timeout"
|
563
|
-
end
|
718
|
+
::Waiter.try_for(options[:timeout] - (Time.now - started), :exception => "Waiting for the document to finish loading timed out") do
|
719
|
+
browser_object.webProgress.isLoadingDocument==false
|
564
720
|
end
|
565
721
|
|
566
722
|
# If the redirect is to a download attachment that does not reload this page, this
|
@@ -569,12 +725,8 @@ module Vapir
|
|
569
725
|
url= document_object.URL
|
570
726
|
|
571
727
|
if(url != options[:last_url])
|
572
|
-
#
|
573
|
-
#
|
574
|
-
# If page redirects to itself that this code will enter in infinite loop.
|
575
|
-
# So we currently don't wait for such a page.
|
576
|
-
# wait variable in JSSh tells if we should wait more for the page to get loaded
|
577
|
-
# or continue. -1 means page is not redirected. Anyother positive values means wait.
|
728
|
+
# check for meta redirects, except for redirects back to the same page (infinite
|
729
|
+
# loop redirects).
|
578
730
|
metas=document_object.getElementsByTagName 'meta'
|
579
731
|
wait_time=metas.to_array.map do |meta|
|
580
732
|
return_time=true
|
@@ -601,6 +753,83 @@ module Vapir
|
|
601
753
|
return self
|
602
754
|
end
|
603
755
|
|
756
|
+
# saves a screenshot of this browser window to the given filename.
|
757
|
+
#
|
758
|
+
# the last argument is an optional options hash, taking options:
|
759
|
+
# - :dc => context to capture (stands for device context). default is :page. may be one of:
|
760
|
+
# - :page takes a screenshot of the full page, and none of the browser chrome. this is supported cross-platform.
|
761
|
+
# - :client takes a screenshot of the client area, which excludes the menu bar and other window trimmings.
|
762
|
+
# only supported on windows.
|
763
|
+
# - :window takes a screenshot of the full browser window. only supported on windows.
|
764
|
+
# - :desktop takes a screenshot of the full desktop. only supported on windows.
|
765
|
+
# - :format => a valid format. if :dc is :window, the default is 'png' ('jpeg' is also supported); if :dc is anything else, 'bmp' is both the
|
766
|
+
# default and the only supported format.
|
767
|
+
def screen_capture(filename, options = {})
|
768
|
+
options = handle_options(options, :format => nil, :dc => :page)
|
769
|
+
|
770
|
+
if options[:dc] == :page
|
771
|
+
options[:format] ||= 'png'
|
772
|
+
jssh_socket.call_function(:window => content_window_object, :options => options, :filename => File.expand_path(filename)) do
|
773
|
+
%q(
|
774
|
+
// this is adapted from Selenium's method Selenium.prototype.doCaptureEntirePageScreenshot
|
775
|
+
var document = window.document;
|
776
|
+
var document_element = document.documentElement;
|
777
|
+
var width = document_element.scrollWidth;
|
778
|
+
var height = document_element.scrollHeight;
|
779
|
+
var styleWidth = width.toString() + 'px';
|
780
|
+
var styleHeight = height.toString() + 'px';
|
781
|
+
|
782
|
+
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'html:canvas'), grabCanvas=canvas;
|
783
|
+
grabCanvas.style.display = 'none';
|
784
|
+
grabCanvas.width = width;
|
785
|
+
grabCanvas.style.width = styleWidth;
|
786
|
+
grabCanvas.style.maxWidth = styleWidth;
|
787
|
+
grabCanvas.height = height;
|
788
|
+
grabCanvas.style.height = styleHeight;
|
789
|
+
grabCanvas.style.maxHeight = styleHeight;
|
790
|
+
|
791
|
+
document_element.appendChild(canvas);
|
792
|
+
try
|
793
|
+
{
|
794
|
+
var context = canvas.getContext('2d');
|
795
|
+
context.clearRect(0, 0, width, height);
|
796
|
+
context.save();
|
797
|
+
|
798
|
+
var prefs=Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefBranch);
|
799
|
+
var background_color = prefs.getCharPref('browser.display.background_color');
|
800
|
+
|
801
|
+
context.drawWindow(window, 0, 0, width, height, background_color);
|
802
|
+
context.restore();
|
803
|
+
var dataUrl = canvas.toDataURL("image/" + options['format']);
|
804
|
+
|
805
|
+
var nsIoService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
|
806
|
+
var channel = nsIoService.newChannelFromURI(nsIoService.newURI(dataUrl, null, null));
|
807
|
+
var binaryInputStream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream);
|
808
|
+
binaryInputStream.setInputStream(channel.open());
|
809
|
+
var numBytes = binaryInputStream.available();
|
810
|
+
var bytes = binaryInputStream.readBytes(numBytes);
|
811
|
+
|
812
|
+
var nsFile = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
|
813
|
+
nsFile.initWithPath(filename);
|
814
|
+
var writeFlag = 0x02; // write only
|
815
|
+
var createFlag = 0x08; // create
|
816
|
+
var truncateFlag = 0x20; // truncate
|
817
|
+
var fileOutputStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
|
818
|
+
fileOutputStream.init(nsFile, writeFlag | createFlag | truncateFlag, 0664, null);
|
819
|
+
fileOutputStream.write(bytes, numBytes);
|
820
|
+
fileOutputStream.close();
|
821
|
+
document_element.removeChild(canvas);
|
822
|
+
}
|
823
|
+
catch(e)
|
824
|
+
{ document_element.removeChild(canvas);
|
825
|
+
}
|
826
|
+
)
|
827
|
+
end
|
828
|
+
else
|
829
|
+
screen_capture_win_window(filename, options)
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
604
833
|
# Add an error checker that gets called on every page load.
|
605
834
|
# * checker - a Proc object
|
606
835
|
def add_checker(checker)
|
@@ -640,22 +869,6 @@ module Vapir
|
|
640
869
|
path
|
641
870
|
end
|
642
871
|
|
643
|
-
def current_os
|
644
|
-
@current_os ||= begin
|
645
|
-
platform= RUBY_PLATFORM =~ /java/ ? java.lang.System.getProperty("os.name") : RUBY_PLATFORM
|
646
|
-
case platform
|
647
|
-
when /mswin|windows|mingw32/i
|
648
|
-
:windows
|
649
|
-
when /darwin|mac os/i
|
650
|
-
:macosx
|
651
|
-
when /linux/i
|
652
|
-
:linux
|
653
|
-
else
|
654
|
-
raise "Unidentified platform #{platform}"
|
655
|
-
end
|
656
|
-
end
|
657
|
-
end
|
658
|
-
|
659
872
|
def path_from_registry
|
660
873
|
raise NotImplementedError, "(need to know how to access windows registry on JRuby)" if RUBY_PLATFORM =~ /java/
|
661
874
|
require 'win32/registry'
|