minibidi 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 607b907a85631222aca0cf29e33cdcc76af704464e8a64ee19b9e2da1b6c3b75
4
+ data.tar.gz: 527e055f3473a2c08f26157d466f2759a210476e97e63d341f90e91b7053315a
5
+ SHA512:
6
+ metadata.gz: 2a470c291ead662299187dff8f4cf4b3eaffa7308ad3ad15adee38ce39fa5112d93941ceabd120a7e7d47bd2153a36d449c18935364d90f346b5fac5def8ac3f
7
+ data.tar.gz: 6ca0eff8038b65628ebf1e66040c5ed7d6b4ffbcd997084e018feeb8911974295e13c61d0d79652b74b20e54dd11ca60df332e992ece59c6231fe3265a4c3030
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in minibidi.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 YusukeIwaki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # minibidi: Mini WebDriver BiDi binding for Ruby
2
+
3
+ ```ruby
4
+ require 'minibidi'
5
+
6
+ Minibidi::Firefox.launch do |browser|
7
+ context = browser.create_browsing_context
8
+ context.navigate("https://github.com/YusukeIwaki")
9
+ data = context.capture_screenshot(origin: :viewport, format: { type: :png })
10
+
11
+ open('YusukeIwaki.png', 'wb') do |f|
12
+ f.write(data)
13
+ end
14
+ end
15
+ ```
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'minibidi'
23
+ ```
24
+
25
+ And then execute `bundle install`
26
+
27
+ ## License
28
+
29
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "lib"
6
+ t.test_files = FileList["test/**/*_test.rb"]
7
+ end
data/bin/console ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "minibidi"
5
+ require "pry"
6
+ Pry.start
@@ -0,0 +1,131 @@
1
+ require 'async'
2
+ require 'async/variable'
3
+
4
+ module Minibidi
5
+ class Browser
6
+ def initialize(async_websocket_connection)
7
+ @websocket = async_websocket_connection
8
+ @debug_protocol = %w[1 true].include?(ENV['DEBUG'])
9
+
10
+ Async do
11
+ while data = async_websocket_connection.read
12
+ if message = Protocol::WebSocket::JSONMessage.wrap(data)
13
+ handle_received_message_from_websocket(message.to_h)
14
+ end
15
+ end
16
+ end
17
+
18
+ bidi_call_async('session.new', {
19
+ capabilities: {
20
+ alwaysMatch: {
21
+ acceptInsecureCerts: false,
22
+ webSocketUrl:true,
23
+ },
24
+ },
25
+ }).wait
26
+ end
27
+
28
+ def create_browsing_context(&block)
29
+ res = bidi_call_async('browsingContext.create', { type: :tab, userContext: :default }).wait
30
+ browsing_context = BrowsingContext.new(self, res[:context])
31
+ if block
32
+ begin
33
+ block.call(browsing_context)
34
+ ensure
35
+ browsing_context.close
36
+ end
37
+ else
38
+ browsing_context
39
+ end
40
+ end
41
+
42
+ def close
43
+ bidi_call_async('browser.close').wait
44
+ end
45
+
46
+ def bidi_call_async(method_, params = {})
47
+ with_message_id do |message_id|
48
+ Async do
49
+ @message_results[message_id] = Async::Variable.new
50
+
51
+ send_message_to_websocket({
52
+ id: message_id,
53
+ method: method_,
54
+ params: params,
55
+ })
56
+
57
+ value = @message_results[message_id].value
58
+ if value.is_a?(ErrorData)
59
+ raise value.to_error
60
+ else
61
+ value
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def with_message_id(&block)
70
+ unless @message_id
71
+ @message_id = 1
72
+ @message_results = {}
73
+ end
74
+
75
+ message_id = @message_id
76
+ @message_id += 1
77
+ block.call(message_id)
78
+ end
79
+
80
+ def send_message_to_websocket(payload)
81
+ debug_print_send(payload)
82
+ message = Protocol::WebSocket::JSONMessage.generate(payload)
83
+ message.send(@websocket)
84
+ @websocket.flush
85
+ end
86
+
87
+ def handle_received_message_from_websocket(payload)
88
+ debug_print_recv(payload)
89
+
90
+ if payload[:id]
91
+ if variable = @message_results.delete(payload[:id])
92
+ case payload[:type]
93
+ when 'success'
94
+ variable.resolve(payload[:result])
95
+ when 'error'
96
+ variable.resolve(ErrorData.parse(payload))
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ class ErrorData < Data.define(:type, :message, :stacktrace)
103
+ def self.parse(payload)
104
+ # {:type=>"error", :id=>1, :error=>"invalid argument", :message=>"method: string value expected", :stacktrace=>"RemoteError@chrome://remote/content/shared/RemoteError.sys.mjs:8:8\nWebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:193:5\nInvalidArgumentError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:384:5\nassert.that/<@chrome://remote/content/shared/webdriver/Assert.sys.mjs:485:13\nassert.string@chrome://remote/content/shared/webdriver/Assert.sys.mjs:385:53\nonPacket@chrome://remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs:172:19\nonMessage@chrome://remote/content/server/WebSocketTransport.sys.mjs:127:18\nhandleEvent@chrome://remote/content/server/WebSocketTransport.sys.mjs:109:14\n"}
105
+ new(
106
+ type: payload[:error],
107
+ message: payload[:message],
108
+ stacktrace: payload[:stacktrace].split("\n"),
109
+ )
110
+ end
111
+
112
+ def to_error
113
+ Error.new("#{type}: #{message}\n#{stacktrace.join("\n")}")
114
+ end
115
+ end
116
+
117
+ class Error < StandardError ; end
118
+
119
+ def debug_print_send(hash)
120
+ return unless @debug_protocol
121
+
122
+ puts "SEND > #{hash}"
123
+ end
124
+
125
+ def debug_print_recv(hash)
126
+ return unless @debug_protocol
127
+
128
+ puts "RECV < #{hash}"
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,24 @@
1
+ module Minibidi
2
+ class BrowserProcess
3
+ def initialize(*command)
4
+ stdin, @stdout, @stderr, @thread = Open3.popen3(*command, { pgroup: true })
5
+ stdin.close
6
+ @pid = @thread.pid
7
+ rescue Errno::ENOENT => err
8
+ raise LaunchError.new("Failed to launch browser process: #{err}")
9
+ end
10
+
11
+ def kill
12
+ Process.kill(:KILL, @pid)
13
+ rescue Errno::ESRCH
14
+ # already killed
15
+ end
16
+
17
+ def dispose
18
+ [@stdout, @stderr].each { |io| io.close unless io.closed? }
19
+ @thread.terminate
20
+ end
21
+
22
+ attr_reader :stdout, :stderr
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ require 'base64'
2
+
3
+ module Minibidi
4
+ class BrowsingContext
5
+ def initialize(browser, context_id)
6
+ @browser = browser
7
+ @context_id = context_id
8
+ end
9
+
10
+ def navigate(url)
11
+ bidi_call_async('browsingContext.navigate', { url: url, wait: :interactive }).wait
12
+ end
13
+
14
+ def capture_screenshot(origin:, format:)
15
+ result = bidi_call_async('browsingContext.captureScreenshot', {
16
+ origin: origin,
17
+ format: format,
18
+ }).wait
19
+
20
+ Base64.strict_decode64(result[:data])
21
+ end
22
+
23
+ private
24
+
25
+ def bidi_call_async(method_, params = {})
26
+ @browser.bidi_call_async(method_, params.merge({ context: @context_id }))
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ module Minibidi
2
+ class Firefox
3
+ def self.launch(&block)
4
+ FirefoxLauncher.new.launch(&block)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,162 @@
1
+ require 'async'
2
+ require 'async/http/endpoint'
3
+ require 'async/websocket'
4
+ require 'open3'
5
+ require 'protocol/websocket/json_message'
6
+ require 'timeout'
7
+ require 'tmpdir'
8
+
9
+ module Minibidi
10
+ class FirefoxLauncher
11
+ def launch(&block)
12
+ raise ArgumentError, "block is required" unless block
13
+
14
+ Dir.mktmpdir('minibidi') do |tmp|
15
+ create_user_profile(tmp)
16
+
17
+ proc = BrowserProcess.new(
18
+ "/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox",
19
+ "--remote-debugging-port=0",
20
+ "--profile #{tmp}",
21
+ "--no-remote",
22
+ "--foreground",
23
+ "about:blank",
24
+ )
25
+ at_exit { proc.kill }
26
+ trap(:INT) { proc.kill ; exit 130 }
27
+ trap(:TERM) { proc.kill ; proc.dispose }
28
+ trap(:HUP) { proc.kill ; proc.dispose }
29
+
30
+ endpoint = wait_for_ws_endpoint(proc)
31
+
32
+ Async::Reactor.run do
33
+ Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse("#{endpoint}/session")) do |async_websocket_connection|
34
+ browser = Browser.new(async_websocket_connection)
35
+ begin
36
+ block.call(browser)
37
+ ensure
38
+ browser.close
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def create_user_profile(profile_dir)
48
+ open(File.join(profile_dir, 'user.js'), 'w') do |f|
49
+ f.write(template_for_user_profile)
50
+ end
51
+ end
52
+
53
+ def template_for_user_profile
54
+ # execute the code below to create a template of user profile for Firefox.
55
+ # -----------
56
+ # import { createProfile } from '@puppeteer/browsers';
57
+ #
58
+ # await createProfile('firefox', {
59
+ # path: './my_prefs',
60
+ # preferences: {
61
+ # 'remote.active-protocols': 1,
62
+ # 'fission.webContentIsolationStrategy': 0,
63
+ # }
64
+ # })
65
+ <<~JS
66
+ user_pref("app.normandy.api_url", "");
67
+ user_pref("app.update.checkInstallTime", false);
68
+ user_pref("app.update.disabledForTesting", true);
69
+ user_pref("apz.content_response_timeout", 60000);
70
+ user_pref("browser.contentblocking.features.standard", "-tp,tpPrivate,cookieBehavior0,-cm,-fp");
71
+ user_pref("browser.dom.window.dump.enabled", true);
72
+ user_pref("browser.newtabpage.activity-stream.feeds.system.topstories", false);
73
+ user_pref("browser.newtabpage.enabled", false);
74
+ user_pref("browser.pagethumbnails.capturing_disabled", true);
75
+ user_pref("browser.safebrowsing.blockedURIs.enabled", false);
76
+ user_pref("browser.safebrowsing.downloads.enabled", false);
77
+ user_pref("browser.safebrowsing.malware.enabled", false);
78
+ user_pref("browser.safebrowsing.phishing.enabled", false);
79
+ user_pref("browser.search.update", false);
80
+ user_pref("browser.sessionstore.resume_from_crash", false);
81
+ user_pref("browser.shell.checkDefaultBrowser", false);
82
+ user_pref("browser.startup.homepage", "about:blank");
83
+ user_pref("browser.startup.homepage_override.mstone", "ignore");
84
+ user_pref("browser.startup.page", 0);
85
+ user_pref("browser.tabs.disableBackgroundZombification", false);
86
+ user_pref("browser.tabs.warnOnCloseOtherTabs", false);
87
+ user_pref("browser.tabs.warnOnOpen", false);
88
+ user_pref("browser.translations.automaticallyPopup", false);
89
+ user_pref("browser.uitour.enabled", false);
90
+ user_pref("browser.urlbar.suggest.searches", false);
91
+ user_pref("browser.usedOnWindows10.introURL", "");
92
+ user_pref("browser.warnOnQuit", false);
93
+ user_pref("datareporting.healthreport.documentServerURI", "http://dummy.test/dummy/healthreport/");
94
+ user_pref("datareporting.healthreport.logging.consoleEnabled", false);
95
+ user_pref("datareporting.healthreport.service.enabled", false);
96
+ user_pref("datareporting.healthreport.service.firstRun", false);
97
+ user_pref("datareporting.healthreport.uploadEnabled", false);
98
+ user_pref("datareporting.policy.dataSubmissionEnabled", false);
99
+ user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
100
+ user_pref("devtools.jsonview.enabled", false);
101
+ user_pref("dom.disable_open_during_load", false);
102
+ user_pref("dom.file.createInChild", true);
103
+ user_pref("dom.ipc.reportProcessHangs", false);
104
+ user_pref("dom.max_chrome_script_run_time", 0);
105
+ user_pref("dom.max_script_run_time", 0);
106
+ user_pref("extensions.autoDisableScopes", 0);
107
+ user_pref("extensions.enabledScopes", 5);
108
+ user_pref("extensions.getAddons.cache.enabled", false);
109
+ user_pref("extensions.installDistroAddons", false);
110
+ user_pref("extensions.screenshots.disabled", true);
111
+ user_pref("extensions.update.enabled", false);
112
+ user_pref("extensions.update.notifyUser", false);
113
+ user_pref("extensions.webservice.discoverURL", "http://dummy.test/dummy/discoveryURL");
114
+ user_pref("focusmanager.testmode", true);
115
+ user_pref("general.useragent.updates.enabled", false);
116
+ user_pref("geo.provider.testing", true);
117
+ user_pref("geo.wifi.scan", false);
118
+ user_pref("hangmonitor.timeout", 0);
119
+ user_pref("javascript.options.showInConsole", true);
120
+ user_pref("media.gmp-manager.updateEnabled", false);
121
+ user_pref("media.sanity-test.disabled", true);
122
+ user_pref("network.cookie.sameSite.laxByDefault", false);
123
+ user_pref("network.http.prompt-temp-redirect", false);
124
+ user_pref("network.http.speculative-parallel-limit", 0);
125
+ user_pref("network.manage-offline-status", false);
126
+ user_pref("network.sntp.pools", "dummy.test");
127
+ user_pref("plugin.state.flash", 0);
128
+ user_pref("privacy.trackingprotection.enabled", false);
129
+ user_pref("remote.enabled", true);
130
+ user_pref("security.certerrors.mitm.priming.enabled", false);
131
+ user_pref("security.fileuri.strict_origin_policy", false);
132
+ user_pref("security.notification_enable_delay", 0);
133
+ user_pref("services.settings.server", "http://dummy.test/dummy/blocklist/");
134
+ user_pref("signon.autofillForms", false);
135
+ user_pref("signon.rememberSignons", false);
136
+ user_pref("startup.homepage_welcome_url", "about:blank");
137
+ user_pref("startup.homepage_welcome_url.additional", "");
138
+ user_pref("toolkit.cosmeticAnimations.enabled", false);
139
+ user_pref("toolkit.startup.max_resumed_crashes", -1);
140
+ user_pref("remote.active-protocols", 1);
141
+ user_pref("fission.webContentIsolationStrategy", 0);
142
+ JS
143
+ end
144
+
145
+ def wait_for_ws_endpoint(browser_process)
146
+ lines = []
147
+ Timeout.timeout(30) do
148
+ loop do
149
+ line = browser_process.stderr.readline
150
+ /^WebDriver BiDi listening on (ws:\/\/.*)$/.match(line) do |m|
151
+ return m[1].gsub(/\r/, '')
152
+ end
153
+ lines << line
154
+ end
155
+ end
156
+ rescue EOFError
157
+ raise LaunchError.new(lines.join("\n"))
158
+ rescue Timeout::Error
159
+ raise LaunchError.new("Timed out after 30 seconds while trying to connect to the browser.")
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,3 @@
1
+ module Minibidi
2
+ VERSION = '0.0.0'
3
+ end
data/lib/minibidi.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "minibidi/version"
2
+
3
+ require 'minibidi/browser'
4
+ require 'minibidi/browsing_context'
5
+ require 'minibidi/browser_process'
6
+ require 'minibidi/firefox'
7
+ require 'minibidi/firefox_launcher'
8
+
9
+ module Minibidi
10
+ class LaunchError < StandardError ; end
11
+ end
data/minibidi.gemspec ADDED
@@ -0,0 +1,32 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "minibidi/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "minibidi"
8
+ spec.version = Minibidi::VERSION
9
+ spec.authors = ["YusukeIwaki"]
10
+ spec.email = ["q7w8e9w8q7w8e9@yahoo.co.jp"]
11
+
12
+ spec.summary = "Mini WebDriver BiDi binding for Ruby"
13
+ spec.homepage = 'https://github.com/YusukeIwaki/minibidi'
14
+ spec.license = "MIT"
15
+
16
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
17
+ `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/}) || f.include?(".git")
19
+ end
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.required_ruby_version = ">= 3.2" # Dependency for socketry/async and Data.define
26
+ spec.add_dependency "async"
27
+ spec.add_dependency "async-websocket"
28
+ spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "minitest"
30
+ spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "rake"
32
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minibidi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - YusukeIwaki
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-04-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-websocket
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - q7w8e9w8q7w8e9@yahoo.co.jp
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - Gemfile
105
+ - LICENSE.txt
106
+ - README.md
107
+ - Rakefile
108
+ - bin/console
109
+ - lib/minibidi.rb
110
+ - lib/minibidi/browser.rb
111
+ - lib/minibidi/browser_process.rb
112
+ - lib/minibidi/browsing_context.rb
113
+ - lib/minibidi/firefox.rb
114
+ - lib/minibidi/firefox_launcher.rb
115
+ - lib/minibidi/version.rb
116
+ - minibidi.gemspec
117
+ homepage: https://github.com/YusukeIwaki/minibidi
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '3.2'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.5.3
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Mini WebDriver BiDi binding for Ruby
140
+ test_files: []