ferrum 0.7 → 0.8
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.
- checksums.yaml +4 -4
- data/README.md +242 -44
- data/lib/ferrum.rb +7 -1
- data/lib/ferrum/browser.rb +8 -6
- data/lib/ferrum/browser/client.rb +4 -6
- data/lib/ferrum/browser/process.rb +2 -2
- data/lib/ferrum/browser/web_socket.rb +6 -3
- data/lib/ferrum/cookies.rb +7 -0
- data/lib/ferrum/dialog.rb +2 -2
- data/lib/ferrum/frame.rb +19 -3
- data/lib/ferrum/frame/dom.rb +38 -41
- data/lib/ferrum/frame/runtime.rb +39 -32
- data/lib/ferrum/keyboard.rb +3 -3
- data/lib/ferrum/mouse.rb +14 -3
- data/lib/ferrum/node.rb +23 -11
- data/lib/ferrum/page.rb +16 -6
- data/lib/ferrum/page/frames.rb +9 -3
- data/lib/ferrum/page/screenshot.rb +2 -2
- data/lib/ferrum/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fb53f7db2767597ff5d6e7ff08536562edec30f5d68fbb2dcac7bf9943e056d
|
4
|
+
data.tar.gz: 46f9f75de5f4c0a4531dbbeda25c538350c3daa167f5de53c9248dbfc6958e34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e15da4027c44cd38e3d4eaf6f3384085fbb85fe5760ade585bbaaf2c4ea4fa79db98c23fee3099b33e1d09ab50e1ce8b979ec8848203a3d0c4b45fbe6208b7f
|
7
|
+
data.tar.gz: 7038d36ca9cca2204d9a489380ae0db7d75f202921a3b31b4bd9cbed3c54fb4bd187d5270052dfcdeba300ba615140b33660982dfaa6f99759a3463c52434fc1
|
data/README.md
CHANGED
@@ -1,57 +1,81 @@
|
|
1
|
-
# Ferrum -
|
1
|
+
# Ferrum - high-level API to control Chrome in Ruby
|
2
2
|
|
3
|
-
[](https://travis-ci.org/rubycdp/ferrum)
|
4
4
|
|
5
|
-
<img align="right"
|
5
|
+
<img align="right"
|
6
|
+
width="320" height="241"
|
6
7
|
alt="Ferrum logo"
|
7
|
-
src="https://raw.githubusercontent.com/
|
8
|
+
src="https://raw.githubusercontent.com/rubycdp/ferrum/master/logo.svg?sanitize=true">
|
8
9
|
|
9
|
-
As simple as Puppeteer, though even simpler.
|
10
|
+
#### As simple as Puppeteer, though even simpler.
|
10
11
|
|
11
|
-
It is Ruby clean and high-level API to Chrome. Runs headless by default,
|
12
|
-
|
13
|
-
Chrome/
|
12
|
+
It is Ruby clean and high-level API to Chrome. Runs headless by default, but you
|
13
|
+
can configure it to run in a headful mode. All you need is Ruby and
|
14
|
+
[Chrome](https://www.google.com/chrome/) or
|
15
|
+
[Chromium](https://www.chromium.org/). Ferrum connects to the browser by [CDP
|
16
|
+
protocol](https://chromedevtools.github.io/devtools-protocol/) and there's _no_
|
17
|
+
Selenium/WebDriver/ChromeDriver dependency. The emphasis was made on a raw CDP
|
18
|
+
protocol because Chrome allows you to do so many things that are barely
|
19
|
+
supported by WebDriver because it should have consistent design with other
|
20
|
+
browsers.
|
14
21
|
|
15
|
-
[Cuprite](https://github.com/
|
16
|
-
|
17
|
-
|
18
|
-
|
22
|
+
* [Cuprite](https://github.com/rubycdp/cuprite) is a pure Ruby driver for
|
23
|
+
[Capybara](https://github.com/teamcapybara/capybara) based on Ferrum. If you are
|
24
|
+
going to crawl sites you better use Ferrum or
|
25
|
+
[Vessel](https://github.com/rubycdp/vessel) because you crawl, not test.
|
19
26
|
|
20
|
-
[Vessel](https://github.com/
|
21
|
-
based on Ferrum.
|
27
|
+
* [Vessel](https://github.com/rubycdp/vessel) high-level web crawling framework
|
28
|
+
based on Ferrum. It looks like [Scrapy](https://scrapy.org/) except that it uses
|
29
|
+
a real browser in order to grab data.
|
30
|
+
|
31
|
+
Web design by [Evrone](https://evrone.com/), what else
|
32
|
+
[we build with Ruby on Rails](https://evrone.com/ruby), what else
|
33
|
+
[we do at Evrone](https://evrone.com/cases#case-studies).
|
34
|
+
|
35
|
+
If you like this project, please consider to
|
36
|
+
_[become a backer](https://www.patreon.com/rubycdp_ferrum)_ on Patreon.
|
22
37
|
|
23
|
-
If you like this project, please consider to _[become a backer](https://www.patreon.com/rferrum)_
|
24
|
-
on Patreon.
|
25
38
|
|
26
39
|
## Index
|
27
40
|
|
28
|
-
* [
|
29
|
-
* [
|
30
|
-
* [
|
31
|
-
* [
|
32
|
-
* [
|
33
|
-
* [
|
34
|
-
* [
|
35
|
-
* [
|
36
|
-
* [
|
37
|
-
* [
|
38
|
-
* [
|
39
|
-
* [
|
41
|
+
* [Install](https://github.com/rubycdp/ferrum#install)
|
42
|
+
* [Examples](https://github.com/rubycdp/ferrum#examples)
|
43
|
+
* [Docker](https://github.com/rubycdp/ferrum#docker)
|
44
|
+
* [Customization](https://github.com/rubycdp/ferrum#customization)
|
45
|
+
* [Navigation](https://github.com/rubycdp/ferrum#navigation)
|
46
|
+
* [Finders](https://github.com/rubycdp/ferrum#finders)
|
47
|
+
* [Screenshots](https://github.com/rubycdp/ferrum#screenshots)
|
48
|
+
* [Network](https://github.com/rubycdp/ferrum#network)
|
49
|
+
* [Mouse](https://github.com/rubycdp/ferrum#mouse)
|
50
|
+
* [Keyboard](https://github.com/rubycdp/ferrum#keyboard)
|
51
|
+
* [Cookies](https://github.com/rubycdp/ferrum#cookies)
|
52
|
+
* [Headers](https://github.com/rubycdp/ferrum#headers)
|
53
|
+
* [JavaScript](https://github.com/rubycdp/ferrum#javascript)
|
54
|
+
* [Frames](https://github.com/rubycdp/ferrum#frames)
|
55
|
+
* [Frame](https://github.com/rubycdp/ferrum#frame)
|
56
|
+
* [Dialog](https://github.com/rubycdp/ferrum#dialog)
|
57
|
+
* [Thread safety](https://github.com/rubycdp/ferrum#thread-safety)
|
58
|
+
* [License](https://github.com/rubycdp/ferrum#license)
|
59
|
+
|
40
60
|
|
41
61
|
## Install
|
42
62
|
|
43
63
|
There's no official Chrome or Chromium package for Linux don't install it this
|
44
|
-
way because it either
|
45
|
-
|
64
|
+
way because it's either outdated or unofficial, both are bad. Download it from
|
65
|
+
official [source](https://www.chromium.org/getting-involved/download-chromium).
|
46
66
|
Chrome binary should be in the `PATH` or `BROWSER_PATH` or you can pass it as an
|
47
|
-
option to browser instance `:browser_path
|
67
|
+
option to browser instance see `:browser_path` in
|
68
|
+
[Customization](https://github.com/rubycdp/ferrum#customization).
|
48
69
|
|
49
|
-
Add this to your Gemfile
|
70
|
+
Add this to your `Gemfile` and run `bundle install`.
|
50
71
|
|
51
72
|
``` ruby
|
52
73
|
gem "ferrum"
|
53
74
|
```
|
54
75
|
|
76
|
+
|
77
|
+
## Examples
|
78
|
+
|
55
79
|
Navigate to a website and save a screenshot:
|
56
80
|
|
57
81
|
```ruby
|
@@ -68,7 +92,7 @@ browser = Ferrum::Browser.new
|
|
68
92
|
browser.goto("https://google.com")
|
69
93
|
input = browser.at_xpath("//div[@id='searchform']/form//input[@type='text']")
|
70
94
|
input.focus.type("Ruby headless driver for Chrome", :Enter)
|
71
|
-
browser.at_css("a > h3").text # => "
|
95
|
+
browser.at_css("a > h3").text # => "rubycdp/ferrum: Ruby Chrome/Chromium driver - GitHub"
|
72
96
|
browser.quit
|
73
97
|
```
|
74
98
|
|
@@ -103,7 +127,17 @@ browser.mouse
|
|
103
127
|
browser.quit
|
104
128
|
```
|
105
129
|
|
106
|
-
|
130
|
+
|
131
|
+
## Docker
|
132
|
+
|
133
|
+
In docker as root you must pass the no-sandbox browser option:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Ferrum::Browser.new(browser_options: { 'no-sandbox': nil })
|
137
|
+
```
|
138
|
+
|
139
|
+
|
140
|
+
## Customization
|
107
141
|
|
108
142
|
You can customize options with the following code in your test setup:
|
109
143
|
|
@@ -127,7 +161,7 @@ Ferrum::Browser.new(options)
|
|
127
161
|
* `:js_errors` (Boolean) - When true, JavaScript errors get re-raised in Ruby.
|
128
162
|
* `:browser_name` (Symbol) - `:chrome` by default, only experimental support
|
129
163
|
for `:firefox` for now.
|
130
|
-
* `:browser_path` (String) - Path to
|
164
|
+
* `:browser_path` (String) - Path to Chrome binary, you can also set ENV
|
131
165
|
variable as `BROWSER_PATH=some/path/chrome bundle exec rspec`.
|
132
166
|
* `:browser_options` (Hash) - Additional command line options,
|
133
167
|
[see them all](https://peter.sh/experiments/chromium-command-line-switches/)
|
@@ -138,9 +172,9 @@ Ferrum::Browser.new(options)
|
|
138
172
|
browser process will not be spawned.
|
139
173
|
* `:process_timeout` (Integer) - How long to wait for the Chrome process to
|
140
174
|
respond on startup
|
141
|
-
|
142
|
-
|
143
|
-
|
175
|
+
* `:ws_max_receive_size` (Integer) - How big messages to accept from Chrome
|
176
|
+
over the web socket, in bytes. Defaults to 64MB. Incoming messages larger
|
177
|
+
than this will cause a `Ferrum::DeadBrowserError`.
|
144
178
|
|
145
179
|
|
146
180
|
## Navigation
|
@@ -186,6 +220,15 @@ browser.goto("https://github.com/")
|
|
186
220
|
browser.refresh
|
187
221
|
```
|
188
222
|
|
223
|
+
#### stop
|
224
|
+
|
225
|
+
Stop all navigations and loading pending resources on the page
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
browser.goto("https://github.com/")
|
229
|
+
browser.stop
|
230
|
+
```
|
231
|
+
|
189
232
|
|
190
233
|
## Finders
|
191
234
|
|
@@ -558,6 +601,8 @@ Sets given values as cookie
|
|
558
601
|
* :value `String`
|
559
602
|
* :domain `String`
|
560
603
|
* :expires `Integer`
|
604
|
+
* :samesite `String`
|
605
|
+
* :httponly `Boolean`
|
561
606
|
|
562
607
|
```ruby
|
563
608
|
browser.cookies.set(name: "stealth", value: "omg", domain: "google.com") # => true
|
@@ -686,17 +731,146 @@ browser.evaluate("window.__injected") # => 42
|
|
686
731
|
|
687
732
|
## Frames
|
688
733
|
|
689
|
-
#### frames
|
690
|
-
|
691
|
-
|
734
|
+
#### frames : `Array[Frame] | []`
|
735
|
+
|
736
|
+
Returns all the frames current page have.
|
737
|
+
|
738
|
+
```ruby
|
739
|
+
browser.goto("https://www.w3schools.com/tags/tag_frame.asp")
|
740
|
+
browser.frames # =>
|
741
|
+
# [
|
742
|
+
# #<Ferrum::Frame @id="C6D104CE454A025FBCF22B98DE612B12" @parent_id=nil @name=nil @state=:stopped_loading @execution_id=1>,
|
743
|
+
# #<Ferrum::Frame @id="C09C4E4404314AAEAE85928EAC109A93" @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=2>,
|
744
|
+
# #<Ferrum::Frame @id="2E9C7F476ED09D87A42F2FEE3C6FBC3C" @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=3>,
|
745
|
+
# ...
|
746
|
+
# ]
|
747
|
+
```
|
748
|
+
|
749
|
+
#### main_frame : `Frame`
|
750
|
+
|
751
|
+
Returns page's main frame, the top of the tree and the parent of all frames.
|
752
|
+
|
753
|
+
#### frame_by(\*\*options) : `Frame | nil`
|
754
|
+
|
755
|
+
Find frame by given options.
|
756
|
+
|
757
|
+
* options `Hash`
|
758
|
+
* :id `String` - Unique frame's id that browser provides
|
759
|
+
* :name `String` - Frame's name if there's one
|
760
|
+
|
761
|
+
```ruby
|
762
|
+
browser.frame_by(id: "C6D104CE454A025FBCF22B98DE612B12")
|
763
|
+
```
|
764
|
+
|
765
|
+
|
766
|
+
## Frame
|
767
|
+
|
768
|
+
#### id : `String`
|
769
|
+
|
770
|
+
Frame's unique id.
|
771
|
+
|
772
|
+
#### parent_id : `String | nil`
|
773
|
+
|
774
|
+
Parent frame id if this one is nested in another one.
|
775
|
+
|
776
|
+
#### execution_id : `Integer`
|
777
|
+
|
778
|
+
Execution context id which is used by JS, each frame has it's own context in
|
779
|
+
which JS evaluates.
|
780
|
+
|
781
|
+
#### name : `String | nil`
|
782
|
+
|
783
|
+
If frame was given a name it should be here.
|
784
|
+
|
785
|
+
#### state : `Symbol | nil`
|
786
|
+
|
787
|
+
One of the states frame's in:
|
788
|
+
|
789
|
+
* `:started_loading`
|
790
|
+
* `:navigated`
|
791
|
+
* `:stopped_loading`
|
792
|
+
|
793
|
+
#### url : `String`
|
794
|
+
|
795
|
+
Returns current frame's location href.
|
692
796
|
|
693
|
-
|
797
|
+
```ruby
|
798
|
+
browser.goto("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
799
|
+
frame = browser.frames[1]
|
800
|
+
frame.url # => https://interactive-examples.mdn.mozilla.net/pages/tabbed/iframe.html
|
801
|
+
```
|
802
|
+
|
803
|
+
#### title
|
804
|
+
|
805
|
+
Returns current frame's title.
|
806
|
+
|
807
|
+
```ruby
|
808
|
+
browser.goto("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
809
|
+
frame = browser.frames[1]
|
810
|
+
frame.title # => HTML Demo: <iframe>
|
811
|
+
```
|
812
|
+
|
813
|
+
#### main? : `Boolean`
|
814
|
+
|
815
|
+
If current frame is the main frame of the page (top of the tree).
|
816
|
+
|
817
|
+
```ruby
|
818
|
+
browser.goto("https://www.w3schools.com/tags/tag_frame.asp")
|
819
|
+
frame = browser.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
|
820
|
+
frame.main? # => false
|
821
|
+
```
|
822
|
+
|
823
|
+
#### current_url : `String`
|
824
|
+
|
825
|
+
Returns current frame's top window location href.
|
826
|
+
|
827
|
+
```ruby
|
828
|
+
browser.goto("https://www.w3schools.com/tags/tag_frame.asp")
|
829
|
+
frame = browser.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
|
830
|
+
frame.current_url # => "https://www.w3schools.com/tags/tag_frame.asp"
|
831
|
+
```
|
832
|
+
|
833
|
+
#### current_title : `String`
|
834
|
+
|
835
|
+
Returns current frame's top window title.
|
836
|
+
|
837
|
+
```ruby
|
838
|
+
browser.goto("https://www.w3schools.com/tags/tag_frame.asp")
|
839
|
+
frame = browser.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
|
840
|
+
frame.current_title # => "HTML frame tag"
|
841
|
+
```
|
842
|
+
|
843
|
+
#### body : `String`
|
844
|
+
|
845
|
+
Returns current frame's html.
|
846
|
+
|
847
|
+
```ruby
|
848
|
+
browser.goto("https://www.w3schools.com/tags/tag_frame.asp")
|
849
|
+
frame = browser.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
|
850
|
+
frame.body # => "<html><head></head><body></body></html>"
|
851
|
+
```
|
852
|
+
|
853
|
+
#### doctype
|
854
|
+
|
855
|
+
Returns current frame's doctype.
|
856
|
+
|
857
|
+
```ruby
|
858
|
+
browser.goto("https://www.w3schools.com/tags/tag_frame.asp")
|
859
|
+
browser.main_frame.doctype # => "<!DOCTYPE html>"
|
860
|
+
```
|
861
|
+
|
862
|
+
#### set_content(html)
|
863
|
+
|
864
|
+
Sets a content of a given frame.
|
865
|
+
|
866
|
+
* html `String`
|
694
867
|
|
695
868
|
```ruby
|
696
869
|
browser.goto("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
697
870
|
frame = browser.frames[1]
|
698
|
-
|
699
|
-
|
871
|
+
frame.body # <html lang="en"><head><style>body {transition: opacity ease-in 0.2s; }...
|
872
|
+
frame.set_content("<html><head></head><body><p>lol</p></body></html>")
|
873
|
+
frame.body # => <html><head></head><body><p>lol</p></body></html>
|
700
874
|
```
|
701
875
|
|
702
876
|
|
@@ -781,3 +955,27 @@ t2.join
|
|
781
955
|
|
782
956
|
browser.quit
|
783
957
|
```
|
958
|
+
|
959
|
+
|
960
|
+
## License
|
961
|
+
|
962
|
+
Copyright 2018-2020 Machinio
|
963
|
+
|
964
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
965
|
+
a copy of this software and associated documentation files (the
|
966
|
+
"Software"), to deal in the Software without restriction, including
|
967
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
968
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
969
|
+
permit persons to whom the Software is furnished to do so, subject to
|
970
|
+
the following conditions:
|
971
|
+
|
972
|
+
The above copyright notice and this permission notice shall be
|
973
|
+
included in all copies or substantial portions of the Software.
|
974
|
+
|
975
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
976
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
977
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
978
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
979
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
980
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
981
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/ferrum.rb
CHANGED
@@ -36,6 +36,12 @@ module Ferrum
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
class ProcessTimeoutError < Error
|
40
|
+
def initialize(timeout)
|
41
|
+
super("Browser did not produce websocket url within #{timeout} seconds")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
39
45
|
class DeadBrowserError < Error
|
40
46
|
def initialize(message = "Browser is dead or given window is closed")
|
41
47
|
super
|
@@ -72,8 +78,8 @@ module Ferrum
|
|
72
78
|
attr_reader :class_name, :message
|
73
79
|
|
74
80
|
def initialize(response)
|
75
|
-
super
|
76
81
|
@class_name, @message = response.values_at("className", "description")
|
82
|
+
super(response.merge("message" => @message))
|
77
83
|
end
|
78
84
|
end
|
79
85
|
|
data/lib/ferrum/browser.rb
CHANGED
@@ -16,8 +16,9 @@ module Ferrum
|
|
16
16
|
extend Forwardable
|
17
17
|
delegate %i[default_context] => :contexts
|
18
18
|
delegate %i[targets create_target create_page page pages windows] => :default_context
|
19
|
-
delegate %i[goto back forward refresh reload
|
20
|
-
at_css at_xpath css xpath current_url
|
19
|
+
delegate %i[goto back forward refresh reload stop
|
20
|
+
at_css at_xpath css xpath current_url current_title url title
|
21
|
+
body doctype set_content
|
21
22
|
headers cookies network
|
22
23
|
mouse keyboard
|
23
24
|
screenshot pdf viewport_size
|
@@ -28,7 +29,7 @@ module Ferrum
|
|
28
29
|
delegate %i[default_user_agent] => :process
|
29
30
|
|
30
31
|
attr_reader :client, :process, :contexts, :logger, :js_errors,
|
31
|
-
:slowmo, :base_url, :options, :window_size
|
32
|
+
:slowmo, :base_url, :options, :window_size, :ws_max_receive_size
|
32
33
|
attr_writer :timeout
|
33
34
|
|
34
35
|
def initialize(options = nil)
|
@@ -39,9 +40,10 @@ module Ferrum
|
|
39
40
|
@original_window_size = @window_size
|
40
41
|
|
41
42
|
@options = Hash(options.merge(window_size: @window_size))
|
42
|
-
@logger, @timeout
|
43
|
+
@logger, @timeout, @ws_max_receive_size =
|
44
|
+
@options.values_at(:logger, :timeout, :ws_max_receive_size)
|
43
45
|
@js_errors = @options.fetch(:js_errors, false)
|
44
|
-
@slowmo = @options[:slowmo].
|
46
|
+
@slowmo = @options[:slowmo].to_f
|
45
47
|
|
46
48
|
if @options.key?(:base_url)
|
47
49
|
self.base_url = @options[:base_url]
|
@@ -114,7 +116,7 @@ module Ferrum
|
|
114
116
|
def start
|
115
117
|
Ferrum.started
|
116
118
|
@process = Process.start(@options)
|
117
|
-
@client = Client.new(self, @process.ws_url
|
119
|
+
@client = Client.new(self, @process.ws_url)
|
118
120
|
@contexts = Contexts.new(self)
|
119
121
|
end
|
120
122
|
end
|
@@ -9,12 +9,11 @@ module Ferrum
|
|
9
9
|
class Client
|
10
10
|
INTERRUPTIONS = %w[Fetch.requestPaused Fetch.authRequired].freeze
|
11
11
|
|
12
|
-
def initialize(browser, ws_url,
|
13
|
-
@command_id = start_id
|
14
|
-
@pendings = Concurrent::Hash.new
|
12
|
+
def initialize(browser, ws_url, id_starts_with: 0)
|
15
13
|
@browser = browser
|
16
|
-
@
|
17
|
-
@
|
14
|
+
@command_id = id_starts_with
|
15
|
+
@pendings = Concurrent::Hash.new
|
16
|
+
@ws = WebSocket.new(ws_url, @browser.ws_max_receive_size, @browser.logger)
|
18
17
|
@subscriber, @interruptor = Subscriber.build(2)
|
19
18
|
|
20
19
|
@thread = Thread.new do
|
@@ -39,7 +38,6 @@ module Ferrum
|
|
39
38
|
pending = Concurrent::IVar.new
|
40
39
|
message = build_message(method, params)
|
41
40
|
@pendings[message[:id]] = pending
|
42
|
-
sleep(@slowmo) if @slowmo
|
43
41
|
@ws.send_message(message)
|
44
42
|
data = pending.value!(@browser.timeout)
|
45
43
|
@pendings.delete(message[:id])
|
@@ -133,8 +133,8 @@ module Ferrum
|
|
133
133
|
end
|
134
134
|
|
135
135
|
unless ws_url
|
136
|
-
@logger.puts
|
137
|
-
raise
|
136
|
+
@logger.puts(output) if @logger
|
137
|
+
raise ProcessTimeoutError.new(timeout)
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
@@ -11,12 +11,13 @@ module Ferrum
|
|
11
11
|
|
12
12
|
attr_reader :url, :messages
|
13
13
|
|
14
|
-
def initialize(url, logger)
|
14
|
+
def initialize(url, max_receive_size, logger)
|
15
15
|
@url = url
|
16
16
|
@logger = logger
|
17
17
|
uri = URI.parse(@url)
|
18
18
|
@sock = TCPSocket.new(uri.host, uri.port)
|
19
|
-
|
19
|
+
max_receive_size ||= ::WebSocket::Driver::MAX_LENGTH
|
20
|
+
@driver = ::WebSocket::Driver.client(self, max_length: max_receive_size)
|
20
21
|
@messages = Queue.new
|
21
22
|
|
22
23
|
@driver.on(:open, &method(:on_open))
|
@@ -25,7 +26,9 @@ module Ferrum
|
|
25
26
|
|
26
27
|
@thread = Thread.new do
|
27
28
|
Thread.current.abort_on_exception = true
|
28
|
-
|
29
|
+
if Thread.current.respond_to?(:report_on_exception=)
|
30
|
+
Thread.current.report_on_exception = true
|
31
|
+
end
|
29
32
|
|
30
33
|
begin
|
31
34
|
while data = @sock.readpartial(512)
|
data/lib/ferrum/cookies.rb
CHANGED
@@ -23,6 +23,10 @@ module Ferrum
|
|
23
23
|
@attributes["path"]
|
24
24
|
end
|
25
25
|
|
26
|
+
def samesite
|
27
|
+
@attributes["sameSite"]
|
28
|
+
end
|
29
|
+
|
26
30
|
def size
|
27
31
|
@attributes["size"]
|
28
32
|
end
|
@@ -65,6 +69,9 @@ module Ferrum
|
|
65
69
|
cookie[:value] ||= value
|
66
70
|
cookie[:domain] ||= default_domain
|
67
71
|
|
72
|
+
cookie[:httpOnly] = cookie.delete(:httponly) if cookie.key?(:httponly)
|
73
|
+
cookie[:sameSite] = cookie.delete(:samesite) if cookie.key?(:samesite)
|
74
|
+
|
68
75
|
expires = cookie.delete(:expires).to_i
|
69
76
|
cookie[:expires] = expires if expires > 0
|
70
77
|
|
data/lib/ferrum/dialog.rb
CHANGED
@@ -14,11 +14,11 @@ module Ferrum
|
|
14
14
|
options = { accept: true }
|
15
15
|
response = prompt_text || default_prompt
|
16
16
|
options.merge!(promptText: response) if response
|
17
|
-
@page.command("Page.handleJavaScriptDialog", **options)
|
17
|
+
@page.command("Page.handleJavaScriptDialog", slowmoable: true, **options)
|
18
18
|
end
|
19
19
|
|
20
20
|
def dismiss
|
21
|
-
@page.command("Page.handleJavaScriptDialog", accept: false)
|
21
|
+
@page.command("Page.handleJavaScriptDialog", slowmoable: true, accept: false)
|
22
22
|
end
|
23
23
|
|
24
24
|
def match?(regexp)
|
data/lib/ferrum/frame.rb
CHANGED
@@ -7,9 +7,8 @@ module Ferrum
|
|
7
7
|
class Frame
|
8
8
|
include DOM, Runtime
|
9
9
|
|
10
|
-
attr_reader :
|
11
|
-
|
12
|
-
attr_accessor :name
|
10
|
+
attr_reader :page, :parent_id, :state
|
11
|
+
attr_accessor :id, :name
|
13
12
|
|
14
13
|
def initialize(id, page, parent_id = nil)
|
15
14
|
@id, @page, @parent_id = id, page, parent_id
|
@@ -35,6 +34,15 @@ module Ferrum
|
|
35
34
|
@parent_id.nil?
|
36
35
|
end
|
37
36
|
|
37
|
+
def set_content(html)
|
38
|
+
evaluate_async(%(
|
39
|
+
document.open();
|
40
|
+
document.write(arguments[0]);
|
41
|
+
document.close();
|
42
|
+
arguments[1](true);
|
43
|
+
), @page.timeout, html)
|
44
|
+
end
|
45
|
+
|
38
46
|
def execution_id?(execution_id)
|
39
47
|
@execution_id == execution_id
|
40
48
|
end
|
@@ -47,6 +55,14 @@ module Ferrum
|
|
47
55
|
@page.event.wait(@page.timeout) ? retry : raise
|
48
56
|
end
|
49
57
|
|
58
|
+
def set_execution_id(value)
|
59
|
+
@execution_id ||= value
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_execution_id
|
63
|
+
@execution_id = nil
|
64
|
+
end
|
65
|
+
|
50
66
|
def inspect
|
51
67
|
%(#<#{self.class} @id=#{@id.inspect} @parent_id=#{@parent_id.inspect} @name=#{@name.inspect} @state=#{@state.inspect} @execution_id=#{@execution_id.inspect}>)
|
52
68
|
end
|
data/lib/ferrum/frame/dom.rb
CHANGED
@@ -29,65 +29,62 @@ module Ferrum
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def doctype
|
32
|
-
evaluate("new XMLSerializer().serializeToString(document.doctype)")
|
32
|
+
evaluate("document.doctype && new XMLSerializer().serializeToString(document.doctype)")
|
33
33
|
end
|
34
34
|
|
35
35
|
def body
|
36
36
|
evaluate("document.documentElement.outerHTML")
|
37
37
|
end
|
38
38
|
|
39
|
-
def at_xpath(selector, within: nil)
|
40
|
-
xpath(selector, within: within).first
|
41
|
-
end
|
42
|
-
|
43
|
-
# FIXME: Check within
|
44
39
|
def xpath(selector, within: nil)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
let results = [];
|
40
|
+
code = <<~JS
|
41
|
+
let selector = arguments[0];
|
42
|
+
let within = arguments[1] || document;
|
43
|
+
let results = [];
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
46
|
+
for (let i = 0; i < xpath.snapshotLength; i++) {
|
47
|
+
results.push(xpath.snapshotItem(i));
|
48
|
+
}
|
55
49
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
throw "Invalid Selector";
|
61
|
-
} else {
|
62
|
-
throw error;
|
63
|
-
}
|
64
|
-
}), @page.timeout, selector, within)
|
50
|
+
arguments[2](results);
|
51
|
+
JS
|
52
|
+
|
53
|
+
evaluate_async(code, @page.timeout, selector, within)
|
65
54
|
end
|
66
55
|
|
67
|
-
|
68
|
-
|
69
|
-
|
56
|
+
def at_xpath(selector, within: nil)
|
57
|
+
code = <<~JS
|
58
|
+
let selector = arguments[0];
|
59
|
+
let within = arguments[1] || document;
|
60
|
+
let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
61
|
+
let result = xpath.snapshotItem(0);
|
62
|
+
arguments[2](result);
|
63
|
+
JS
|
70
64
|
|
71
|
-
|
72
|
-
nodeId: node_id,
|
73
|
-
selector: selector)["nodeIds"]
|
74
|
-
ids.map { |id| build_node(id) }.compact
|
65
|
+
evaluate_async(code, @page.timeout, selector, within)
|
75
66
|
end
|
76
67
|
|
77
|
-
def
|
78
|
-
|
68
|
+
def css(selector, within: nil)
|
69
|
+
code = <<~JS
|
70
|
+
let selector = arguments[0];
|
71
|
+
let within = arguments[1] || document;
|
72
|
+
let results = within.querySelectorAll(selector);
|
73
|
+
arguments[2](results);
|
74
|
+
JS
|
79
75
|
|
80
|
-
|
81
|
-
nodeId: node_id,
|
82
|
-
selector: selector)["nodeId"]
|
83
|
-
build_node(id)
|
76
|
+
evaluate_async(code, @page.timeout, selector, within)
|
84
77
|
end
|
85
78
|
|
86
|
-
|
79
|
+
def at_css(selector, within: nil)
|
80
|
+
code = <<~JS
|
81
|
+
let selector = arguments[0];
|
82
|
+
let within = arguments[1] || document;
|
83
|
+
let result = within.querySelector(selector);
|
84
|
+
arguments[2](result);
|
85
|
+
JS
|
87
86
|
|
88
|
-
|
89
|
-
description = @page.command("DOM.describeNode", nodeId: node_id)
|
90
|
-
Node.new(self, @page.target_id, node_id, description["node"])
|
87
|
+
evaluate_async(code, @page.timeout, selector, within)
|
91
88
|
end
|
92
89
|
end
|
93
90
|
end
|
data/lib/ferrum/frame/runtime.rb
CHANGED
@@ -85,8 +85,10 @@ module Ferrum
|
|
85
85
|
options.merge!(returnByValue: by_value)
|
86
86
|
|
87
87
|
response = @page.command("Runtime.callFunctionOn",
|
88
|
-
|
89
|
-
|
88
|
+
wait: wait, slowmoable: true,
|
89
|
+
**options)
|
90
|
+
handle_error(response)
|
91
|
+
response = response["result"]
|
90
92
|
|
91
93
|
by_value ? response.dig("value") : handle_response(response)
|
92
94
|
end
|
@@ -137,14 +139,18 @@ module Ferrum
|
|
137
139
|
end
|
138
140
|
|
139
141
|
response = @page.command("Runtime.callFunctionOn",
|
140
|
-
|
142
|
+
slowmoable: true,
|
143
|
+
**params)
|
144
|
+
handle_error(response)
|
145
|
+
response = response["result"]
|
141
146
|
|
142
147
|
handle ? handle_response(response) : response
|
143
148
|
end
|
144
149
|
end
|
145
150
|
|
146
151
|
# FIXME: We should have a central place to handle all type of errors
|
147
|
-
def handle_error(
|
152
|
+
def handle_error(response)
|
153
|
+
result = response["result"]
|
148
154
|
return if result["subtype"] != "error"
|
149
155
|
|
150
156
|
case result["description"]
|
@@ -220,37 +226,38 @@ module Ferrum
|
|
220
226
|
end
|
221
227
|
|
222
228
|
def cyclic?(object_id)
|
223
|
-
@page.command(
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
const seen = [];
|
234
|
-
function detectCycle(obj) {
|
235
|
-
if (typeof obj === "object") {
|
236
|
-
if (seen.indexOf(obj) !== -1) {
|
237
|
-
return true;
|
238
|
-
}
|
239
|
-
seen.push(obj);
|
240
|
-
for (let key in obj) {
|
241
|
-
if (obj.hasOwnProperty(key) && detectCycle(obj[key])) {
|
242
|
-
return true;
|
243
|
-
}
|
244
|
-
}
|
245
|
-
}
|
229
|
+
@page.command(
|
230
|
+
"Runtime.callFunctionOn",
|
231
|
+
objectId: object_id,
|
232
|
+
returnByValue: true,
|
233
|
+
functionDeclaration: <<~JS
|
234
|
+
function() {
|
235
|
+
if (Array.isArray(this) &&
|
236
|
+
this.every(e => e instanceof Node)) {
|
237
|
+
return false;
|
238
|
+
}
|
246
239
|
|
247
|
-
|
240
|
+
const seen = [];
|
241
|
+
function detectCycle(obj) {
|
242
|
+
if (typeof obj === "object") {
|
243
|
+
if (seen.indexOf(obj) !== -1) {
|
244
|
+
return true;
|
245
|
+
}
|
246
|
+
seen.push(obj);
|
247
|
+
for (let key in obj) {
|
248
|
+
if (obj.hasOwnProperty(key) && detectCycle(obj[key])) {
|
249
|
+
return true;
|
248
250
|
}
|
249
|
-
|
250
|
-
return detectCycle(this);
|
251
251
|
}
|
252
|
-
|
253
|
-
|
252
|
+
}
|
253
|
+
|
254
|
+
return false;
|
255
|
+
}
|
256
|
+
|
257
|
+
return detectCycle(this);
|
258
|
+
}
|
259
|
+
JS
|
260
|
+
)
|
254
261
|
end
|
255
262
|
end
|
256
263
|
end
|
data/lib/ferrum/keyboard.rb
CHANGED
@@ -33,13 +33,13 @@ module Ferrum
|
|
33
33
|
def down(key)
|
34
34
|
key = normalize_keys(Array(key))
|
35
35
|
type = key[:text] ? "keyDown" : "rawKeyDown"
|
36
|
-
@page.command("Input.dispatchKeyEvent", type: type, **key)
|
36
|
+
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: type, **key)
|
37
37
|
self
|
38
38
|
end
|
39
39
|
|
40
40
|
def up(key)
|
41
41
|
key = normalize_keys(Array(key))
|
42
|
-
@page.command("Input.dispatchKeyEvent", type: "keyUp", **key)
|
42
|
+
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: "keyUp", **key)
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
@@ -49,7 +49,7 @@ module Ferrum
|
|
49
49
|
keys.each do |key|
|
50
50
|
type = key[:text] ? "keyDown" : "rawKeyDown"
|
51
51
|
@page.command("Input.dispatchKeyEvent", type: type, **key)
|
52
|
-
@page.command("Input.dispatchKeyEvent", type: "keyUp", **key)
|
52
|
+
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: "keyUp", **key)
|
53
53
|
end
|
54
54
|
|
55
55
|
self
|
data/lib/ferrum/mouse.rb
CHANGED
@@ -32,10 +32,21 @@ module Ferrum
|
|
32
32
|
tap { mouse_event(type: "mouseReleased", **options) }
|
33
33
|
end
|
34
34
|
|
35
|
-
# FIXME: steps
|
36
35
|
def move(x:, y:, steps: 1)
|
36
|
+
from_x, from_y = @x, @y
|
37
37
|
@x, @y = x, y
|
38
|
-
|
38
|
+
|
39
|
+
steps.times do |i|
|
40
|
+
new_x = from_x + (@x - from_x) * ((i + 1) / steps.to_f)
|
41
|
+
new_y = from_y + (@y - from_y) * ((i + 1) / steps.to_f)
|
42
|
+
|
43
|
+
@page.command("Input.dispatchMouseEvent",
|
44
|
+
slowmoable: true,
|
45
|
+
type: "mouseMoved",
|
46
|
+
x: new_x.to_i,
|
47
|
+
y: new_y.to_i)
|
48
|
+
end
|
49
|
+
|
39
50
|
self
|
40
51
|
end
|
41
52
|
|
@@ -45,7 +56,7 @@ module Ferrum
|
|
45
56
|
button = validate_button(button)
|
46
57
|
options = { x: @x, y: @y, type: type, button: button, clickCount: count }
|
47
58
|
options.merge!(modifiers: modifiers) if modifiers
|
48
|
-
@page.command("Input.dispatchMouseEvent", wait: wait, **options)
|
59
|
+
@page.command("Input.dispatchMouseEvent", wait: wait, slowmoable: true, **options)
|
49
60
|
end
|
50
61
|
|
51
62
|
def validate_button(button)
|
data/lib/ferrum/node.rb
CHANGED
@@ -24,7 +24,7 @@ module Ferrum
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def focus
|
27
|
-
tap { page.command("DOM.focus", nodeId: node_id) }
|
27
|
+
tap { page.command("DOM.focus", slowmoable: true, nodeId: node_id) }
|
28
28
|
end
|
29
29
|
|
30
30
|
def blur
|
@@ -37,22 +37,23 @@ module Ferrum
|
|
37
37
|
|
38
38
|
# mode: (:left | :right | :double)
|
39
39
|
# keys: (:alt, (:ctrl | :control), (:meta | :command), :shift)
|
40
|
-
# offset: { :x, :y }
|
41
|
-
def click(mode: :left, keys: [], offset: {})
|
42
|
-
x, y = find_position(offset
|
40
|
+
# offset: { :x, :y, :position (:top | :center) }
|
41
|
+
def click(mode: :left, keys: [], offset: {}, delay: 0)
|
42
|
+
x, y = find_position(**offset)
|
43
43
|
modifiers = page.keyboard.modifiers(keys)
|
44
44
|
|
45
45
|
case mode
|
46
46
|
when :right
|
47
47
|
page.mouse.move(x: x, y: y)
|
48
48
|
page.mouse.down(button: :right, modifiers: modifiers)
|
49
|
+
sleep(delay)
|
49
50
|
page.mouse.up(button: :right, modifiers: modifiers)
|
50
51
|
when :double
|
51
52
|
page.mouse.move(x: x, y: y)
|
52
53
|
page.mouse.down(modifiers: modifiers, count: 2)
|
53
54
|
page.mouse.up(modifiers: modifiers, count: 2)
|
54
55
|
when :left
|
55
|
-
page.mouse.click(x: x, y: y, modifiers: modifiers)
|
56
|
+
page.mouse.click(x: x, y: y, modifiers: modifiers, delay: delay)
|
56
57
|
end
|
57
58
|
|
58
59
|
self
|
@@ -63,7 +64,7 @@ module Ferrum
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def select_file(value)
|
66
|
-
page.command("DOM.setFileInputFiles", nodeId: node_id, files: Array(value))
|
67
|
+
page.command("DOM.setFileInputFiles", slowmoable: true, nodeId: node_id, files: Array(value))
|
67
68
|
end
|
68
69
|
|
69
70
|
def at_xpath(selector)
|
@@ -119,20 +120,31 @@ module Ferrum
|
|
119
120
|
%(#<#{self.class} @target_id=#{@target_id.inspect} @node_id=#{@node_id} @description=#{@description.inspect}>)
|
120
121
|
end
|
121
122
|
|
122
|
-
def find_position(
|
123
|
+
def find_position(x: nil, y: nil, position: :top)
|
124
|
+
offset_x, offset_y = x, y
|
123
125
|
quads = get_content_quads
|
124
|
-
|
126
|
+
x = y = nil
|
125
127
|
|
126
|
-
if offset_x
|
128
|
+
if offset_x && offset_y && position == :top
|
127
129
|
point = quads.first
|
128
|
-
|
130
|
+
x = point[:x] + offset_x.to_i
|
131
|
+
y = point[:y] + offset_y.to_i
|
129
132
|
else
|
130
133
|
x, y = quads.inject([0, 0]) do |memo, point|
|
131
134
|
[memo[0] + point[:x],
|
132
135
|
memo[1] + point[:y]]
|
133
136
|
end
|
134
|
-
|
137
|
+
|
138
|
+
x = x / 4
|
139
|
+
y = y / 4
|
140
|
+
end
|
141
|
+
|
142
|
+
if offset_x && offset_y && position == :center
|
143
|
+
x = x + offset_x.to_i
|
144
|
+
y = y + offset_y.to_i
|
135
145
|
end
|
146
|
+
|
147
|
+
[x, y]
|
136
148
|
end
|
137
149
|
|
138
150
|
private
|
data/lib/ferrum/page.rb
CHANGED
@@ -31,7 +31,7 @@ module Ferrum
|
|
31
31
|
|
32
32
|
extend Forwardable
|
33
33
|
delegate %i[at_css at_xpath css xpath
|
34
|
-
current_url current_title url title body doctype
|
34
|
+
current_url current_title url title body doctype set_content
|
35
35
|
execution_id evaluate evaluate_on evaluate_async execute
|
36
36
|
add_script_tag add_style_tag] => :main_frame
|
37
37
|
|
@@ -44,13 +44,14 @@ module Ferrum
|
|
44
44
|
|
45
45
|
def initialize(target_id, browser)
|
46
46
|
@frames = {}
|
47
|
+
@main_frame = Frame.new(nil, self)
|
47
48
|
@target_id, @browser = target_id, browser
|
48
49
|
@event = Event.new.tap(&:set)
|
49
50
|
|
50
51
|
host = @browser.process.host
|
51
52
|
port = @browser.process.port
|
52
53
|
ws_url = "ws://#{host}:#{port}/devtools/page/#{@target_id}"
|
53
|
-
@client = Browser::Client.new(browser, ws_url, 1000)
|
54
|
+
@client = Browser::Client.new(browser, ws_url, id_starts_with: 1000)
|
54
55
|
|
55
56
|
@mouse, @keyboard = Mouse.new(self), Keyboard.new(self)
|
56
57
|
@headers, @cookies = Headers.new(self), Cookies.new(self)
|
@@ -99,7 +100,8 @@ module Ferrum
|
|
99
100
|
@browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { width: width, height: height, windowState: "normal" })
|
100
101
|
end
|
101
102
|
|
102
|
-
command("Emulation.setDeviceMetricsOverride",
|
103
|
+
command("Emulation.setDeviceMetricsOverride", slowmoable: true,
|
104
|
+
width: width,
|
103
105
|
height: height,
|
104
106
|
deviceScaleFactor: 1,
|
105
107
|
mobile: false,
|
@@ -107,10 +109,14 @@ module Ferrum
|
|
107
109
|
end
|
108
110
|
|
109
111
|
def refresh
|
110
|
-
command("Page.reload", wait: timeout)
|
112
|
+
command("Page.reload", wait: timeout, slowmoable: true)
|
111
113
|
end
|
112
114
|
alias_method :reload, :refresh
|
113
115
|
|
116
|
+
def stop
|
117
|
+
command("Page.stopLoading", slowmoable: true)
|
118
|
+
end
|
119
|
+
|
114
120
|
def back
|
115
121
|
history_navigate(delta: -1)
|
116
122
|
end
|
@@ -125,9 +131,11 @@ module Ferrum
|
|
125
131
|
enabled
|
126
132
|
end
|
127
133
|
|
128
|
-
def command(method, wait: 0, **params)
|
134
|
+
def command(method, wait: 0, slowmoable: false, **params)
|
129
135
|
iteration = @event.reset if wait > 0
|
136
|
+
sleep(@browser.slowmo) if slowmoable && @browser.slowmo > 0
|
130
137
|
result = @client.command(method, params)
|
138
|
+
|
131
139
|
if wait > 0
|
132
140
|
@event.wait(wait) # Wait a bit after command and check if iteration has
|
133
141
|
# changed which means there was some network event for
|
@@ -236,7 +244,9 @@ module Ferrum
|
|
236
244
|
|
237
245
|
if entry = entries[index + delta]
|
238
246
|
# Potential wait because of network event
|
239
|
-
command("Page.navigateToHistoryEntry", wait: Mouse::CLICK_WAIT,
|
247
|
+
command("Page.navigateToHistoryEntry", wait: Mouse::CLICK_WAIT,
|
248
|
+
slowmoable: true,
|
249
|
+
entryId: entry["id"])
|
240
250
|
end
|
241
251
|
end
|
242
252
|
|
data/lib/ferrum/page/frames.rb
CHANGED
@@ -73,21 +73,27 @@ module Ferrum
|
|
73
73
|
on("Runtime.executionContextCreated") do |params|
|
74
74
|
context_id = params.dig("context", "id")
|
75
75
|
frame_id = params.dig("context", "auxData", "frameId")
|
76
|
+
|
77
|
+
unless @main_frame.id
|
78
|
+
@main_frame.id = frame_id
|
79
|
+
@frames[frame_id] = @main_frame
|
80
|
+
end
|
81
|
+
|
76
82
|
frame = @frames[frame_id] || Frame.new(frame_id, self)
|
77
|
-
frame.
|
83
|
+
frame.set_execution_id(context_id)
|
78
84
|
|
79
|
-
@main_frame ||= frame
|
80
85
|
@frames[frame_id] ||= frame
|
81
86
|
end
|
82
87
|
|
83
88
|
on("Runtime.executionContextDestroyed") do |params|
|
84
89
|
execution_id = params["executionContextId"]
|
85
90
|
frame = frames.find { |f| f.execution_id?(execution_id) }
|
86
|
-
frame.
|
91
|
+
frame.reset_execution_id
|
87
92
|
end
|
88
93
|
|
89
94
|
on("Runtime.executionContextsCleared") do
|
90
95
|
@frames.delete_if { |_, f| !f.main? }
|
96
|
+
@main_frame.reset_execution_id
|
91
97
|
end
|
92
98
|
end
|
93
99
|
|
data/lib/ferrum/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ferrum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.8'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Vorotilin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: websocket-driver
|
@@ -231,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
231
|
- !ruby/object:Gem::Version
|
232
232
|
version: '0'
|
233
233
|
requirements: []
|
234
|
-
rubygems_version: 3.
|
234
|
+
rubygems_version: 3.1.2
|
235
235
|
signing_key:
|
236
236
|
specification_version: 4
|
237
237
|
summary: Ruby headless Chrome driver
|