vapir-ie 1.7.0.rc1
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/History.txt +437 -0
- data/LICENSE.txt +41 -0
- data/README.txt +0 -0
- data/lib/vapir-ie.rb +44 -0
- data/lib/vapir-ie/AutoItX.chm +0 -0
- data/lib/vapir-ie/AutoItX3.dll +0 -0
- data/lib/vapir-ie/IEDialog/Release/IEDialog.dll +0 -0
- data/lib/vapir-ie/autoit.rb +13 -0
- data/lib/vapir-ie/close_all.rb +32 -0
- data/lib/vapir-ie/container.rb +51 -0
- data/lib/vapir-ie/element.rb +376 -0
- data/lib/vapir-ie/elements.rb +9 -0
- data/lib/vapir-ie/form.rb +8 -0
- data/lib/vapir-ie/frame.rb +24 -0
- data/lib/vapir-ie/ie-class.rb +880 -0
- data/lib/vapir-ie/ie-process.rb +60 -0
- data/lib/vapir-ie/image.rb +59 -0
- data/lib/vapir-ie/input_elements.rb +158 -0
- data/lib/vapir-ie/link.rb +23 -0
- data/lib/vapir-ie/logger.rb +21 -0
- data/lib/vapir-ie/modal_dialog.rb +224 -0
- data/lib/vapir-ie/non_control_elements.rb +77 -0
- data/lib/vapir-ie/page_container.rb +203 -0
- data/lib/vapir-ie/process.rb +22 -0
- data/lib/vapir-ie/screen_capture.rb +7 -0
- data/lib/vapir-ie/scripts/select_file.rb +79 -0
- data/lib/vapir-ie/table.rb +33 -0
- data/lib/vapir-ie/version.rb +5 -0
- data/lib/vapir-ie/win32ole.rb +45 -0
- data/lib/vapir-ie/win32ole/win32ole.so +0 -0
- data/lib/vapir/ie.rb +1 -0
- metadata +130 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module Vapir
|
2
|
+
|
3
|
+
class IE::Pre < IE::Element
|
4
|
+
include Vapir::Pre
|
5
|
+
end
|
6
|
+
|
7
|
+
class IE::P < IE::Element
|
8
|
+
include Vapir::P
|
9
|
+
end
|
10
|
+
|
11
|
+
# this class is used to deal with Div tags in the html page. http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/div.asp?frame=true
|
12
|
+
# It would not normally be created by users
|
13
|
+
class IE::Div < IE::Element
|
14
|
+
include Vapir::Div
|
15
|
+
end
|
16
|
+
|
17
|
+
# this class is used to deal with Span tags in the html page. It would not normally be created by users
|
18
|
+
class IE::Span < IE::Element
|
19
|
+
include Vapir::Span
|
20
|
+
end
|
21
|
+
|
22
|
+
class IE::Map < IE::Element
|
23
|
+
include Vapir::Map
|
24
|
+
end
|
25
|
+
|
26
|
+
class IE::Area < IE::Element
|
27
|
+
include Vapir::Area
|
28
|
+
end
|
29
|
+
|
30
|
+
# Accesses Label element on the html page - http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/label.asp?frame=true
|
31
|
+
class IE::Label < IE::Element
|
32
|
+
include Vapir::Label
|
33
|
+
end
|
34
|
+
|
35
|
+
class IE::Li < IE::Element
|
36
|
+
include Vapir::Li
|
37
|
+
end
|
38
|
+
class IE::Ul < IE::Element
|
39
|
+
include Vapir::Ul
|
40
|
+
end
|
41
|
+
class IE::Ol < IE::Element
|
42
|
+
include Vapir::Ol
|
43
|
+
end
|
44
|
+
class IE::H1 < IE::Element
|
45
|
+
include Vapir::H1
|
46
|
+
end
|
47
|
+
class IE::H2 < IE::Element
|
48
|
+
include Vapir::H2
|
49
|
+
end
|
50
|
+
class IE::H3 < IE::Element
|
51
|
+
include Vapir::H3
|
52
|
+
end
|
53
|
+
class IE::H4 < IE::Element
|
54
|
+
include Vapir::H4
|
55
|
+
end
|
56
|
+
class IE::H5 < IE::Element
|
57
|
+
include Vapir::H5
|
58
|
+
end
|
59
|
+
class IE::H6 < IE::Element
|
60
|
+
include Vapir::H6
|
61
|
+
end
|
62
|
+
class IE::Dl < IE::Element
|
63
|
+
include Vapir::Dl
|
64
|
+
end
|
65
|
+
class IE::Dt < IE::Element
|
66
|
+
include Vapir::Dt
|
67
|
+
end
|
68
|
+
class IE::Dd < IE::Element
|
69
|
+
include Vapir::Dd
|
70
|
+
end
|
71
|
+
class IE::Strong < IE::Element
|
72
|
+
include Vapir::Strong
|
73
|
+
end
|
74
|
+
class IE::Em < IE::Element
|
75
|
+
include Vapir::Em
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'vapir-ie/container'
|
2
|
+
|
3
|
+
module Vapir
|
4
|
+
# A PageContainer contains an HTML Document. In other words, it is a
|
5
|
+
# what JavaScript calls a Window.
|
6
|
+
#
|
7
|
+
# this assumes that document_object is defined on the includer.
|
8
|
+
module IE::PageContainer
|
9
|
+
# Used internally to determine when IE has finished loading a page
|
10
|
+
# http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowserreadystate.aspx
|
11
|
+
# http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.readystate.aspx
|
12
|
+
module WebBrowserReadyState
|
13
|
+
Uninitialized = 0 # No document is currently loaded.
|
14
|
+
Loading = 1 # The control is loading a new document.
|
15
|
+
Loaded = 2 # The control has loaded and initialized the new document, but has not yet received all the document data.
|
16
|
+
Interactive = 3 # The control has loaded enough of the document to allow limited user interaction, such as clicking hyperlinks that have been displayed.
|
17
|
+
Complete = 4 # The control has finished loading the new document and all its contents.
|
18
|
+
end
|
19
|
+
READYSTATE_COMPLETE = WebBrowserReadyState::Complete
|
20
|
+
|
21
|
+
def containing_object
|
22
|
+
document_object
|
23
|
+
end
|
24
|
+
include IE::Container
|
25
|
+
include Vapir::Exception
|
26
|
+
|
27
|
+
# This method checks the currently displayed page for http errors, 404, 500 etc
|
28
|
+
# It gets called internally by the wait method, so a user does not need to call it explicitly
|
29
|
+
def check_for_http_error
|
30
|
+
# check for IE7
|
31
|
+
n = self.document.invoke('parentWindow').navigator.appVersion
|
32
|
+
m=/MSIE\s(.*?);/.match( n )
|
33
|
+
if m and m[1] =='7.0'
|
34
|
+
if m = /HTTP (\d\d\d.*)/.match( self.title )
|
35
|
+
raise NavigationException, m[1]
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# assume its IE6
|
39
|
+
url = self.document.location.href
|
40
|
+
if /shdoclc.dll/.match(url)
|
41
|
+
m = /id=IEText.*?>(.*?)</i.match(self.html)
|
42
|
+
raise NavigationException, m[1] if m
|
43
|
+
end
|
44
|
+
end
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def document_element
|
49
|
+
document_object.documentElement
|
50
|
+
end
|
51
|
+
def content_window_object
|
52
|
+
document_object.parentWindow
|
53
|
+
end
|
54
|
+
|
55
|
+
def page_container
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# The HTML of the current page
|
60
|
+
def html
|
61
|
+
document_element.outerHTML
|
62
|
+
end
|
63
|
+
|
64
|
+
# The url of the page object.
|
65
|
+
def url
|
66
|
+
document_object.location.href
|
67
|
+
end
|
68
|
+
|
69
|
+
# The text of the current page
|
70
|
+
def text
|
71
|
+
document_element.innerText
|
72
|
+
end
|
73
|
+
|
74
|
+
def close
|
75
|
+
content_window_object.close
|
76
|
+
end
|
77
|
+
|
78
|
+
def title
|
79
|
+
document_object.title
|
80
|
+
end
|
81
|
+
|
82
|
+
# Execute the given JavaScript string
|
83
|
+
def execute_script(source)
|
84
|
+
retried=false
|
85
|
+
result=nil
|
86
|
+
begin
|
87
|
+
result=document_object.parentWindow.eval(source)
|
88
|
+
rescue WIN32OLERuntimeError
|
89
|
+
# don't retry more than once; don't catch anything but the particular thing we're looking for
|
90
|
+
if retried || $!.message !~ /unknown property or method:? `eval'/
|
91
|
+
raise
|
92
|
+
end
|
93
|
+
# this can happen if no scripts have executed at all - the 'eval' function doesn't exist.
|
94
|
+
# execScript works, but behaves differently than eval (it doesn't return anything) - but
|
95
|
+
# once an execScript has run, eval is subsequently defined. so, execScript a blank script,
|
96
|
+
# and then try again with eval.
|
97
|
+
document.parentWindow.execScript('null')
|
98
|
+
retried=true
|
99
|
+
retry
|
100
|
+
end
|
101
|
+
return result
|
102
|
+
end
|
103
|
+
|
104
|
+
# Block execution until the page has loaded.
|
105
|
+
# =nodoc
|
106
|
+
# Note: This code needs to be prepared for the ie object to be closed at
|
107
|
+
# any moment!
|
108
|
+
def wait(options={})
|
109
|
+
unless options.is_a?(Hash)
|
110
|
+
raise ArgumentError, "given options should be a Hash, not #{options.inspect} (#{options.class})\nold conflicting arguments of no_sleep or last_url are gone"
|
111
|
+
end
|
112
|
+
options={:sleep => false, :interval => 0.1, :timeout => 120}.merge(options)
|
113
|
+
@xml_parser_doc = nil
|
114
|
+
@down_load_time = nil
|
115
|
+
start_load_time = Time.now
|
116
|
+
|
117
|
+
if respond_to?(:browser_object)
|
118
|
+
::Waiter.try_for(options[:timeout]-(Time.now-start_load_time), :interval => options[:interval], :exception => "The browser was still busy at the end of the specified interval") do
|
119
|
+
return unless exists?
|
120
|
+
!browser_object.busy
|
121
|
+
end
|
122
|
+
::Waiter.try_for(options[:timeout]-(Time.now-start_load_time), :interval => options[:interval], :exception => "The browser's readyState was still not ready for interaction at the end of the specified interval") do
|
123
|
+
return unless exists?
|
124
|
+
[WebBrowserReadyState::Interactive, WebBrowserReadyState::Complete].include?(browser_object.readyState)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
::Waiter.try_for(options[:timeout]-(Time.now-start_load_time), :interval => options[:interval], :exception => "The browser's document was still not defined at the end of the specified interval") do
|
128
|
+
return unless exists?
|
129
|
+
document_object
|
130
|
+
end
|
131
|
+
urls=[]
|
132
|
+
all_frames_complete_result=::Waiter.try_for(options[:timeout]-(Time.now-start_load_time), :interval => options[:interval], :exception => nil, :condition => proc{|result| result==true }) do
|
133
|
+
return unless exists?
|
134
|
+
all_frames_complete?(document_object, urls)
|
135
|
+
end
|
136
|
+
case all_frames_complete_result
|
137
|
+
when false
|
138
|
+
raise "A frame on the browser did not come into readyState complete by the end of the specified interval"
|
139
|
+
when Exception
|
140
|
+
message = "A frame on the browser encountered an error.\n"
|
141
|
+
if all_frames_complete_result.message =~ /0x80070005/
|
142
|
+
message += "An 'Access is denied' error might be fixed by adding the domain of the site to your 'Trusted Sites'.\n"
|
143
|
+
end
|
144
|
+
message+="Original message was:\n\n"
|
145
|
+
message+=all_frames_complete_result.message
|
146
|
+
raise all_frames_complete_result.class, message, all_frames_complete_result.backtrace
|
147
|
+
when true
|
148
|
+
# dandy; carry on.
|
149
|
+
else
|
150
|
+
# this should never happen.
|
151
|
+
raise "Unexpected result from all_frames_complete?: #{all_frames_complete_result.inspect}"
|
152
|
+
end
|
153
|
+
@url_list=(@url_list || [])+urls
|
154
|
+
|
155
|
+
@down_load_time= Time.now - start_load_time
|
156
|
+
run_error_checks if respond_to?(:run_error_checks)
|
157
|
+
sleep @pause_after_wait if options[:sleep]
|
158
|
+
@down_load_time
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
# this returns true if all frames are complete.
|
163
|
+
# it returns false if a frame is incomplete.
|
164
|
+
# if an unexpected exception is encountered, it returns that exception. yes, returns, not raises,
|
165
|
+
# due to the fact that an exception may indicate either the frame not being complete, or an actual
|
166
|
+
# error condition - it is difficult to differentiate. in the usage above, in #wait, we check
|
167
|
+
# if an exception is still being raised at the end of the specified interval, and raise it if so.
|
168
|
+
# if it stops being raised, we carry on.
|
169
|
+
def all_frames_complete?(document, urls=nil)
|
170
|
+
begin
|
171
|
+
if urls && !urls.include?(document.location.href)
|
172
|
+
urls << document.location.href
|
173
|
+
end
|
174
|
+
frames=document.frames
|
175
|
+
return document.readyState=='complete' && (0...frames.length).all? do |i|
|
176
|
+
frame=document.frames[i.to_s]
|
177
|
+
frame_document=begin
|
178
|
+
frame.document
|
179
|
+
rescue WIN32OLERuntimeError
|
180
|
+
$!
|
181
|
+
end
|
182
|
+
case frame_document
|
183
|
+
when nil
|
184
|
+
# frame hasn't loaded to the point where it has a document yet
|
185
|
+
false
|
186
|
+
when WIN32OLE
|
187
|
+
# frame has a document - check recursively
|
188
|
+
all_frames_complete?(frame_document, urls)
|
189
|
+
when WIN32OLERuntimeError
|
190
|
+
# if we get a WIN32OLERuntimeError with access denied, that is probably a 404 and it's not going
|
191
|
+
# to load, so no reason to keep waiting for it - consider it 'complete' and return true.
|
192
|
+
# there's probably a better method of determining this but I haven't found it yet.
|
193
|
+
true
|
194
|
+
else # don't know what we'd have here
|
195
|
+
raise RuntimeError, "unknown frame.document: #{frame_document.inspect} (#{frame_document.class})"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
rescue WIN32OLERuntimeError
|
199
|
+
return $!
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end # module
|
203
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'vapir-ie/ie-class'
|
2
|
+
|
3
|
+
module Vapir
|
4
|
+
module Process
|
5
|
+
|
6
|
+
# Returns the number of windows processes running with the specified name.
|
7
|
+
def self.count name
|
8
|
+
mgmt = WIN32OLE.connect('winmgmts:\\\\.')
|
9
|
+
processes = mgmt.InstancesOf('win32_process')
|
10
|
+
processes.extend Enumerable
|
11
|
+
processes.select{|x| x.name == name}.length
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class IE
|
17
|
+
# Returns the number of IEXPLORE processes currently running.
|
18
|
+
def self.process_count
|
19
|
+
Vapir::Process.count 'iexplore.exe'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
at_exit do
|
2
|
+
if $!
|
3
|
+
err={:class => $!.class, :message => $!.message, :backtrace => $!.backtrace}
|
4
|
+
if $upload_dialog && $upload_dialog.exists?
|
5
|
+
# if the upload dialog still exists, we need to close it so that the #click WIN32OLE call in the parent process can return.
|
6
|
+
if (upload_dialog_popup=$upload_dialog.enabled_popup)
|
7
|
+
# we will assume this popup is of the "The above file name is invalid" variety, so click 'OK'
|
8
|
+
contents=begin
|
9
|
+
upload_dialog_popup.children.select{|child| child.class_name=='Static' && child.text!=''}.map{|child| child.text}.join(' ')
|
10
|
+
rescue WinWindow::Error
|
11
|
+
"{Unable to retrieve contents}"
|
12
|
+
end
|
13
|
+
err[:message]+="\n\nA popup was found on the dialog with title: \n#{upload_dialog_popup.text.inspect}\nand text contents: \n#{contents}"
|
14
|
+
if upload_dialog_popup.click_child_button_try_for!('OK', 4, :exception => nil)
|
15
|
+
err[:message]+="\n\nClicked 'OK' on the popup."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
# once the popup is gone (or if there wasn't one - this happens if the field was set to blank, or set to a directory that exists)
|
19
|
+
# then click 'cancel'
|
20
|
+
if $upload_dialog.click_child_button_try_for!('Cancel', 4, :exception => nil)
|
21
|
+
err[:message]+="\n\nClicked the 'Cancel' button instead of 'Open' on the File Upload dialog."
|
22
|
+
end
|
23
|
+
|
24
|
+
# none of the above are expected to error, but maybe should be in a begin/rescue just in case?
|
25
|
+
end
|
26
|
+
if $error_file_name && $error_file_name != ''
|
27
|
+
File.open($error_file_name, 'w') do |error_file|
|
28
|
+
to_write=Marshal.dump(err)
|
29
|
+
error_file.write(to_write)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
else
|
33
|
+
if $error_file_name && $error_file_name != '' && File.exists?($error_file_name)
|
34
|
+
File.delete($error_file_name)
|
35
|
+
# the file not existing indicates success.
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'vapir-common', 'lib'))
|
41
|
+
|
42
|
+
require 'vapir-common/win_window'
|
43
|
+
require 'vapir-common/waiter'
|
44
|
+
require 'vapir-common/exceptions'
|
45
|
+
|
46
|
+
browser_hwnd, file_path, $error_file_name=*ARGV
|
47
|
+
unless (2..3).include?(ARGV.size) && browser_hwnd =~ /^\d+$/ && browser_hwnd.to_i > 0
|
48
|
+
raise ArgumentError, "This script takes two or three arguments: the hWnd that the File Selection dialog will pop up on (positive integer); the path to the file to select; and (optional) a filename to write any failure message to."
|
49
|
+
end
|
50
|
+
|
51
|
+
# titles of file upload window titles in supported browsers
|
52
|
+
# Add to this titles in other languages, too
|
53
|
+
UploadWindowTitles= { :IE8 => "Choose File to Upload",
|
54
|
+
:IE7 => 'Choose file',
|
55
|
+
}
|
56
|
+
# list of arguments to give to WinWindow#child_control_with_preceding_label to find the filename field
|
57
|
+
# on dialogs of supported browsers (just the one right now because it's the same in ie7 and ie8)
|
58
|
+
# add to this stuff for other languages, too
|
59
|
+
UploadWindowFilenameFields = [["File &name:", {:control_class_name => 'ComboBoxEx32'}]]
|
60
|
+
|
61
|
+
browser_window=WinWindow.new(browser_hwnd.to_i)
|
62
|
+
|
63
|
+
popup=nil
|
64
|
+
$upload_dialog=::Waiter.try_for(16, :exception => nil) do
|
65
|
+
if (popup=browser_window.enabled_popup) && UploadWindowTitles.values.include?(popup.text)
|
66
|
+
popup
|
67
|
+
end
|
68
|
+
end
|
69
|
+
unless $upload_dialog
|
70
|
+
raise Vapir::Exception::NoMatchingWindowFoundException.new('No window found to upload a file - '+(popup ? "enabled popup exists but has unrecognized text #{popup.text}" : 'no popup is on the browser'))
|
71
|
+
end
|
72
|
+
filename_fields=UploadWindowFilenameFields.map do |control_args|
|
73
|
+
$upload_dialog.child_control_with_preceding_label(*control_args)
|
74
|
+
end
|
75
|
+
unless (filename_field=filename_fields.compact.first)
|
76
|
+
raise Vapir::Exception::NoMatchingWindowFoundException, "Could not find a filename field in the File Upload dialog"
|
77
|
+
end
|
78
|
+
filename_field.send_set_text! file_path
|
79
|
+
$upload_dialog.click_child_button_try_for!('Open', 4, :exception => WinWindow::Error.new("Failed to click the Open button on the File Upload dialog. It exists, but we couldn't click it."))
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'vapir-ie/element'
|
2
|
+
require 'vapir-common/elements/elements'
|
3
|
+
|
4
|
+
module Vapir
|
5
|
+
|
6
|
+
# This class is used for dealing with tables.
|
7
|
+
# Normally a user would not need to create this object as it is returned by the Vapir::Container#table method
|
8
|
+
#
|
9
|
+
# many of the methods available to this object are inherited from the Element class
|
10
|
+
#
|
11
|
+
class IE::Table < IE::Element
|
12
|
+
include Vapir::Table
|
13
|
+
|
14
|
+
def self.create_from_element(container, element)
|
15
|
+
Vapir::Table.create_from_element(container, element)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# this class is a table body
|
20
|
+
class IE::TableBody < IE::Element
|
21
|
+
include Vapir::TBody
|
22
|
+
end
|
23
|
+
|
24
|
+
class IE::TableRow < IE::Element
|
25
|
+
include Vapir::TableRow
|
26
|
+
end
|
27
|
+
|
28
|
+
# this class is a table cell - when called via the Table object
|
29
|
+
class IE::TableCell < IE::Element
|
30
|
+
include Vapir::TableCell
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# load the correct version of win32ole
|
2
|
+
|
3
|
+
# Use our modified win32ole library
|
4
|
+
|
5
|
+
if RUBY_VERSION =~ /^1\.8/
|
6
|
+
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'vapir-ie', 'win32ole'))
|
7
|
+
else
|
8
|
+
# loading win32ole from stdlib on 1.9
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
require 'win32ole'
|
13
|
+
|
14
|
+
WIN32OLE.codepage = WIN32OLE::CP_UTF8
|
15
|
+
|
16
|
+
# necessary extension of win32ole
|
17
|
+
class WIN32OLE
|
18
|
+
def respond_to?(method)
|
19
|
+
super || object_respond_to?(method)
|
20
|
+
end
|
21
|
+
|
22
|
+
# checks if WIN32OLE#ole_method returns an WIN32OLE_METHOD, or errors.
|
23
|
+
# WARNING: #ole_method is pretty slow, and consequently so is this. you are likely to be better
|
24
|
+
# off just calling a method you are not sure exists, and rescuing the WIN32OLERuntimeError
|
25
|
+
# that is raised if it doesn't exist.
|
26
|
+
def object_respond_to?(method)
|
27
|
+
method=method.to_s
|
28
|
+
# strip assignment = from methods. going to assume that if it has a getter method, it will take assignment, too. this may not be correct, but will have to do.
|
29
|
+
if method =~ /=\z/
|
30
|
+
method=$`
|
31
|
+
end
|
32
|
+
respond_to_cache[method]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def respond_to_cache
|
37
|
+
@respond_to_cache||=Hash.new do |hash, key|
|
38
|
+
hash[key]=begin
|
39
|
+
!!self.ole_method(key)
|
40
|
+
rescue WIN32OLERuntimeError
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|