vapir-firefox 1.7.2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|