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
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'vapir-firefox/browser.rb'
|
2
|
+
|
3
|
+
module Vapir
|
4
|
+
class Firefox
|
5
|
+
module ClearTracksMethods
|
6
|
+
#--
|
7
|
+
#
|
8
|
+
# currently defined sanitizer items are:
|
9
|
+
# ["cache", "cookies", "offlineApps", "history", "formdata", "downloads", "passwords", "sessions", "siteSettings"]
|
10
|
+
def sanitizer # :nodoc:
|
11
|
+
@@sanitizer ||= begin
|
12
|
+
sanitizer_class = jssh_socket.object('Sanitizer')
|
13
|
+
if sanitizer_class.type=='undefined'
|
14
|
+
loader = jssh_socket.Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(jssh_socket.Components.interfaces.mozIJSSubScriptLoader)
|
15
|
+
loader.loadSubScript("chrome://browser/content/sanitize.js")
|
16
|
+
sanitizer_class = jssh_socket.object('Sanitizer')
|
17
|
+
end
|
18
|
+
sanitizer_class.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
#
|
22
|
+
def clear_history
|
23
|
+
sanitizer.items.history.clear()
|
24
|
+
end
|
25
|
+
def clear_cookies
|
26
|
+
sanitizer.items.cookies.clear()
|
27
|
+
#cookie_manager = jssh_socket.Components.classes["@mozilla.org/cookiemanager;1"].getService(jssh_socket.Components.interfaces.nsICookieManager)
|
28
|
+
#cookie_manager.removeAll()
|
29
|
+
end
|
30
|
+
def clear_cache
|
31
|
+
sanitizer.items.cache.clear
|
32
|
+
end
|
33
|
+
alias clear_temporary_files clear_cache
|
34
|
+
def clear_all_tracks
|
35
|
+
sanitizer.items.to_hash.inject({}) do |hash, (key, item)|
|
36
|
+
# don't try to clear siteSettings; sometimes siteSettings.clear() raises
|
37
|
+
# an error which jssh doesn't handle properly - it somehow bypasses the
|
38
|
+
# try/catch block and shows up on the socket outside of the returned value.
|
39
|
+
# jssh bug?
|
40
|
+
if key!='siteSettings' && hash[key].canClear
|
41
|
+
hash[key]=(item.clear() rescue $!)
|
42
|
+
end
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
include ClearTracksMethods
|
48
|
+
extend ClearTracksMethods
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'vapir-common/config'
|
2
|
+
require 'vapir-firefox' # need the class to be set first
|
3
|
+
|
4
|
+
module Vapir
|
5
|
+
# if vapir-firefox is required before any other browser-specific library, then set the default browser to firefox
|
6
|
+
@base_configuration.default_browser = :firefox unless @base_configuration.locally_defined_key?(:default_browser)
|
7
|
+
|
8
|
+
# add firefox-specific stuff to base, and then bring them in from env and yaml
|
9
|
+
@base_configuration.create(:firefox_profile)
|
10
|
+
@base_configuration.create(:firefox_binary_path)
|
11
|
+
@base_configuration.create_update(:firefox_quit_sleep_time, 4, :validator => :numeric)
|
12
|
+
@configurations.update_from_source
|
13
|
+
class Firefox
|
14
|
+
@configuration_parent = Vapir.config
|
15
|
+
extend Configurable
|
16
|
+
end
|
17
|
+
end
|
@@ -44,7 +44,7 @@ module Vapir
|
|
44
44
|
include Vapir::Container
|
45
45
|
|
46
46
|
def extra_for_contained
|
47
|
-
|
47
|
+
base_extra_for_contained.merge(:jssh_socket => jssh_socket)
|
48
48
|
end
|
49
49
|
|
50
50
|
public
|
@@ -79,5 +79,71 @@ module Vapir
|
|
79
79
|
base_element_class.factory(element_object, extra_for_contained)
|
80
80
|
end
|
81
81
|
end
|
82
|
+
|
83
|
+
# returns a JsshObject representing an array (in javascript) of the visible text nodes of this container. same as
|
84
|
+
# the Vapir::Common #visible_text_nodes implementation, but much much faster.
|
85
|
+
def visible_text_nodes
|
86
|
+
text_nodes = jssh_socket.call_function(:element_object => containing_object, :document_object => document_object) do %Q(
|
87
|
+
var Ycomb = function(gen){ return function(f){ return f(f); }(function(f){ return gen(function(){ return f(f).apply(null, arguments); }); }); }; // TODO: move this somewhere better - jssh_socket?
|
88
|
+
var recurse_text_nodes = Ycomb(function(recurse)
|
89
|
+
{ return function(node, parent_visibility)
|
90
|
+
{ if(node.nodeType==1 || node.nodeType==9)
|
91
|
+
{ var style = node.nodeType==1 ? document_object.defaultView.getComputedStyle(node, null) : null;
|
92
|
+
var our_visibility = style && style.visibility;
|
93
|
+
if(!(our_visibility && $A(['hidden', 'collapse', 'visible']).include(our_visibility.toLowerCase())))
|
94
|
+
{ our_visibility = parent_visibility;
|
95
|
+
}
|
96
|
+
var display = style && style.display;
|
97
|
+
if(display && display.toLowerCase()=='none')
|
98
|
+
{ return [];
|
99
|
+
}
|
100
|
+
else
|
101
|
+
{ return $A(node.childNodes).inject([], function(result, child_node)
|
102
|
+
{ return result.concat(recurse(child_node, our_visibility));
|
103
|
+
});
|
104
|
+
}
|
105
|
+
}
|
106
|
+
else if(node.nodeType==3)
|
107
|
+
{ if(parent_visibility && $A(['hidden', 'collapse']).include(parent_visibility.toLowerCase()))
|
108
|
+
{ return [];
|
109
|
+
}
|
110
|
+
else
|
111
|
+
{ return [node.data];
|
112
|
+
}
|
113
|
+
}
|
114
|
+
else
|
115
|
+
{ return [];
|
116
|
+
}
|
117
|
+
};
|
118
|
+
});
|
119
|
+
var element_to_check = element_object;
|
120
|
+
var real_visibility = null;
|
121
|
+
while(element_to_check)
|
122
|
+
{ var style = element_to_check.nodeType==1 ? document_object.defaultView.getComputedStyle(element_object, null) : null;
|
123
|
+
if(style)
|
124
|
+
{ // only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
|
125
|
+
// or 'visible'. ignore 'inherit'; keep looking upward.
|
126
|
+
// this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
|
127
|
+
// this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
|
128
|
+
if(real_visibility==null && (visibility=style.visibility))
|
129
|
+
{ var visibility=visibility.toLowerCase();
|
130
|
+
if($A(['hidden', 'collapse', 'visible']).include(visibility))
|
131
|
+
{ real_visibility=visibility;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
// check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
|
135
|
+
var display=style.display;
|
136
|
+
if(display && (display=display.toLowerCase())=='none')
|
137
|
+
{ // if display is none, then this element is not visible, and thus has no visible text nodes underneath.
|
138
|
+
return [];
|
139
|
+
}
|
140
|
+
}
|
141
|
+
element_to_check=element_to_check.parentNode;
|
142
|
+
}
|
143
|
+
return recurse_text_nodes(element_object, real_visibility);
|
144
|
+
)
|
145
|
+
end.to_array
|
146
|
+
end
|
147
|
+
|
82
148
|
end
|
83
149
|
end # module
|
@@ -23,15 +23,10 @@ module Vapir
|
|
23
23
|
attr_reader :jssh_socket
|
24
24
|
|
25
25
|
def outer_html
|
26
|
-
orig_siblings_length = (parentNode = element_object.parentNode) && parentNode.childNodes.length
|
27
|
-
|
28
26
|
temp_parent_element=document_object.createElement('div')
|
29
27
|
temp_parent_element.appendChild(element_object.cloneNode(true))
|
30
28
|
self_outer_html=temp_parent_element.innerHTML
|
31
29
|
|
32
|
-
new_siblings_length = (parentNode = element_object.parentNode) && parentNode.childNodes.length
|
33
|
-
#debug code:
|
34
|
-
raise "the parent somehow changed - had #{orig_siblings_length} children; now has #{new_siblings_length}" unless orig_siblings_length==new_siblings_length
|
35
30
|
return self_outer_html
|
36
31
|
end
|
37
32
|
alias outerHTML outer_html
|
@@ -70,8 +65,7 @@ raise "the parent somehow changed - had #{orig_siblings_length} children; now ha
|
|
70
65
|
event=create_event_object(event_type, options)
|
71
66
|
if !options[:wait]
|
72
67
|
raise "need a content window on which to setTimeout if we are not waiting" unless content_window_object
|
73
|
-
|
74
|
-
content_window_object.setTimeout(fire_event_func, 0)
|
68
|
+
content_window_object.setTimeout(jssh_socket.call_function(:element_object => element_object, :event => event){ "return function(){ element_object.dispatchEvent(event) };" }, 0)
|
75
69
|
nil
|
76
70
|
else
|
77
71
|
result=element_object.dispatchEvent(event)
|
@@ -116,12 +110,12 @@ raise "the parent somehow changed - had #{orig_siblings_length} children; now ha
|
|
116
110
|
[:bubbles, true],
|
117
111
|
[:cancelable, true],
|
118
112
|
[:windowObject, content_window_object],
|
119
|
-
[:ctrlKey, false],
|
120
|
-
[:altKey, false],
|
121
|
-
[:shiftKey, false],
|
122
|
-
[:metaKey, false],
|
123
|
-
[:keyCode, 0],
|
124
|
-
[:charCode, 0],
|
113
|
+
[:ctrlKey, options[:ctrlKey] || false],
|
114
|
+
[:altKey, options[:altKey] || false],
|
115
|
+
[:shiftKey, options[:shiftKey] || false],
|
116
|
+
[:metaKey, options[:metaKey] || false],
|
117
|
+
[:keyCode, options[:keyCode] || 0],
|
118
|
+
[:charCode, options[:charCode] || 0],
|
125
119
|
]
|
126
120
|
when 'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup'
|
127
121
|
dom_event_type = 'MouseEvents'
|
@@ -137,10 +131,10 @@ raise "the parent somehow changed - had #{orig_siblings_length} children; now ha
|
|
137
131
|
[:screenY, options[:screenY] || 0],
|
138
132
|
[:clientX, options[:clientX] || client_center[0]], # by default, assume the mouse is at the center of the element
|
139
133
|
[:clientY, options[:clientY] || client_center[1]],
|
140
|
-
[:ctrlKey, false],
|
141
|
-
[:altKey, false],
|
142
|
-
[:shiftKey, false],
|
143
|
-
[:metaKey, false],
|
134
|
+
[:ctrlKey, options[:ctrlKey] || false],
|
135
|
+
[:altKey, options[:altKey] || false],
|
136
|
+
[:shiftKey, options[:shiftKey] || false],
|
137
|
+
[:metaKey, options[:metaKey] || false],
|
144
138
|
[:button, MouseButtonCodes[options[:button] || :left]],
|
145
139
|
[:relatedTarget, nil],
|
146
140
|
]
|
@@ -186,14 +180,13 @@ raise "the parent somehow changed - had #{orig_siblings_length} children; now ha
|
|
186
180
|
mouse_down_event=create_event_object('mousedown', options)
|
187
181
|
mouse_up_event=create_event_object('mouseup', options)
|
188
182
|
click_event=create_event_object('click', options)
|
189
|
-
|
190
|
-
|
183
|
+
content_window_object.setTimeout(jssh_socket.call_function(:element_object => element_object, :mouse_down_event => mouse_down_event, :mouse_up_event => mouse_up_event, :click_event => click_event) do
|
184
|
+
" return function()
|
191
185
|
{ element_object.dispatchEvent(mouse_down_event);
|
192
186
|
element_object.dispatchEvent(mouse_up_event);
|
193
187
|
element_object.dispatchEvent(click_event);
|
194
|
-
};
|
195
|
-
|
196
|
-
content_window_object.setTimeout(click_func, 0)
|
188
|
+
};"
|
189
|
+
end, 0)
|
197
190
|
end
|
198
191
|
end
|
199
192
|
result
|
@@ -205,11 +198,51 @@ raise "the parent somehow changed - had #{orig_siblings_length} children; now ha
|
|
205
198
|
def click_no_wait(options={})
|
206
199
|
click(options.merge(:wait => false))
|
207
200
|
end
|
208
|
-
|
201
|
+
|
209
202
|
# Waits for the browser to finish loading, if it is loading. See Firefox#wait.
|
210
203
|
def wait(options={})
|
211
204
|
@container.wait(options)
|
212
205
|
end
|
206
|
+
|
207
|
+
# Checks this element and its parents for display: none or visibility: hidden, these are
|
208
|
+
# the most common methods to hide an html element. Returns false if this seems to be hidden
|
209
|
+
# or a parent is hidden.
|
210
|
+
def visible?
|
211
|
+
assert_exists do
|
212
|
+
jssh_socket.call_function(:element_to_check => element_object, :document_object => document_object) do %Q(
|
213
|
+
var really_visible=null;
|
214
|
+
while(element_to_check) //&& !(element_to_check instanceof Components.interfaces.nsIDOMDocument)
|
215
|
+
{ var style = element_to_check.nodeType==1 ? document_object.defaultView.getComputedStyle(element_to_check, null) : null;
|
216
|
+
if(style)
|
217
|
+
{ // only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
|
218
|
+
// or 'visible'. ignore 'inherit'; keep looking upward.
|
219
|
+
// this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
|
220
|
+
// this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
|
221
|
+
var visibility=style && style.visibility;
|
222
|
+
if(really_visible==null && visibility)
|
223
|
+
{ visibility=visibility.toLowerCase();
|
224
|
+
if(visibility=='hidden' || visibility=='collapse')
|
225
|
+
{ really_visible=false;
|
226
|
+
return false; // don't need to continue knowing it's not visible.
|
227
|
+
}
|
228
|
+
else if(visibility=='visible')
|
229
|
+
{ really_visible=true; // we don't return true yet because a parent with display of 'none' can override
|
230
|
+
}
|
231
|
+
}
|
232
|
+
// check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
|
233
|
+
var display=style && style.display;
|
234
|
+
if(display && display.toLowerCase()=='none')
|
235
|
+
{ return false;
|
236
|
+
}
|
237
|
+
}
|
238
|
+
element_to_check=element_to_check.parentNode;
|
239
|
+
}
|
240
|
+
return true;
|
241
|
+
)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
213
246
|
|
214
247
|
def self.element_object_style(element_object, document_object)
|
215
248
|
if element_object.nodeType==1 #element_object.instanceof(element_object.jssh_socket.Components.interfaces.nsIDOMDocument)
|
@@ -224,27 +257,18 @@ raise "the parent somehow changed - had #{orig_siblings_length} children; now ha
|
|
224
257
|
|
225
258
|
private
|
226
259
|
def element_object_exists?
|
227
|
-
# parent=@element_object
|
228
|
-
# while true
|
229
|
-
# return false unless parent # if we encounter a parent such that parentNode is nil, we aren't on the document.
|
230
|
-
# return true if parent==document_object # if we encounter the document as a parent, we are on the document.
|
231
|
-
# new_parent=parent.parentNode
|
232
|
-
# raise(RuntimeError, "Circular reference in parents!") if new_parent==parent
|
233
|
-
# parent=new_parent
|
234
|
-
# end
|
235
|
-
# above is horrendously slow; optimized below.
|
236
260
|
return false unless @element_object
|
237
|
-
return jssh_socket.
|
238
|
-
|
261
|
+
return jssh_socket.call_function(:parent => @element_object, :document_object => container.document_object) do # use the container's document so that frames look at their parent document, not their own document
|
262
|
+
" while(true)
|
239
263
|
{ if(!parent)
|
240
|
-
{ return false;
|
264
|
+
{ return false; // if we encounter a parent such that parentNode is nil, we aren't on the document.
|
241
265
|
}
|
242
|
-
if(parent==document_object)
|
266
|
+
if(parent==document_object) // if we encounter the document as a parent, we are on the document.
|
243
267
|
{ return true;
|
244
268
|
}
|
245
269
|
parent=parent.parentNode;
|
246
|
-
}
|
247
|
-
|
270
|
+
}"
|
271
|
+
end
|
248
272
|
end
|
249
273
|
|
250
274
|
end # Element
|