Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/rust/src/chrome.rs
2885 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
use crate::config::ManagerConfig;
19
use crate::config::ARCH::{ARM64, X32};
20
use crate::config::OS::{LINUX, MACOS, WINDOWS};
21
use crate::downloads::{parse_json_from_url, read_version_from_link};
22
use crate::files::{compose_driver_path_in_cache, BrowserPath};
23
use crate::logger::Logger;
24
use crate::metadata::{
25
create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata,
26
};
27
use crate::{
28
create_http_client, format_three_args, SeleniumManager, BETA, DASH_DASH_VERSION, DEV, NIGHTLY,
29
OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE,
30
UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
31
};
32
use anyhow::anyhow;
33
use anyhow::Error;
34
use reqwest::Client;
35
use serde::{Deserialize, Serialize};
36
use std::collections::HashMap;
37
use std::option::Option;
38
use std::path::{Path, PathBuf};
39
use std::sync::mpsc;
40
use std::sync::mpsc::{Receiver, Sender};
41
42
pub const CHROME_NAME: &str = "chrome";
43
pub const CHROMEDRIVER_NAME: &str = "chromedriver";
44
const DRIVER_URL: &str = "https://chromedriver.storage.googleapis.com/";
45
const LATEST_RELEASE: &str = "LATEST_RELEASE";
46
const CFT_URL: &str = "https://googlechromelabs.github.io/chrome-for-testing/";
47
const GOOD_VERSIONS_ENDPOINT: &str = "known-good-versions-with-downloads.json";
48
const LATEST_VERSIONS_ENDPOINT: &str = "last-known-good-versions-with-downloads.json";
49
const CFT_MACOS_APP_NAME: &str =
50
"Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing";
51
const MIN_CHROME_VERSION_CFT: i32 = 113;
52
const MIN_CHROMEDRIVER_VERSION_CFT: i32 = 115;
53
const CHROMIUM_SNAP_LINK: &str = "/snap/bin/chromium";
54
const CHROMIUM_SNAP_BINARY: &str = "/snap/chromium/current/usr/lib/chromium-browser/chrome";
55
56
pub struct ChromeManager {
57
pub browser_name: &'static str,
58
pub driver_name: &'static str,
59
pub config: ManagerConfig,
60
pub http_client: Client,
61
pub log: Logger,
62
pub tx: Sender<String>,
63
pub rx: Receiver<String>,
64
pub download_browser: bool,
65
pub driver_url: Option<String>,
66
pub browser_url: Option<String>,
67
}
68
69
impl ChromeManager {
70
pub fn new() -> Result<Box<Self>, Error> {
71
let browser_name = CHROME_NAME;
72
let driver_name = CHROMEDRIVER_NAME;
73
let config = ManagerConfig::default(browser_name, driver_name);
74
let default_timeout = config.timeout.to_owned();
75
let default_proxy = &config.proxy;
76
let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
77
Ok(Box::new(ChromeManager {
78
browser_name,
79
driver_name,
80
http_client: create_http_client(default_timeout, default_proxy)?,
81
config,
82
log: Logger::new(),
83
tx,
84
rx,
85
download_browser: false,
86
driver_url: None,
87
browser_url: None,
88
}))
89
}
90
91
fn create_latest_release_url(&self) -> String {
92
format!(
93
"{}{}",
94
self.get_driver_mirror_url_or_default(DRIVER_URL),
95
LATEST_RELEASE
96
)
97
}
98
99
fn create_latest_release_with_version_url(&self) -> String {
100
format!(
101
"{}{}_{}",
102
self.get_driver_mirror_url_or_default(DRIVER_URL),
103
LATEST_RELEASE,
104
self.get_major_browser_version()
105
)
106
}
107
108
fn create_cft_url(&self, base_url: &str, endpoint: &str) -> String {
109
format!("{}{}", base_url, endpoint)
110
}
111
112
fn create_cft_url_for_browsers(&self, endpoint: &str) -> String {
113
self.create_cft_url(&self.get_browser_mirror_url_or_default(CFT_URL), endpoint)
114
}
115
116
fn create_cft_url_for_drivers(&self, endpoint: &str) -> String {
117
self.create_cft_url(&self.get_driver_mirror_url_or_default(CFT_URL), endpoint)
118
}
119
120
fn request_driver_version_from_latest(&self, driver_url: &str) -> Result<String, Error> {
121
self.log.debug(format!(
122
"Reading {} version from {}",
123
&self.driver_name, driver_url
124
));
125
read_version_from_link(self.get_http_client(), driver_url, self.get_logger())
126
}
127
128
fn request_versions_from_online<T>(&self, driver_url: &str) -> Result<T, Error>
129
where
130
T: Serialize + for<'a> Deserialize<'a>,
131
{
132
self.log
133
.debug(format!("Discovering versions from {}", driver_url));
134
parse_json_from_url::<T>(self.get_http_client(), driver_url)
135
}
136
137
fn request_latest_driver_version_from_online(&mut self) -> Result<String, Error> {
138
let driver_name = self.driver_name;
139
self.get_logger().trace(format!(
140
"Using Chrome for Testing (CfT) endpoints to find out latest stable {} version",
141
driver_name
142
));
143
144
let latest_versions_url = self.create_cft_url_for_drivers(LATEST_VERSIONS_ENDPOINT);
145
let versions_with_downloads =
146
self.request_versions_from_online::<LatestVersionsWithDownloads>(&latest_versions_url)?;
147
let stable_channel = versions_with_downloads.channels.stable;
148
let chromedriver = stable_channel.downloads.chromedriver;
149
if chromedriver.is_none() {
150
self.log.warn(format!(
151
"Latest stable version of {} not found using CfT endpoints. Trying with {}",
152
&self.driver_name, LATEST_RELEASE
153
));
154
return self.request_driver_version_from_latest(&self.create_latest_release_url());
155
}
156
157
let platform_url: Vec<&PlatformUrl> = chromedriver
158
.as_ref()
159
.unwrap()
160
.iter()
161
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
162
.collect();
163
self.log.trace(format!(
164
"CfT URLs for downloading {}: {:?}",
165
self.get_driver_name(),
166
platform_url
167
));
168
self.driver_url = Some(platform_url.first().unwrap().url.to_string());
169
170
Ok(stable_channel.version)
171
}
172
173
fn request_good_driver_version_from_online(&mut self) -> Result<String, Error> {
174
let browser_or_driver_version = if self.get_driver_version().is_empty() {
175
self.get_browser_version()
176
} else {
177
self.get_driver_version()
178
};
179
let version_for_filtering = self.get_major_version(browser_or_driver_version)?;
180
self.log.trace(format!(
181
"Driver version used to request CfT: {version_for_filtering}"
182
));
183
184
let good_versions_url = self.create_cft_url_for_drivers(GOOD_VERSIONS_ENDPOINT);
185
let all_versions =
186
self.request_versions_from_online::<VersionsWithDownloads>(&good_versions_url)?;
187
let filtered_versions: Vec<Version> = all_versions
188
.versions
189
.into_iter()
190
.filter(|r| r.version.starts_with(version_for_filtering.as_str()))
191
.collect();
192
if filtered_versions.is_empty() {
193
return Err(anyhow!(format_three_args(
194
UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG,
195
self.get_driver_name(),
196
&version_for_filtering,
197
&MIN_CHROMEDRIVER_VERSION_CFT.to_string(),
198
)));
199
}
200
201
let driver_version = filtered_versions.last().unwrap();
202
let url: Vec<&PlatformUrl> = driver_version
203
.downloads
204
.chromedriver
205
.as_ref()
206
.unwrap()
207
.iter()
208
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
209
.collect();
210
self.log.trace(format!("URLs for CfT: {:?}", url));
211
self.driver_url = Some(url.first().unwrap().url.to_string());
212
213
Ok(driver_version.version.to_string())
214
}
215
}
216
217
impl SeleniumManager for ChromeManager {
218
fn get_browser_name(&self) -> &str {
219
self.browser_name
220
}
221
222
fn get_browser_names_in_path(&self) -> Vec<&str> {
223
vec![self.get_browser_name(), "chromium-browser", "chromium"]
224
}
225
226
fn get_http_client(&self) -> &Client {
227
&self.http_client
228
}
229
230
fn set_http_client(&mut self, http_client: Client) {
231
self.http_client = http_client;
232
}
233
234
fn get_browser_path_map(&self) -> HashMap<BrowserPath, &str> {
235
HashMap::from([
236
(
237
BrowserPath::new(WINDOWS, STABLE),
238
r"Google\Chrome\Application\chrome.exe",
239
),
240
(
241
BrowserPath::new(WINDOWS, BETA),
242
r"Google\Chrome Beta\Application\chrome.exe",
243
),
244
(
245
BrowserPath::new(WINDOWS, DEV),
246
r"Google\Chrome Dev\Application\chrome.exe",
247
),
248
(
249
BrowserPath::new(WINDOWS, NIGHTLY),
250
r"Google\Chrome SxS\Application\chrome.exe",
251
),
252
(
253
BrowserPath::new(MACOS, STABLE),
254
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
255
),
256
(
257
BrowserPath::new(MACOS, BETA),
258
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
259
),
260
(
261
BrowserPath::new(MACOS, DEV),
262
"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
263
),
264
(
265
BrowserPath::new(MACOS, NIGHTLY),
266
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
267
),
268
(BrowserPath::new(LINUX, STABLE), "/usr/bin/google-chrome"),
269
(BrowserPath::new(LINUX, BETA), "/usr/bin/google-chrome-beta"),
270
(
271
BrowserPath::new(LINUX, DEV),
272
"/usr/bin/google-chrome-unstable",
273
),
274
])
275
}
276
277
fn discover_browser_version(&mut self) -> Result<Option<String>, Error> {
278
self.general_discover_browser_version(
279
r"HKCU\Software\Google\Chrome\BLBeacon",
280
REG_VERSION_ARG,
281
DASH_DASH_VERSION,
282
)
283
}
284
285
fn get_driver_name(&self) -> &str {
286
self.driver_name
287
}
288
289
fn request_driver_version(&mut self) -> Result<String, Error> {
290
let major_browser_version_binding = self.get_major_browser_version();
291
let major_browser_version = major_browser_version_binding.as_str();
292
let cache_path = self.get_cache_path()?;
293
let mut metadata = get_metadata(self.get_logger(), &cache_path);
294
295
match get_driver_version_from_metadata(
296
&metadata.drivers,
297
self.driver_name,
298
major_browser_version,
299
) {
300
Some(driver_version) => {
301
self.log.trace(format!(
302
"Driver TTL is valid. Getting {} version from metadata",
303
&self.driver_name
304
));
305
Ok(driver_version)
306
}
307
_ => {
308
self.assert_online_or_err(OFFLINE_REQUEST_ERR_MSG)?;
309
310
let major_browser_version_int =
311
major_browser_version.parse::<i32>().unwrap_or_default();
312
let driver_version = if self.is_browser_version_stable()
313
|| major_browser_version.is_empty()
314
|| self.is_browser_version_unstable()
315
{
316
// For discovering the latest driver version, the CfT endpoints are also used
317
self.request_latest_driver_version_from_online()?
318
} else if !major_browser_version.is_empty()
319
&& major_browser_version_int < MIN_CHROMEDRIVER_VERSION_CFT
320
{
321
// For old versions (chromedriver 114-), the traditional method should work:
322
// https://chromedriver.chromium.org/downloads
323
self.request_driver_version_from_latest(
324
&self.create_latest_release_with_version_url(),
325
)?
326
} else {
327
// As of chromedriver 115+, the metadata for version discovery are published
328
// by the "Chrome for Testing" (CfT) JSON endpoints:
329
// https://googlechromelabs.github.io/chrome-for-testing/
330
self.request_good_driver_version_from_online()?
331
};
332
333
let driver_ttl = self.get_ttl();
334
if driver_ttl > 0 && !major_browser_version.is_empty() && !driver_version.is_empty()
335
{
336
metadata.drivers.push(create_driver_metadata(
337
major_browser_version,
338
self.driver_name,
339
&driver_version,
340
driver_ttl,
341
));
342
write_metadata(&metadata, self.get_logger(), cache_path);
343
}
344
Ok(driver_version)
345
}
346
}
347
}
348
349
fn request_browser_version(&mut self) -> Result<Option<String>, Error> {
350
self.general_request_browser_version(self.browser_name)
351
}
352
353
fn get_driver_url(&mut self) -> Result<String, Error> {
354
let major_driver_version = self
355
.get_major_driver_version()
356
.parse::<i32>()
357
.unwrap_or_default();
358
359
if major_driver_version >= MIN_CHROMEDRIVER_VERSION_CFT && self.driver_url.is_none() {
360
// This case happens when driver_version is set (e.g. using CLI flag)
361
self.request_good_driver_version_from_online()?;
362
}
363
364
// As of Chrome 115+, the driver URL is already gathered thanks to the CfT endpoints
365
if self.driver_url.is_some() {
366
return Ok(self.driver_url.as_ref().unwrap().to_string());
367
}
368
369
let driver_version = self.get_driver_version();
370
let os = self.get_os();
371
let arch = self.get_arch();
372
let driver_label = if WINDOWS.is(os) {
373
"win32"
374
} else if MACOS.is(os) {
375
if ARM64.is(arch) {
376
// As of chromedriver 106, the naming convention for macOS ARM64 releases changed. See:
377
// https://groups.google.com/g/chromedriver-users/c/JRuQzH3qr2c
378
if major_driver_version < 106 {
379
"mac64_m1"
380
} else {
381
"mac_arm64"
382
}
383
} else {
384
"mac64"
385
}
386
} else {
387
"linux64"
388
};
389
Ok(format!(
390
"{}{}/{}_{}.zip",
391
self.get_driver_mirror_url_or_default(DRIVER_URL),
392
driver_version,
393
self.driver_name,
394
driver_label
395
))
396
}
397
398
fn get_driver_path_in_cache(&self) -> Result<PathBuf, Error> {
399
Ok(compose_driver_path_in_cache(
400
self.get_cache_path()?.unwrap_or_default(),
401
self.driver_name,
402
self.get_os(),
403
self.get_platform_label(),
404
self.get_driver_version(),
405
))
406
}
407
408
fn get_config(&self) -> &ManagerConfig {
409
&self.config
410
}
411
412
fn get_config_mut(&mut self) -> &mut ManagerConfig {
413
&mut self.config
414
}
415
416
fn set_config(&mut self, config: ManagerConfig) {
417
self.config = config;
418
}
419
420
fn get_logger(&self) -> &Logger {
421
&self.log
422
}
423
424
fn set_logger(&mut self, log: Logger) {
425
self.log = log;
426
}
427
428
fn get_sender(&self) -> &Sender<String> {
429
&self.tx
430
}
431
432
fn get_receiver(&self) -> &Receiver<String> {
433
&self.rx
434
}
435
436
fn get_platform_label(&self) -> &str {
437
let os = self.get_os();
438
let arch = self.get_arch();
439
if WINDOWS.is(os) {
440
if X32.is(arch) {
441
"win32"
442
} else {
443
"win64"
444
}
445
} else if MACOS.is(os) {
446
if ARM64.is(arch) {
447
"mac-arm64"
448
} else {
449
"mac-x64"
450
}
451
} else {
452
"linux64"
453
}
454
}
455
456
fn request_latest_browser_version_from_online(
457
&mut self,
458
_browser_version: &str,
459
) -> Result<String, Error> {
460
let browser_name = self.browser_name;
461
self.get_logger().trace(format!(
462
"Using Chrome for Testing (CfT) endpoints to find out latest stable {} version",
463
browser_name
464
));
465
466
let latest_versions_url = self.create_cft_url_for_browsers(LATEST_VERSIONS_ENDPOINT);
467
let versions_with_downloads =
468
self.request_versions_from_online::<LatestVersionsWithDownloads>(&latest_versions_url)?;
469
let stable_channel = versions_with_downloads.channels.stable;
470
let chrome = stable_channel.downloads.chrome;
471
472
let platform_url: Vec<&PlatformUrl> = chrome
473
.iter()
474
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
475
.collect();
476
self.log.trace(format!(
477
"CfT URLs for downloading {}: {:?}",
478
self.get_browser_name(),
479
platform_url
480
));
481
let browser_version = stable_channel.version;
482
self.browser_url = Some(platform_url.first().unwrap().url.to_string());
483
484
Ok(browser_version)
485
}
486
487
fn request_fixed_browser_version_from_online(
488
&mut self,
489
_browser_version: &str,
490
) -> Result<String, Error> {
491
let browser_name = self.browser_name;
492
let mut browser_version = self.get_browser_version().to_string();
493
let major_browser_version = self.get_major_browser_version();
494
self.get_logger().trace(format!(
495
"Using Chrome for Testing (CfT) endpoints to find out {} {}",
496
browser_name, major_browser_version
497
));
498
499
if self.is_browser_version_unstable() {
500
let latest_versions_url = self.create_cft_url_for_browsers(LATEST_VERSIONS_ENDPOINT);
501
let versions_with_downloads = self
502
.request_versions_from_online::<LatestVersionsWithDownloads>(
503
&latest_versions_url,
504
)?;
505
let channel = if browser_version.eq_ignore_ascii_case(BETA) {
506
versions_with_downloads.channels.beta
507
} else if browser_version.eq_ignore_ascii_case(DEV) {
508
versions_with_downloads.channels.dev
509
} else {
510
versions_with_downloads.channels.canary
511
};
512
browser_version = channel.version;
513
let platform_url: Vec<&PlatformUrl> = channel
514
.downloads
515
.chrome
516
.iter()
517
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
518
.collect();
519
self.browser_url = Some(platform_url.first().unwrap().url.to_string());
520
521
Ok(browser_version)
522
} else {
523
let good_versions_url = self.create_cft_url_for_browsers(GOOD_VERSIONS_ENDPOINT);
524
let all_versions =
525
self.request_versions_from_online::<VersionsWithDownloads>(&good_versions_url)?;
526
let iter_versions = all_versions.versions.into_iter();
527
let filtered_versions: Vec<Version> = if self.is_browser_version_specific() {
528
iter_versions
529
.filter(|r| r.version.eq(browser_version.as_str()))
530
.collect()
531
} else {
532
iter_versions
533
.filter(|r| r.version.starts_with(major_browser_version.as_str()))
534
.collect()
535
};
536
if filtered_versions.is_empty() {
537
return self.unavailable_download();
538
}
539
let last_browser = filtered_versions.last().unwrap();
540
let platform_url: Vec<&PlatformUrl> = last_browser
541
.downloads
542
.chrome
543
.iter()
544
.filter(|p| p.platform.eq_ignore_ascii_case(self.get_platform_label()))
545
.collect();
546
self.browser_url = Some(platform_url.first().unwrap().url.to_string());
547
548
Ok(last_browser.version.to_string())
549
}
550
}
551
552
fn get_min_browser_version_for_download(&self) -> Result<i32, Error> {
553
Ok(MIN_CHROME_VERSION_CFT)
554
}
555
556
fn get_browser_binary_path(&mut self, _browser_version: &str) -> Result<PathBuf, Error> {
557
let browser_in_cache = self.get_browser_path_in_cache()?;
558
if MACOS.is(self.get_os()) {
559
Ok(browser_in_cache.join(CFT_MACOS_APP_NAME))
560
} else {
561
Ok(browser_in_cache.join(self.get_browser_name_with_extension()))
562
}
563
}
564
565
fn get_browser_url_for_download(&mut self, browser_version: &str) -> Result<String, Error> {
566
if let Some(browser_url) = self.browser_url.clone() {
567
Ok(browser_url)
568
} else {
569
if self.is_browser_version_stable() || self.is_browser_version_empty() {
570
self.request_latest_browser_version_from_online(browser_version)?;
571
} else {
572
self.request_fixed_browser_version_from_online(browser_version)?;
573
}
574
Ok(self.browser_url.clone().unwrap())
575
}
576
}
577
578
fn get_browser_label_for_download(
579
&self,
580
_browser_version: &str,
581
) -> Result<Option<&str>, Error> {
582
Ok(None)
583
}
584
585
fn is_download_browser(&self) -> bool {
586
self.download_browser
587
}
588
589
fn set_download_browser(&mut self, download_browser: bool) {
590
self.download_browser = download_browser;
591
}
592
593
fn is_snap(&self, browser_path: &str) -> bool {
594
LINUX.is(self.get_os())
595
&& (browser_path.eq(CHROMIUM_SNAP_LINK) || browser_path.eq(CHROMIUM_SNAP_BINARY))
596
}
597
598
fn get_snap_path(&self) -> Option<PathBuf> {
599
Some(Path::new(CHROMIUM_SNAP_BINARY).to_path_buf())
600
}
601
}
602
603
#[derive(Serialize, Deserialize)]
604
pub struct LatestVersionsWithDownloads {
605
pub timestamp: String,
606
pub channels: Channels,
607
}
608
609
#[derive(Serialize, Deserialize)]
610
pub struct Channels {
611
#[serde(rename = "Stable")]
612
pub stable: Channel,
613
#[serde(rename = "Beta")]
614
pub beta: Channel,
615
#[serde(rename = "Dev")]
616
pub dev: Channel,
617
#[serde(rename = "Canary")]
618
pub canary: Channel,
619
}
620
621
#[derive(Serialize, Deserialize, Debug)]
622
pub struct Channel {
623
pub channel: String,
624
pub version: String,
625
pub revision: String,
626
pub downloads: Downloads,
627
}
628
629
#[derive(Serialize, Deserialize, Debug)]
630
pub struct VersionsWithDownloads {
631
pub timestamp: String,
632
pub versions: Vec<Version>,
633
}
634
635
#[derive(Serialize, Deserialize, Debug)]
636
pub struct Version {
637
pub version: String,
638
pub revision: String,
639
pub downloads: Downloads,
640
}
641
642
#[derive(Serialize, Deserialize, Debug)]
643
pub struct Downloads {
644
pub chrome: Vec<PlatformUrl>,
645
pub chromedriver: Option<Vec<PlatformUrl>>,
646
}
647
648
#[derive(Serialize, Deserialize, Debug)]
649
pub struct PlatformUrl {
650
pub platform: String,
651
pub url: String,
652
}
653
654