[IMP] python: Cleanup for new separation of concern

refactor_total
Brett Spaulding 1 year ago
parent 6af3f45af5
commit 842e440bd2

@ -27,11 +27,16 @@ class ProcessArtistQueue extends Command
*/
public function handle()
{
// This queue will prompt the scraping of all artist albums, mark done when complete
$artists = ArtistQueue::where('state', 'pending')->get();
$bar = new ProgressBar($this->output, count($artists));
$bar->start();
foreach ($artists as $artist) {
$artist->state = 'in_progress';
$artist->save();
$artist->process_artist();
$artist->state = 'done';
$artist->save();
$bar->advance();
}
}

@ -0,0 +1,20 @@
<?php
namespace App\Console;
use App\Console\Commands\ProcessArtistQueue;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
protected $commands = [
ProcessArtistQueue::class,
];
protected function schedule(Schedule $schedule): void
{
$schedule->command('app:process-artist-queue')->everyMinute()->withoutOverlapping();
}
}

@ -1,6 +1,5 @@
import json
from apscheduler.schedulers.background import BackgroundScheduler
from database import Model
from flask import Flask, render_template
from redis import Redis
from utils.download import download_album
@ -9,7 +8,6 @@ from utils.processor import process_download
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
Album = Model('album')
def process_downloads():
@ -31,18 +29,10 @@ cron.add_job(process_downloads, 'interval', minutes=1)
cron.start()
@app.route('/')
def index():
# redis.incr('hits')
# counter = 'This Compose/Flask demo has been viewed %s time(s).' % redis.get('hits')
return render_template('base.html')
@app.route('/api/v1/get/artist/<path:path>')
@app.route('/api/v1/process/album')
def get_artist(path):
"""
Process for the requested Artist
Process for the requested Album
:param path: The Artist to get files for
:return: a status
"""

@ -1,20 +0,0 @@
import os
from redis import Redis
redis = Redis(host='redis', port=6379)
BASE_URL = 'https://www.youtube.com'
QUERY_URL = BASE_URL + '/results?search_query='
CWD = os.getcwd()
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
MEDIA_FOLDER = os.path.join(ROOT_DIR, 'music')
ALBUM_CONTAINER_ID = 'shelf-container'
ALBUM_CONTAINER_CLASS = 'ytd-search-refinement-card-renderer'
ALBUM_CONTAINER_ITEMS_XPATH = '/html/body/ytd-app/div[1]/ytd-page-manager/ytd-search/div[1]/ytd-two-column-search-results-renderer/ytd-secondary-search-container-renderer/div/ytd-universal-watch-card-renderer/div[4]/ytd-watch-card-section-sequence-renderer[2]/div/ytd-horizontal-card-list-renderer/div[2]/div[2]'
# ALBUM_CONTAINER_FULL_XPATH = '/html/body/ytd-app/div[1]/ytd-page-manager/ytd-search/div[1]/ytd-two-column-search-results-renderer/ytd-secondary-search-container-renderer/div/ytd-universal-watch-card-renderer/div[4]/ytd-watch-card-section-sequence-renderer[2]/div'
ALBUM_CONTAINER_FULL_XPATH = '/html/body/ytd-app/div[1]/ytd-page-manager/ytd-search/div[1]/ytd-two-column-search-results-renderer/ytd-secondary-search-container-renderer/div/ytd-universal-watch-card-renderer/div[4]/ytd-watch-card-section-sequence-renderer[2]/div/ytd-horizontal-card-list-renderer/div[2]'
BTN_RIGHT_FULL_XPATH = '/html/body/ytd-app/div[1]/ytd-page-manager/ytd-search/div[1]/ytd-two-column-search-results-renderer/ytd-secondary-search-container-renderer/div/ytd-universal-watch-card-renderer/div[4]/ytd-watch-card-section-sequence-renderer[2]/div/ytd-horizontal-card-list-renderer/div[2]/div[3]/div[2]/ytd-button-renderer/yt-button-shape/button'
click_script = """
document.evaluate('%s', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click();
""" % BTN_RIGHT_FULL_XPATH

@ -1,89 +0,0 @@
import json
import operator as oprtr
from const import *
from pysondb import PysonDB
def evaluate_condition(record_field, operator, condition):
return operator(record_field, condition)
def evaluate_operator(op):
if op == '>':
op = oprtr.gt
elif op == '<':
op = oprtr.lt
elif op == '=':
op = oprtr.eq
elif op == '!=':
op = oprtr.ne
else:
raise UserWarning('Invalid Operator: %s' % op)
return op
class Model:
# TODO: Modify some of this to be wrapped into an ENV wrapper that gets loaded in when the server starts and creates
# class objects that can be manipulated easier by things like update_by_id
def __init__(self, name):
self.env = PysonDB(CWD + '/database/%s.json' % name)
def _search(self, records, params):
"""
Iterate through list of condition tuples and append results to a checklist that will evaluate at the end
ex params: [('name', '=', 'John'), ('zip', '!=', '12345')]
:param params: List of tuples
:return: Record to search recordset if True
"""
filtered_record_ids =[]
for record in records:
record_id = self.env.get_by_id(record)
checklist = []
for param in params:
field = param[0]
operator = evaluate_operator(param[1])
condition = param[2]
checklist.append(evaluate_condition(record_id[field], operator, condition))
passed = all(x for x in checklist)
if passed:
record_id.update({'id': record})
filtered_record_ids.append(record_id)
return filtered_record_ids
def search(self, params):
"""
:param params: List of tuples that will be evaluated and return a total list of records
:return: None, List or Single record
"""
records = self.env.get_all()
record_ids = self._search(records, params)
if not record_ids:
record_ids = None
return record_ids
def read(self, record_id):
data = self.env.get_by_id(record_id)
return data
def create(self, vals):
record = self.env.add(vals)
return record
def create_many(self, record_list):
record_ids = self.env.add_many(record_list)
return record_ids
def write(self, record_id, vals):
record = self.env.update_by_id(record_id, vals)
return record
def unlink(self, record_id):
self.env.delete_by_id(record_id)
return True
def purge(self):
self.env.purge()

@ -1,12 +0,0 @@
{
"version": 2,
"keys": [
"album",
"artist",
"cover",
"downloaded",
"downloading",
"link"
],
"data": {}
}

@ -1,20 +1,10 @@
FROM debian:bullseye-slim
RUN apt update && apt upgrade -y
RUN apt install firefox-esr -y
RUN apt install ffmpeg -y
RUN apt install curl python3-pip -y
ADD . /code
WORKDIR /code
# Geckodriver Install for Selenium
# RUN bash geckodriver-install.sh
#RUN json=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest)
#RUN url=$(echo "$json" | jq -r '.assets[].browser_download_url | select(contains("linux64") and endswith("gz"))')
ARG url="https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz"
RUN curl -s -L "$url" | tar -xz
RUN chmod +x geckodriver
RUN mv geckodriver /usr/local/bin
RUN export PATH=$PATH:/usr/local/bin/geckodriver
RUN pip3 install -r requirements.txt
ENV FLASK_APP=app

@ -1,3 +0,0 @@
[target.i686-pc-windows-gnu]
linker = "i686-w64-mingw32-gcc"
rustflags = "-C panic=abort"

File diff suppressed because it is too large Load Diff

@ -1,2 +0,0 @@
Please see our contributor documentation at
https://firefox-source-docs.mozilla.org/testing/geckodriver/#for-developers.

File diff suppressed because it is too large Load Diff

@ -1,51 +0,0 @@
[package]
edition = "2018"
name = "geckodriver"
version = "0.33.0"
authors = ["Mozilla"]
include = [
"/.cargo",
"/build.rs",
"/src"
]
description = "Proxy for using WebDriver clients to interact with Gecko-based browsers."
readme = "README.md"
keywords = [
"firefox",
"httpd",
"mozilla",
"w3c",
"webdriver",
]
license = "MPL-2.0"
repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver"
[dependencies]
base64 = "0.13"
chrono = "0.4.6"
clap = { version = "~3.1", default-features = false, features = ["cargo", "std", "suggestions", "wrap_help"] }
hyper = "0.14"
lazy_static = "1.0"
log = { version = "0.4", features = ["std"] }
marionette = "0.4.0"
mozdevice = "0.5.1"
mozprofile = "0.9.1"
mozrunner = "0.15.1"
mozversion = "0.5.1"
regex = { version="1.0", default-features = false, features = ["perf", "std"] }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.8"
tempfile = "3"
unicode-segmentation = "1.9"
url = "2.0"
uuid = { version = "1.0", features = ["v4"] }
webdriver = "0.48.0"
zip = { version = "0.6", default-features = false, features = ["deflate"] }
[dev-dependencies]
tempfile = "3"
[[bin]]
name = "geckodriver"

@ -1,31 +0,0 @@
## System
* Version: <!-- geckodriver version -->
* Platform: <!-- e.g. Linux/macOS/Windows + version -->
* Firefox: <!-- from the about dialogue -->
* Selenium: <!-- client + version -->
## Testcase
<!--
Please provide a minimal HTML document which permits the problem
to be reproduced.
-->
## Stacktrace
<!--
Error and stacktrace produced by client.
-->
## Trace-level log
<!--
See https://searchfox.org/mozilla-central/source/testing/geckodriver/doc/TraceLogs.md
for how to produce a trace-level log.
For trace logs with more than 20 lines please add its contents as attachment.
-->

@ -1,385 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

@ -1,85 +0,0 @@
geckodriver
===========
Proxy for using W3C [WebDriver] compatible clients to interact with
Gecko-based browsers.
This program provides the HTTP API described by the [WebDriver
protocol] to communicate with Gecko browsers, such as Firefox. It
translates calls into the [Marionette remote protocol] by acting
as a proxy between the local- and remote ends.
[WebDriver protocol]: https://w3c.github.io/webdriver/#protocol
[Marionette remote protocol]: https://firefox-source-docs.mozilla.org/testing/marionette/
[WebDriver]: https://developer.mozilla.org/en-US/docs/Web/WebDriver
Downloads
---------
* [Releases](https://github.com/mozilla/geckodriver/releases/latest)
* [Change log](https://searchfox.org/mozilla-central/source/testing/geckodriver/CHANGES.md)
Documentation
-------------
* [WebDriver] (work in progress)
* [Commands](https://developer.mozilla.org/en-US/docs/Web/WebDriver/Commands)
* [Errors](https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors)
* [Types](https://developer.mozilla.org/en-US/docs/Web/WebDriver/Types)
* [Cross browser testing](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing)
* [Selenium](https://seleniumhq.github.io/docs/) (work in progress)
* [C# API](https://seleniumhq.github.io/selenium/docs/api/dotnet/)
* [JavaScript API](https://seleniumhq.github.io/selenium/docs/api/javascript/)
* [Java API](https://seleniumhq.github.io/selenium/docs/api/java/)
* [Perl API](https://metacpan.org/pod/Selenium::Remote::Driver)
* [Python API](https://seleniumhq.github.io/selenium/docs/api/py/)
* [Ruby API](https://seleniumhq.github.io/selenium/docs/api/rb/)
* [geckodriver usage](https://firefox-source-docs.mozilla.org/testing/geckodriver/Usage.html)
* [Supported platforms](https://firefox-source-docs.mozilla.org/testing/geckodriver/Support.html)
* [Firefox capabilities](https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html)
* [Capabilities example](https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html#capabilities-example)
* [Enabling trace logs](https://firefox-source-docs.mozilla.org/testing/geckodriver/TraceLogs.html)
* [Analyzing crash data from Firefox](https://firefox-source-docs.mozilla.org/testing/geckodriver/CrashReports.html)
* [Contributing](https://firefox-source-docs.mozilla.org/testing/geckodriver/#for-developers)
* [Building](https://firefox-source-docs.mozilla.org/testing/geckodriver/Building.html)
* [Testing](https://firefox-source-docs.mozilla.org/testing/geckodriver/Testing.html)
* [Releasing](https://firefox-source-docs.mozilla.org/testing/geckodriver/Releasing.html)
* [Self-serving an ARM build](https://firefox-source-docs.mozilla.org/testing/geckodriver/ARM.html)
Source code
-----------
geckodriver is made available under the [Mozilla Public License].
Its source code can be found in [mozilla-central] under testing/geckodriver.
This GitHub repository is only used for issue tracking and making releases.
[source code]: https://hg.mozilla.org/mozilla-unified/file/tip/testing/geckodriver
[Mozilla Public License]: https://www.mozilla.org/en-US/MPL/2.0/
[mozilla-central]: https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver
Custom release builds
---------------------
If a binary is not available for your platform, it's possibe to create a custom
build using the [Rust] toolchain. To do this, checkout the release tag for the
version of interest and run `cargo build`. Alternatively the latest version may
be built and installed from `crates.io` using `cargo install geckodriver`.
[Rust]: https://rustup.rs/
Contact
-------
The mailing list for geckodriver discussion is
https://groups.google.com/a/mozilla.org/g/dev-webdriver.
There is also an Element channel to talk about using and developing
geckodriver on `#webdriver:mozilla.org <https://chat.mozilla.org/#/room/#webdriver:mozilla.org>`__

@ -1,136 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Writes build information to ${OUT_DIR}/build-info.rs which is included in
// the program during compilation:
//
// ```no_run
// const COMMIT_HASH: Option<&'static str> = Some("c31a366");
// const COMMIT_DATE: Option<&'static str> = Some("1988-05-10");
// ```
//
// The values are `None` if running hg failed, e.g. if it is not installed or
// if we are not in an hg repo.
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() -> io::Result<()> {
let cur_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let build_info = get_build_info(&cur_dir);
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let mut fh = File::create(out_dir.join("build-info.rs"))?;
writeln!(
fh,
"const COMMIT_HASH: Option<&'static str> = {:?};",
build_info.hash()
)?;
writeln!(
fh,
"const COMMIT_DATE: Option<&'static str> = {:?};",
build_info.date()
)?;
Ok(())
}
fn get_build_info(dir: &Path) -> Box<dyn BuildInfo> {
if Path::exists(&dir.join(".hg")) {
Box::new(Hg {})
} else if Path::exists(&dir.join(".git")) {
Box::new(Git {})
} else if let Some(parent) = dir.parent() {
get_build_info(parent)
} else {
eprintln!("unable to detect vcs");
Box::new(Noop {})
}
}
trait BuildInfo {
fn hash(&self) -> Option<String>;
fn date(&self) -> Option<String>;
}
struct Hg;
impl Hg {
fn exec<I, S>(&self, args: I) -> Option<String>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new("hg")
.env("HGPLAIN", "1")
.args(args)
.output()
.ok()
.and_then(|r| String::from_utf8(r.stdout).ok())
.map(|s| s.trim_end().into())
}
}
impl BuildInfo for Hg {
fn hash(&self) -> Option<String> {
self.exec(["log", "-r.", "-T{node|short}"])
}
fn date(&self) -> Option<String> {
self.exec(["log", "-r.", "-T{date|isodate}"])
}
}
struct Git;
impl Git {
fn exec<I, S>(&self, args: I) -> Option<String>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new("git")
.env("GIT_CONFIG_NOSYSTEM", "1")
.args(args)
.output()
.ok()
.and_then(|r| String::from_utf8(r.stdout).ok())
.map(|s| s.trim_end().into())
}
fn to_hg_sha(&self, git_sha: String) -> Option<String> {
self.exec(["cinnabar", "git2hg", &git_sha])
}
}
impl BuildInfo for Git {
fn hash(&self) -> Option<String> {
self.exec(["rev-parse", "HEAD"])
.and_then(|sha| self.to_hg_sha(sha))
.map(|mut s| {
s.truncate(12);
s
})
}
fn date(&self) -> Option<String> {
self.exec(["log", "-1", "--date=short", "--pretty=format:%cd"])
}
}
struct Noop;
impl BuildInfo for Noop {
fn hash(&self) -> Option<String> {
None
}
fn date(&self) -> Option<String> {
None
}
}

@ -1,50 +0,0 @@
# Self-serving an ARM build
Mozilla announced the intent to deprecate ARMv7 HF builds of
geckodriver in September 2018. This does not mean you can no longer
use geckodriver on ARM systems, and this document explains how you
can self-service a build for ARMv7 HF.
Assuming you have already checked out [central], the steps to
cross-compile ARMv7 from a Linux host system is as follows:
1. If you dont have Rust installed:
```shell
% curl https://sh.rustup.rs -sSf | sh
```
2. Install cross-compiler toolchain:
```shell
% apt install gcc-arm-linux-gnueabihf libc6-armhf-cross libc6-dev-armhf-cross
```
3. Create a new shell, or to reuse the existing shell:
```shell
% source $HOME/.cargo/env
```
4. Install rustc target toolchain:
```shell
% rustup target install armv7-unknown-linux-gnueabihf
```
5. Put this in [testing/geckodriver/.cargo/config]:
```rust
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
```
6. Build geckodriver from testing/geckodriver:
```shell
% cd testing/geckodriver
% cargo build --release --target armv7-unknown-linux-gnueabihf
```
[central]: https://hg.mozilla.org/mozilla-central/
[testing/geckodriver/.cargo/config]: https://searchfox.org/mozilla-central/source/testing/geckodriver/.cargo/config

@ -1,45 +0,0 @@
# Reporting bugs
When opening a new issue or commenting on existing issues, please
make sure discussions are related to concrete technical issues
with geckodriver or Marionette. Questions or discussions are more
appropriate for the [mailing list].
For issue reports to be actionable, it must be clear exactly
what the observed and expected behaviours are, and how to set up
the state required to observe the erroneous behaviour. The most
useful thing to provide is a minimal HTML document which permits
the problem to be reproduced along with a [trace-level log] from
geckodriver showing the exact wire protocol calls made.
Because of the wide variety and different charateristics of clients
used with geckodriver, their stacktraces, logs, and code examples are
typically not very useful as they distract from the actual underlying
cause. **For this reason, we cannot overstate the importance of
always providing the [trace-level log] from geckodriver.** Bugs
relating to a specific client should be filed with that project.
We welcome you to file issues in the [GitHub issue tracker] once you are
confident it has not already been reported. The [ISSUE_TEMPLATE.md]
contains a helpful checklist for things we will want to know about
the affected system, reproduction steps, and logs.
geckodriver development follows a rolling release model as
we dont release patches for older versions. It is therefore
useful to use the tip-of-tree geckodriver binary, or failing this,
the latest release when verifying the problem. geckodriver is only
compatible with the current release channel versions of Firefox, and
it consequently does not help to report bugs that affect outdated
and unsupported Firefoxen. Please always try to verify the issue
in the latest Firefox Nightly before you file your bug.
Once we are satisfied the issue raised is of sufficiently actionable
character, we will continue with triaging it and file a bug where it
is appropriate. Bugs specific to geckodriver will be filed in the
[`Testing :: geckodriver`] component in Bugzilla.
[mailing list]: index.rst/#communication
[trace-level log]: TraceLogs.md
[GitHub issue tracker]: https://github.com/mozilla/geckodriver/issues
[ISSUE_TEMPLATE.md]: https://raw.githubusercontent.com/mozilla/geckodriver/master/ISSUE_TEMPLATE.md
[`Testing :: geckodriver`]: https://bugzilla.mozilla.org/buglist.cgi?component=geckodriver

@ -1,46 +0,0 @@
# Building geckodriver
geckodriver is written in [Rust], a systems programming language
from Mozilla. Crucially, it relies on the [webdriver crate] to
provide the HTTPD and do most of the heavy lifting of marshalling
the WebDriver protocol. geckodriver translates WebDriver [commands],
[responses], and [errors] to the [Marionette protocol], and acts
as a proxy between [WebDriver] and [Marionette].
To build geckodriver:
```shell
% ./mach build testing/geckodriver
```
If you use artifact builds you may build geckodriver using cargo,
since mach in this case does not have a compile environment:
```shell
% cd testing/geckodriver
% cargo build
Compiling geckodriver v0.21.0 (file:///code/gecko/testing/geckodriver)
Finished dev [optimized + debuginfo] target(s) in 7.83s
```
Because all Rust code in central shares the same cargo workspace,
the binary will be put in the `$(topsrcdir)/target` directory.
You can run your freshly built geckodriver this way:
```shell
% ./mach geckodriver -- --other --flags
```
See [Testing](Testing.md) for how to run tests.
[Rust]: https://www.rust-lang.org/
[webdriver crate]: https://crates.io/crates/webdriver
[commands]: https://docs.rs/webdriver/newest/webdriver/command/
[responses]: https://docs.rs/webdriver/newest/webdriver/response/
[errors]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html
[Marionette protocol]: /testing/marionette/Protocol.md
[WebDriver]: https://w3c.github.io/webdriver/
[Marionette]: /testing/marionette/index.rst

@ -1,98 +0,0 @@
# Firefox capabilities
geckodriver has a few capabilities that are specific to Firefox.
Most of these [are documented on MDN](https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions).
We additionally have some capabilities that largely are implementation
concerns that normal users should not care about:
## `moz:debuggerAddress`
A boolean value to indicate if Firefox has to be started with the
[Remote Protocol] enabled, which is a low-level debugging interface that
implements a subset of the [Chrome DevTools Protocol] (CDP).
When enabled the returned `moz:debuggerAddress` capability of the `New Session`
command is the `host:port` combination of a server that supports the following
HTTP endpoints:
### GET /json/version
The browser version metadata:
```json
{
"Browser": "Firefox/84.0a1",
"Protocol-Version": "1.0",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Gecko/20100101 Firefox/84.0",
"V8-Version": "1.0",
"WebKit-Version": "1.0",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/fe507083-2960-a442-bbd7-7dfe1f111c05"
}
```
### GET /json/list
A list of all available websocket targets:
```json
[ {
"description": "",
"devtoolsFrontendUrl": null,
"faviconUrl": "",
"id": "ecbf9028-676a-1b40-8596-a5edc0e2875b",
"type": "page",
"url": "https://www.mozilla.org/en-US/",
"browsingContextId": 29,
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/ecbf9028-676a-1b40-8596-a5edc0e2875b"
} ]
```
The contained `webSocketDebuggerUrl` entries can be used to connect to the
websocket and interact with the browser by using the CDP protocol.
[Remote Protocol]: /remote/index.rst
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
## `moz:useNonSpecCompliantPointerOrigin`
A boolean value to indicate how the pointer origin for an action
command will be calculated.
With Firefox 59 the calculation will be based on the requirements
by the [WebDriver] specification. This means that the pointer origin
is no longer computed based on the top and left position of the
referenced element, but on the in-view center point.
To temporarily disable the WebDriver conformant behavior use `false`
as value for this capability.
Please note that this capability exists only temporarily, and that
it will be removed once all Selenium bindings can handle the new
behavior.
## `moz:webdriverClick`
A boolean value to indicate which kind of interactability checks
to run when performing a click or sending keys to an elements. For
Firefoxen prior to version 58.0 some legacy code as imported from
an older version of FirefoxDriver was in use.
With Firefox 58 the interactability checks as required by the
[WebDriver] specification are enabled by default. This means
geckodriver will additionally check if an element is obscured by
another when clicking, and if an element is focusable for sending
keys.
Because of this change in behaviour, we are aware that some extra
errors could be returned. In most cases the test in question might
have to be updated so it's conform with the new checks. But if the
problem is located in geckodriver, then please raise an issue in
the [issue tracker].
To temporarily disable the WebDriver conformant checks use `false`
as value for this capability.
Please note that this capability exists only temporarily, and that
it will be removed once the interactability checks have been
stabilized.

@ -1,67 +0,0 @@
# Analyzing crash data of Firefox
It's not uncommon that under some special platform configurations and while
running automated tests via Selenium and geckodriver Firefox could crash. In
those cases it is very helpful to retrieve the generated crash data aka
minidump files, and report these to us.
## Retrieve the crash data
Because geckodriver creates a temporary user profile for Firefox, it also
automatically removes all its folders once the tests have been finished. That
also means that if Firefox crashed the created minidump files are lost. To
prevent that a custom profile has to be used instead. The following code
shows an example by using the Python Selenium bindings on Mac OS:
```python
import tempfile
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
# Custom profile folder to keep the minidump files
profile = tempfile.mkdtemp(".selenium")
print("*** Using profile: {}".format(profile))
# Use the above folder as custom profile
opts = Options()
opts.add_argument("-profile")
opts.add_argument(profile)
opts.binary = "/Applications/Firefox.app/Contents/MacOS/firefox"
driver = webdriver.Firefox(
options=opts,
# hard-code the Marionette port so geckodriver can connect
service_args=["--marionette-port", "2828"]
)
# Your test code which crashes Firefox
```
Executing the test with Selenium now, which triggers the crash of Firefox
will leave all the files from the user profile around in the above path.
To retrieve the minidump files navigate to that folder and look for a sub
folder with the name `minidumps`. It should contain at least one series of
files. One file with the `.dmp` extension and another one with `.extra`.
Both of those files are needed. If more crash files are present grab them all.
Attach the files as best archived as zip file to the created [geckodriver issue]
on Github.
[geckodriver issue]: https://github.com/mozilla/geckodriver/issues/new
## Getting details of the crash
More advanced users can upload the generated minidump files themselves and
receive details information about the crash. Therefore find the [crash reporter]
folder and copy all the generated minidump files into the `pending` sub directory.
Make sure that both the `.dmp` and `.extra` files are present.
Once done you can also [view the crash reports].
If you submitted a crash please do not forget to also add the link of the
crash report to the geckodriver issue.
[crash reporter]: https://support.mozilla.org/kb/mozillacrashreporter#w_viewing-reports-outside-of-firefox
[view the crash reports]: https://support.mozilla.orgkb/mozillacrashreporter#w_viewing-crash-reports

@ -1,218 +0,0 @@
<!-- markdownlint-disable MD033 -->
# Flags
## <code>--allow-hosts <var>ALLOW_HOSTS</var>...</code>
Values of the `Host` header to allow for incoming requests.
By default the value of <var>HOST</var> is allowed. If `--allow-hosts`
is provided, exactly the given values will be permitted. For example
`--allow-host geckodriver.test webdriver.local` will allow requests
with `Host` set to `geckodriver.test` or `webdriver.local`.
Requests with `Host` set to an IP address are always allowed.
## <code>--allow-origins <var>ALLOW_ORIGINS</var>...</code>
Values of the `Origin` header to allow for incoming requests.
`Origin` is set by web browsers for all `POST` requests, and most
other cross-origin requests. By default any request with an `Origin`
header is rejected to protect against malicious websites trying to
access geckodriver running on the local machine.
If `--allow-origins` is provided, web services running on the given
origin will be able to make requests to geckodriver. For example
`--allow-origins https://webdriver.test:8080` will allow a web-based
service on the origin with scheme `https`, hostname `webdriver.test`,
and port `8080` to access the geckodriver instance.
## <code>&#x2D;&#x2D;android-storage <var>ANDROID_STORAGE</var></code>
**Deprecation warning**: This argument is deprecated and planned to be removed
with the 0.31.0 release of geckodriver. As such it shouldn't be used with version
0.30.0 or later anymore. By default the automatic detection will now use the
external storage location, which is always readable and writeable.
Selects the test data location on the Android device, eg. the Firefox profile.
By default `auto` is used.
<style type="text/css">
table { width: 100%; margin-bottom: 2em; }
table, th, td { border: solid gray 1px; }
td, th { padding: 10px; text-align: left; vertical-align: middle; }
td:nth-child(1), th:nth-child(1) { width: 10em; text-align: center; }
</style>
<table>
<thead>
<tr>
<th>Value
<th>Description
</tr>
</thead>
<tr>
<td>auto
<td>Best suitable location based on whether the device is rooted.<br/>
If the device is rooted <code>internal</code> is used, otherwise <code>app</code>.
<tr>
<td>app
<td><p>Location: <code>/data/data/%androidPackage%/test_root</code></p>
Based on the <code>androidPackage</code> capability that is passed as part of
<code>moz:firefoxOptions</code> when creating a new session. Commands that
change data in the app's directory are executed using run-as. This requires
that the installed app is debuggable.
<tr>
<td>internal
<td><p>Location: <code>/data/local/tmp/test_root</code></p>
The device must be rooted since when the app runs, files that are created
in the profile, which is owned by the app user, cannot be changed by the
shell user. Commands will be executed via <code>su</code>.
<tr>
<td>sdcard
<td><p>Location: <code>$EXTERNAL_STORAGE/Android/data/%androidPackage%/files/test_root</code></p>
This location is supported by all versions of Android whether if the device
is rooted or not.
</table>
## <code>-b <var>BINARY</var></code> / <code>&#x2D;&#x2D;binary <var>BINARY</var></code>
Path to the Firefox binary to use. By default geckodriver tries to
find and use the system installation of Firefox, but that behaviour
can be changed by using this option. Note that the `binary`
capability of the `moz:firefoxOptions` object that is passed when
[creating a new session] will override this option.
On Linux systems it will use the first _firefox_ binary found
by searching the `PATH` environmental variable, which is roughly
equivalent to calling [whereis(1)] and extracting the second column:
```shell
% whereis firefox
firefox: /usr/bin/firefox /usr/local/firefox
```
On macOS, the binary is found by looking for the first _firefox-bin_
binary in the same fashion as on Linux systems. This means it is
possible to also use `PATH` to control where geckodriver should
find Firefox on macOS. It will then look for _/Applications/Firefox.app_.
On Windows systems, geckodriver looks for the system Firefox by
scanning the Windows registry.
[creating a new session]: https://w3c.github.io/webdriver/#new-session
[whereis(1)]: http://www.manpagez.com/man/1/whereis/
## <code>&#x2D;&#x2D;connect-existing</code>
Connect geckodriver to an existing Firefox instance. This means
geckodriver will abstain from the default of starting a new Firefox
session.
The existing Firefox instance must have [Marionette] enabled.
To enable the remote protocol in Firefox, you can pass the
`-marionette` flag. Unless the `marionette.port` preference
has been user-set, Marionette will listen on port 2828. So when
using `--connect-existing` it is likely you will also have to use
`--marionette-port` to set the correct port.
`--marionette-port`: #marionette-port
## <code>&#x2D;&#x2D;host <var>HOST</var></code>
Host to use for the WebDriver server. Defaults to 127.0.0.1.
## <code>&#x2D;&#x2D;jsdebugger</code>
Attach [browser toolbox] debugger when Firefox starts. This is
useful for debugging [Marionette] internals.
To be prompted at the start of the test run or between tests,
you can set the `marionette.debugging.clicktostart` preference to
`true`.
For reference, below is the list of preferences that enables the
chrome debugger. These are all set implicitly when the
argument is passed to geckodriver.
* `devtools.browsertoolbox.panel` -> `jsdebugger`
Selects the Debugger panel by default.
* `devtools.chrome.enabled` → true
Enables debugging of chrome code.
* `devtools.debugger.prompt-connection` → false
Controls the remote connection prompt. Note that this will
automatically expose your Firefox instance to localhost.
* `devtools.debugger.remote-enabled` → true
Allows a remote debugger to connect, which is necessary for
debugging chrome code.
[browser toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox
## <code>&#x2D;&#x2D;log <var>LEVEL</var></code>
Set the Gecko and geckodriver log level. Possible values are `fatal`,
`error`, `warn`, `info`, `config`, `debug`, and `trace`.
## <code>&#x2D;&#x2D;log-no-truncate</code>
Disables truncation of long log lines.
## <code>&#x2D;&#x2D;marionette-host <var>HOST</var></code>
Selects the host for geckodrivers connection to the [Marionette]
remote protocol. Defaults to 127.0.0.1.
## <code>&#x2D;&#x2D;marionette-port <var>PORT</var></code>
Selects the port for geckodrivers connection to the [Marionette]
remote protocol.
In the default mode where geckodriver starts and manages the Firefox
process, it will pick a free port assigned by the system and set the
`marionette.port` preference in the profile.
When `--connect-existing` is used and the Firefox process is not
under geckodrivers control, it will simply connect to <var>PORT</var>.
`--connect-existing`: #connect-existing
## <code>-p <var>PORT</var></code> / <code>&#x2D;&#x2D;port <var>PORT</var></code>
Port to use for the WebDriver server. Defaults to 4444.
A helpful trick is that it is possible to bind to 0 to get the
system to atomically assign a free port.
## <code>&#x2D;&#x2D;profile-root <var>PROFILE_ROOT</var></code>
Path to the directory to use when creating temporary profiles. By
default this is the system temporary directory. Both geckodriver and
Firefox must have read-write access to this path.
This setting can be useful when Firefox is sandboxed from the host
filesystem such that it doesn't share the same system temporary
directory as geckodriver (e.g. when running Firefox inside a container
or packaged as a snap).
## <code>-v<var>[v]</var></code>
Increases the logging verbosity by to debug level when passing
a single `-v`, or to trace level if `-vv` is passed. This is
analogous to passing `--log debug` and `--log trace`, respectively.
## <code>&#x2D;&#x2D;websocket-port<var>PORT</var></code>
Port to use to connect to WebDriver BiDi. Defaults to 9222.
A helpful trick is that it is possible to bind to 0 to get the
system to atomically assign a free port.
[Marionette]: /testing/marionette/index.rst

@ -1,44 +0,0 @@
# MacOS notarization
With the introduction of macOS 10.15 “Catalina” Apple introduced
[new notarization requirements] that all software must be signed
and notarized centrally.
Whilst the geckodriver binary is technically both signed and notarized, the
actual validation can only be performed by MacOS if the machine that starts
the geckodriver binary for the very first time is online. Offline validation
would require shipping geckodriver as a DMG/PKG. You can track the relevant
progress in [bug 1783943].
Note: geckodriver releases between 0.26.0 and 0.31.0 don't have the
notarization applied and always require the manual steps below to
bypass the notarization requirement of the binary during the very first start.
[new notarization requirements]: https://developer.apple.com/news/?id=04102019a
[bug 1783943]: https://bugzilla.mozilla.org/show_bug.cgi?id=1783943
## Offline mode
There are some mitigating circumstances:
* Verification problems only occur when other notarized programs,
such as a web browser, downloads the software from the internet.
* Arbitrary software downloaded through other means, such as
curl(1) is _not_ affected by this change.
In other words, if your method for fetching geckodriver on macOS
is through the GitHub web UI using a web browser, the program will
not be able to run unless you manually disable the quarantine check
(explained below). If downloading geckodriver via other means
than a macOS notarized program, you should not be affected.
To bypass the notarization requirement on macOS if you have downloaded
the geckodriver .tar.gz via a web browser, you can run the following
command in a terminal:
% xattr -r -d com.apple.quarantine geckodriver
A problem with notarization will manifest itself through a security
dialogue appearing, explaining that the source of the program is
not trusted.

@ -1,31 +0,0 @@
# Submitting patches
You can submit patches by using [Phabricator]. Walk through its documentation
in how to set it up, and uploading patches for review. Don't worry about which
person to select for reviewing your code. It will be done automatically.
Please also make sure to follow the [commit creation guidelines].
Once you have contributed a couple of patches, we are happy to sponsor you in
[becoming a Mozilla committer]. When you have been granted commit access
level 1, you will have permission to use the [Firefox CI] to trigger your own
“try runs” to test your changes. You can use the following [try preset] to run
the most relevant tests:
```shell
% ./mach try --preset geckodriver
```
This preset will schedule geckodriver-related tests on various platforms. You can
reduce the number of tasks by filtering on platforms (e.g. linux) or build type
(e.g. opt):
```shell
% ./mach try --preset geckodriver -xq "'linux 'opt"
```
[Phabricator]: https://moz-conduit.readthedocs.io/en/latest/phabricator-user.html
[commit creation guidelines]: https://mozilla-version-control-tools.readthedocs.io/en/latest/devguide/contributing.html?highlight=phabricator#submitting-patches-for-review
[becoming a Mozilla committer]: https://www.mozilla.org/en-US/about/governance/policies/commit/
[Firefox CI]: https://treeherder.mozilla.org/
[try preset]: https://firefox-source-docs.mozilla.org/tools/try/presets.html

@ -1,103 +0,0 @@
# Profiles
geckodriver uses [profiles] to instrument Firefox behaviour. The
user will usually rely on geckodriver to generate a temporary,
throwaway profile. These profiles are deleted when the WebDriver
session expires.
In cases where the user needs to use custom, prepared profiles,
geckodriver will make modifications to the profile that ensures
correct behaviour. See [_Automation preferences_] below on the
precedence of user-defined preferences in this case.
Custom profiles can be provided two different ways:
1. by appending `--profile /some/location` to the [`args` capability],
which will instruct geckodriver to use the profile _in-place_;
2. or by setting the [`profile` capability] to a Base64-encoded
ZIP of the profile directory.
Note that geckodriver has a [known bug concerning `--profile`] that
prevents the randomised Marionette port from being passed to
geckodriver. To circumvent this issue, make sure you specify the
port manually using `--marionette-port <port>`.
The second way is compatible with shipping Firefox profiles across
a network, when for example the geckodriver instance is running on
a remote system. This is the case when using Seleniums `RemoteWebDriver`
concept, where the WebDriver client and the server are running on
two distinct systems.
[profiles]: https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data
[_Automation preferences_]: #automation-preferences
[`args` capability]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#args_array_of_strings
[`profile` capability]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#profile_string
[known bug concerning `--profile`]: https://github.com/mozilla/geckodriver/issues/1058
## Default locations for temporary profiles
When a custom user profile is not provided with the `-profile`
command-line argument geckodriver generates a temporary, throwaway
profile. This is written to the default system temporary folder
and subsequently removed when the WebDriver session expires.
The default location for temporary profiles depends on the system.
On Unix systems it uses /tmp, and on Windows it uses the Windows
directory.
The default location can be overridden. On Unix you set the `TMPDIR`
environment variable. On Windows, the following environment variables
are respected, in order:
1. `TMP`
2. `TEMP`
3. `USERPROFILE`
It is not necessary to change the temporary directory system-wide.
All you have to do is make sure it gets set for the environment of
the geckodriver process:
TMPDIR=/some/location ./geckodriver
## Automation preferences
As indicated in the introduction, geckodriver configures Firefox
so it is well-behaved in automation environments. It uses a
combination of preferences written to the profile prior to launching
Firefox (1), and a set of recommended preferences set on startup (2).
These can be perused here:
1. [testing/geckodriver/src/prefs.rs](https://searchfox.org/mozilla-central/source/testing/geckodriver/src/prefs.rs)
2. [remote/components/marionette.js](https://searchfox.org/mozilla-central/source/remote/components/marionette.js)
As mentioned, these are _recommended_ preferences, and any user-defined
preferences in the [user.js file] or as part of the [`prefs` capability]
take precedence. This means for example that the user can tweak
`browser.startup.page` to override the recommended preference for
starting the browser with a blank page.
The recommended preferences set at runtime (see 2 above) may also
be disabled entirely by setting `remote.prefs.recommended` starting with Firefox
91. For older versions of Firefox, the preference to use was
`marionette.prefs.recommended`.
This may however cause geckodriver to not behave correctly according
to the WebDriver standard, so it should be used with caution.
Users should take note that the `marionette.port` preference is
special, and will always be overridden when using geckodriver unless
the `--marionette-port <port>` flag is used specifically to instruct
the Marionette server in Firefox which port to use.
[user.js file]: http://kb.mozillazine.org/User.js_file
[`prefs` capability]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#prefs_preferences_object
## Temporary profiles not being removed
It is a known bug that geckodriver in some instances fails to remove
the temporary profile, particularly when the session is not explicitly
deleted or the process gets interrupted. See [geckodriver issue
299] for more information.
[geckodriver issue 299]: https://github.com/mozilla/geckodriver/issues/299

@ -1,292 +0,0 @@
# Releasing geckodriver
Releasing geckodriver is not as easy as it once used to be when the
projects canonical home was on GitHub. Today geckodriver is hosted
in [mozilla-central], and whilst we do want to make future releases
from [Mozillas CI infrastructure], we are currently in between two
worlds: development happens in m-c, but releases continue to be made
from GitHub.
In any case, the steps to release geckodriver are as follows:
[mozilla-central]: https://hg.mozilla.org/mozilla-central/
[Mozillas CI infrastructure]: https://treeherder.mozilla.org/
## Update in-tree dependency crates
geckodriver depends on a number of Rust crates that also live in
central by using relative paths. Here an excerpt from its `Cargo.toml`:
```ini
[dependencies]
marionette = { path = "./marionette" }
mozdevice = { path = "../mozbase/rust/mozdevice" }
mozprofile = { path = "../mozbase/rust/mozprofile" }
mozrunner = { path = "../mozbase/rust/mozrunner" }
mozversion = { path = "../mozbase/rust/mozversion" }
webdriver = { path = "../webdriver" }
```
Because we need to export the geckodriver source code to the old
GitHub repository when we release, we first need to publish these
crates in the specified order if they have had any changes in the
interim since the last release. If they have received no changes,
you can skip them:
- `testing/mozbase/rust/mozdevice`
- `testing/mozbase/rust/mozprofile`
- `testing/mozbase/rust/mozrunner`
- `testing/mozbase/rust/mozversion`
- `testing/webdriver`
- `testing/geckodriver/marionette`
For each crate:
1. Change into the crates folder.
2. Bump the version number in `Cargo.toml` based on [semantic versioning rules],
and also update the version dependency for other in-tree crates using the
currently modified crate. Note that running `cargo update` will fail if you
missed updating a crate's dependency.
3. Use the [cargo-semver-checks] command to validate the version change:
```shell
% cargo semver-checks check-release
```
4. Update the crate:
```shell
% cargo update -p <crate name>
```
5. We also publish audit information for the crates based on Mozilla's
[audit criteria]. Because we use [wildcard audit entries] make sure that the
latest day of publication is still within the `end` date. The related entry
of the crate can be found at the top of [audits.toml]. If the date is over,
then update its value to at most 1 year in the future.
6. Commit the changes for the modified [Cargo.toml] files, [Cargo.lock] and
[audits.toml].
```shell
% git add Cargo.toml Cargo.lock audits.toml testing
% git commit -m "Bug XYZ - [rust-<name>] Release version <version>"
```
[semantic versioning rules]: https://semver.org/
[cargo-semver-checks]: https://crates.io/crates/cargo-semver-checks
[audit criteria]: https://mozilla.github.io/cargo-vet/audit-criteria.html
[wildcard audit entries]: https://mozilla.github.io/cargo-vet/wildcard-audit-entries.html
[Cargo.toml]: https://searchfox.org/mozilla-central/source/testing/geckodriver/Cargo.toml
[Cargo.lock]: https://searchfox.org/mozilla-central/source/Cargo.lock
[audits.toml]: https://searchfox.org/mozilla-central/source/supply-chain/audits.toml
## Update the change log
Notable changes to geckodriver are mentioned in [CHANGES.md]. Many
users rely on this, so its important that you make it **relevant
to end-users**. For example, we only mention changes that are visible
to users. The change log is not a complete anthology of commits,
as these often will not convey the essence of a change to end-users.
If a feature was added but removed before release, there is no reason
to list it as a change.
It is good practice to also include relevant information from the
[webdriver], [marionette], [rust-mozrunner], and [rust-mozdevice] crates,
since these are the most important dependencies of geckodriver and a lot
of its functionality is implemented there.
To get a list of all the changes for one of the above crates one of the following
commands can be used:
```shell
% hg log -M -r <revision>::central --template "{node|short}\t{desc|firstline}\n" <path>
% git log --reverse $(git cinnabar hg2git <revision>)..HEAD --pretty="%s" <path>
```
where `<revision>` is the changeset of the last geckodriver release and `<path>`
the location of the crate in the repository.
Add the list of changes to the related release bug on Bugzilla, and also check the
dependency list of the bug for other fixes that are worth mentioning.
We follow the writing style of the existing change log, with
one section per version (with a release date), with subsections
Added, Changed, 'Fixed' and Removed. If the targeted
Firefox or Selenium versions have changed, it is good to make a
mention of this. Lines are optimally formatted at roughly 72 columns
to make the file readable in a text editor as well as rendered HTML.
fmt(1) does a splendid job at text formatting.
[CHANGES.md]: https://searchfox.org/mozilla-central/source/testing/geckodriver/CHANGES.md
[webdriver]: https://searchfox.org/mozilla-central/source/testing/webdriver
[marionette]: https://searchfox.org/mozilla-central/source/testing/geckodriver/marionette
[rust-mozrunner]: https://searchfox.org/mozilla-central/source/testing/mozbase/rust/mozrunner
[rust-mozdevice]: https://searchfox.org/mozilla-central/source/testing/mozbase/rust/mozdevice
## Bump the version number and update the support page
Bump the version number in [Cargo.toml] to the next version.
geckodriver follows [semantic versioning] so its a good idea to
familiarise yourself with that before deciding on the version number.
After youve changed the version number, run
```shell
% ./mach build testing/geckodriver
```
again to update [Cargo.lock].
Now update the [support page] by adding a new row to the versions table,
including the required versions of Selenium, and Firefox.
Finally commit all those changes.
[semantic versioning]: http://semver.org/
[support page]: https://searchfox.org/mozilla-central/source/testing/geckodriver/doc/Support.md
## Add the changeset id
To easily allow a release build of geckodriver after cloning the
repository, the changeset id for the release has to be added to the
change log. Therefore add a final place-holder commit to the patch
series, to already get review for.
Once all previous revisions of the patch series have been landed, and got merged
to `mozilla-central`, the changeset id from the merge commit has to picked for
finalizing the change log. This specific id is needed because Taskcluster creates
the final signed builds based on that merge.
## Release new in-tree dependency crates
Make sure to wait until the complete patch series from above has been
merged to mozilla-central. Then continue with the following steps.
Before releasing geckodriver all dependency crates as
[updated earlier](#update-in-tree-dependency-crates) have to be
released first.
Therefore change into each of the directories for crates with an update
and run the following command to publish the crate:
```shell
% cargo publish
```
Note that if a crate has an in-tree dependency make sure to first
change the dependency information.
Do not release the geckodriver crate yet!
Once all crates have been published observe the `/target/package/` folder under
the root of the mozilla-central repository and remove all the folders related
to the above published packages (it will save ~1GB disk space).
## Export to GitHub
The canonical GitHub repository is <https://github.com/mozilla/geckodriver.git>
so make sure you have a local clone of that. It has three branches:
_master_ which only contains the [README.md]; _old_ which was the
state of the project when it was exported to mozilla-central; and
_release_, from where releases are made.
Before we copy the code over to the GitHub repository we need to
check out the [release commit that bumped the version number](#add-the-changeset-id)
on mozilla-central:
```shell
% hg update $RELEASE_REVISION
```
Or:
```shell
% git checkout $(git cinnabar hg2git $RELEASE_REVISION)
```
We will now export the contents of [testing/geckodriver] to a new branch that
is based on the _release_ branch, which will be used to create a pull request:
```shell
% cd $SRC/geckodriver
% git checkout release
% git pull
% git checkout -b do_release_X.Y.Z
% git rm -rf .
% git clean -fxd
% cp -rt $SRC/gecko/testing/geckodriver .
```
Now verify that geckodriver builds correctly by running:
```shell
% cargo build
```
[README.md]: https://searchfox.org/mozilla-central/source/testing/geckodriver/README.md
[testing/geckodriver]: https://searchfox.org/mozilla-central/source/testing/geckodriver
## Commit local changes
Now commit all the changes you have made locally to the _release_ branch.
It is recommended to setup a [GPG key] for signing the commit, so
that the release commit is marked as `verified`.
```shell
% git add . -- ':!mach_commands.py :!moz.build :!target/*'
% git commit -S -am "Import of vX.Y.Z" (signed)
```
or if you cannot use signing use:
```shell
% git add . -- ':!mach_commands.py :!moz.build :!target/*'
% git commit -am "Import of vX.Y.Z" (unsigned)
```
Then push the changes, and create a pull request:
```shell
% git push origin do_release_X.Y.Z
```
As indicated above, the changes you make to this branch will not
be upstreamed back into mozilla-central. It is merely used as a
place for external consumers to build their own version of geckodriver.
[GPG key]: https://help.github.com/articles/signing-commits/
## Make the release
geckodriver needs to be manually released on github.com. Therefore start to
[draft a new release], and make the following changes:
1. Specify the "Tag version", and select "Release" as target.
2. Leave the release title empty
3. Paste the raw Markdown source from [CHANGES.md] into the description field.
This will highlight for end-users what changes were made in that particular
package when they visit the GitHub downloads section. Make sure to check that
all references can be resolved, and if not make sure to add those too.
4. Find the signed geckodriver archives in the [taskcluster index] by
replacing %changeset% with the full release changeset id. Rename the
individual files so the basename looks like 'geckodriver-v%version%-%platform%'.
Upload them all, including the checksum files for the Linux platforms.
5. Before announcing the release on GitHub publish the geckodriver crate as well
on crates.io by running `cargo publish` from the release branch.
6. Send the release announcement to the [dev-webdriver] mailing list.
[draft a new release]: https://github.com/mozilla/geckodriver/releases/new
[taskcluster index]: https://firefox-ci-tc.services.mozilla.com/tasks/index/gecko.v2.mozilla-central.revision.%changeset%.geckodriver
[dev-webdriver]: https://groups.google.com/a/mozilla.org/g/dev-webdriver
Congratulations! Youve released geckodriver!

@ -1,183 +0,0 @@
<!-- markdownlint-disable MD033 -->
# Supported platforms
The following table shows a mapping between [geckodriver releases],
and required versions of Selenium and Firefox:
<style type="text/css">
table { width: 100%; margin-bottom: 2em; }
table, th, td { border: solid gray 1px; }
td, th { padding: 5px 10px; text-align: center; }
</style>
<table>
<thead>
<tr>
<th rowspan="2">geckodriver
<th rowspan="2">Selenium
<th colspan="2">Firefox
</tr>
<tr>
<th>min
<th>max
</tr>
</thead>
</thead>
<tr>
<td>0.33.0
<td>≥ 3.11 (3.14 Python)
<td>102 ESR
<td>n/a
<tr>
<td>0.32.2
<td>≥ 3.11 (3.14 Python)
<td>102 ESR
<td>n/a
<tr>
<td>0.32.1
<td>≥ 3.11 (3.14 Python)
<td>102 ESR
<td>n/a
<tr>
<td>0.32.0
<td>≥ 3.11 (3.14 Python)
<td>102 ESR
<td>n/a
<tr>
<td>0.31.0
<td>≥ 3.11 (3.14 Python)
<td>91 ESR
<td>n/a
<tr>
<tr>
<td>0.30.0
<td>≥ 3.11 (3.14 Python)
<td>78 ESR
<td>90
<tr>
<td>0.29.1
<td>≥ 3.11 (3.14 Python)
<td>60
<td>90
<tr>
<td>0.29.0
<td>≥ 3.11 (3.14 Python)
<td>60
<td>90
<tr>
<td>0.28.0
<td>≥ 3.11 (3.14 Python)
<td>60
<td>90
<tr>
<td>0.27.0
<td>≥ 3.11 (3.14 Python)
<td>60
<td>90
<tr>
<td>0.26.0
<td>≥ 3.11 (3.14 Python)
<td>60
<td>90
<tr>
<td>0.25.0
<td>≥ 3.11 (3.14 Python)
<td>57
<td>90
<tr>
<td>0.24.0
<td>≥ 3.11 (3.14 Python)
<td>57
<td>79
<tr>
<td>0.23.0
<td>≥ 3.11 (3.14 Python)
<td>57
<td>79
<tr>
<td>0.22.0
<td>≥ 3.11 (3.14 Python)
<td>57
<td>79
<tr>
<td>0.21.0
<td>≥ 3.11 (3.14 Python)
<td>57
<td>79
<tr>
<td>0.20.1
<td>≥ 3.5
<td>55
<td>62
<tr>
<td>0.20.0
<td>≥ 3.5
<td>55
<td>62
<tr>
<td>0.19.1
<td>≥ 3.5
<td>55
<td>62
<tr>
<td>0.19.0
<td>≥ 3.5
<td>55
<td>62
<tr>
<td>0.18.0
<td>≥ 3.4
<td>53
<td>62
<tr>
<td>0.17.0
<td>≥ 3.4
<td>52
<td>62
</table>
## Clients
[Selenium] users must update to version 3.11 or later to use geckodriver.
Other clients that follow the [W3C WebDriver specification][WebDriver]
are also supported.
## Firefoxen
geckodriver is not yet feature complete. This means that it does
not yet offer full conformance with the [WebDriver] standard
or complete compatibility with [Selenium]. You can track the
[implementation status] of the latest [Firefox Nightly] on MDN.
We also keep track of known [Selenium], [remote protocol], and
[specification] problems in our [issue tracker].
Support is best in Firefox 57 and greater, although generally the more
recent the Firefox version, the better the experience as they have
more bug fixes and features. Some features will only be available
in the most recent Firefox versions, and we strongly advise using the
latest [Firefox Nightly] with geckodriver. Since Windows XP support
in Firefox was dropped with Firefox 53, we do not support this platform.
## Android
Starting with the 0.26.0 release geckodriver is able to connect
to Android devices, and to control packages which are based on [GeckoView]
(eg. [Firefox Preview] aka Fenix, or [Firefox Reality]). But it also still
supports versions of Fennec up to 68 ESR, which is the last officially
supported release from Mozilla.
To run tests on Android specific capabilities under `moz:firefoxOptions`
have to be set when requesting a new session. See the Android section under
[Firefox Capabilities](Capabilities.md#android) for more details.
[geckodriver releases]: https://github.com/mozilla/geckodriver/releases
[Selenium]: https://github.com/seleniumhq/selenium
[WebDriver]: https://w3c.github.io/webdriver/
[implementation status]: https://bugzilla.mozilla.org/showdependencytree.cgi?id=721859&hide_resolved=1
[remote protocol]: https://github.com/mozilla/geckodriver/issues?q=is%3Aissue+is%3Aopen+label%3Amarionette
[specification]: https://github.com/mozilla/geckodriver/issues?q=is%3Aissue+is%3Aopen+label%3Aspec
[issue tracker]: https://github.com/mozilla/geckodriver/issues
[Firefox Nightly]: https://nightly.mozilla.org/
[GeckoView]: https://wiki.mozilla.org/Mobile/GeckoView
[Firefox Preview]: https://play.google.com/store/apps/details?id=org.mozilla.fenix
[Firefox Reality]: https://play.google.com/store/apps/details?id=org.mozilla.vrbrowser

@ -1,69 +0,0 @@
# Testing geckodriver
We verify and test geckodriver in a couple of different ways.
Since it is an implementation of the WebDriver web standard, we share
a set of conformance tests with other browser vendors through the
[Web Platform Tests] (WPT) initiative. This lets us ensure web
compatibility between _different_ WebDriver implementations for
different browsers.
In addition to the WPT tests, geckodriver and webdriver have
unit tests. These are written in Rust, but you must explicitly
tell mach to build these by adding the following line to your [mozconfig]:
```make
ac_add_options --enable-rust-tests
```
Tests can then be run by using the `test` sub command for [cargo] in the
specific source folder:
```shell
% cd testing/geckodriver/src
% cargo test
```
To run the more extensive WPT tests you can use mach, but first
make sure you have built Firefox:
```shell
% ./mach build
% ./mach wpt testing/web-platform/tests/webdriver
```
As these are functional integration tests and pop up Firefox windows
sporadically, a helpful tip is to suppress the window whilst you
are running them by using Firefox [headless mode]:
```shell
% ./mach wpt --headless testing/web-platform/tests/webdriver
```
The `--headless` flag is equivalent to setting the `MOZ_HEADLESS`
output variable. In addition to `MOZ_HEADLESS` there is also
`MOZ_HEADLESS_WIDTH` and `MOZ_HEADLESS_HEIGHT` for controlling the
dimensions of the no-op virtual display. This is similar to using
Xvfb(1) which you may know from the X windowing system, but has
the additional benefit of also working on macOS and Windows.
As you get in to development of geckodriver and Marionette you will
increasingly grow to understand our love for [trace-level logs].
They provide us with the input—the HTTP requests—from the client
(in WPTs case from the tests use of a custom WebDriver client),
the translation geckodriver makes to the [Marionette protocol],
the log output from Marionette, its responses back to geckodriver,
and finally the output—or the HTTP response—back to the client.
The [trace-level logs] can be surfaced by passing on the `-vv`
flag to geckodriver through WPT:
```shell
% ./mach wpt --webdriver-arg=-vv testing/web-platform/tests/webdriver
```
[Web Platform Tests]: http://web-platform-tests.org/
[cargo]: http://doc.crates.io/guide.html
[headless mode]: https://developer.mozilla.org/en-US/Firefox/Headless_mode
[mozconfig]: https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Configuring_Build_Options
[trace-level logs]: TraceLogs.md
[Marionette protocol]: /testing/marionette/Protocol.md

@ -1,206 +0,0 @@
# Enabling trace logs
geckodriver provides different bands of logs for different audiences.
The most important log entries are shown to everyone by default,
and these include which port geckodriver provides the WebDriver
API on, as well as informative warnings, errors, and fatal exceptions.
The different log bands are, in ascending bandwidth:
1. `fatal` is reserved for exceptional circumstances when geckodriver
or Firefox cannot recover. This usually entails that either
one or both of the processes will exit.
2. `error` messages are mistakes in the program code which it is
possible to recover from.
3. `warn` shows warnings of more informative nature that are not
necessarily problems in geckodriver. This could for example happen
if you use the legacy `desiredCapabilities`/`requiredCapabilities`
objects instead of the new `alwaysMatch`/`firstMatch` structures.
4. `info` (default) contains information about which port geckodriver
binds to, but also all messages from the lower-bandwidth levels
listed above.
5. `config` additionally shows the negotiated capabilities after
matching the `alwaysMatch` capabilities with the sequence of
`firstMatch` capabilities.
6. `debug` is reserved for information that is useful when programming.
7. `trace`, where in addition to itself, all previous levels
are included. The trace level shows all HTTP requests received
by geckodriver, packets sent to and from the remote protocol in
Firefox, and responses sent back to your client.
In other words this means that the configured level will coalesce
entries from all lower bands including itself. If you set the log
level to `error`, you will get log entries for both `fatal` and `error`.
Similarly for `trace`, you will get all the logs that are offered.
To help debug a problem with geckodriver or Firefox, the trace-level
output is vital to understand what is going on. This is why we ask
that trace logs are included when filing bugs gainst geckodriver.
It is only under very special circumstances that a trace log is
not needed, so you will normally find that our first action when
triaging your issue will be to ask you to include one. Do yourself
and us a favour and provide a trace-level log right away.
To silence geckodriver altogether you may for example either redirect
all output to append to some log files:
```shell
% geckodriver >>geckodriver.log 2>>geckodriver.err.log
```
Or a black hole somewhere:
```shell
% geckodriver >/dev/null 2>&1
```
The log level set for geckodriver is propagated to the Marionette
logger in Firefox. Marionette is the remote protocol that geckodriver
uses to implement WebDriver. This means enabling trace logs for
geckodriver will also implicitly enable them for Marionette.
The log level is set in different ways. Either by using the
`--log <LEVEL>` option, where `LEVEL` is one of the log levels
from the list above, or by using the `-v` (for debug) or `-vv`
(for trace) shorthands. For example, the following command will
enable trace logs for both geckodriver and Marionette:
```shell
% geckodriver -vv
```
The second way of setting the log level is through capabilities.
geckodriver accepts a Mozilla-specific configuration object
in [`moz:firefoxOptions`]. This JSON Object, which is further
described in the [README] can hold Firefox-specific configuration,
such as which Firefox binary to use, additional preferences to set,
and of course which log level to use.
[`moz:firefoxOptions`]: https://searchfox.org/mozilla-central/source/testing/geckodriver/README.md#firefox-capabilities
[README]: https://searchfox.org/mozilla-central/source/testing/geckodriver/README.md
Each client has its own way of specifying capabilities, and some clients
include “helpers” for providing browser-specific configuration.
It is often advisable to use these helpers instead of encoding the
JSON Object yourself because it can be difficult to get the exact
details right, but if you choose to, it should look like this:
```json
{"moz:firefoxOptions": {"log": {"level": "trace"}}}
```
Note that most known WebDriver clients, such as those provided by
the Selenium project, do not expose a way to actually _see_ the logs
unless you redirect the log output to a particular file (using the
method shown above) or let the client “inherit” geckodrivers
output, for example by redirecting the stdout and stderr streams to
its own. The notable exceptions are the Python and Ruby bindings,
which surface geckodriver logs in a remarkable easy and efficient way.
See the client-specific documentation below for the most idiomatic
way to enable trace logs in your language. We want to expand this
documentation to cover all the best known clients people use with
geckodriver. If you find your language missing, please consider
[submitting a patch].
[submitting a patch]: Patches.md
## C-Sharp
The Selenium [C# client] comes with a [`FirefoxOptions`] helper for
constructing the [`moz:firefoxOptions`] capabilities object:
```csharp
FirefoxOptions options = new FirefoxOptions();
options.LogLevel = FirefoxDriverLogLevel.Trace;
IWebDriver driver = new FirefoxDriver(options);
```
The log output is directed to stdout.
[C# client]: https://seleniumhq.github.io/selenium/docs/api/dotnet/
[`FirefoxOptions`]: https://seleniumhq.github.io/selenium/docs/api/dotnet/html/T_OpenQA_Selenium_Firefox_FirefoxOptions.htm
## Java
The Selenium [Java client] also comes with
a [`org.openqa.selenium.firefox.FirefoxOptions`] helper for
constructing the [`moz:firefoxOptions`] capabilities object:
```java
FirefoxOptions options = new FirefoxOptions();
options.setLogLevel(FirefoxDriverLogLevel.TRACE);
WebDriver driver = new FirefoxDriver(options);
```
The log output is directed to stdout.
[Java client]: https://seleniumhq.github.io/selenium/docs/api/java/
[`org.openqa.selenium.firefox.FirefoxOptions`]: https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/firefox/FirefoxOptions.html
## Javascript (webdriver.io)
With the Selenium [JavaScript client] the capabilities object can directly be
constructed:
```javascript
import WebDriver from 'webdriver'
const driver = await WebDriver.newSession({
capabilities: {
browserName: 'firefox',
'moz:firefoxOptions': {
log: { level: 'trace' },
}
}
})
```
The log output is directed to stdout, or if geckodriver runs as a wdio plugin
then the generated logs are part of the wdio log system.
[JavaScript client]: https://webdriver.io/
## Python
The Selenium [Python client] comes with a
[`selenium.webdriver.firefox.options.Options`] helper that can
be used programmatically to construct the [`moz:firefoxOptions`]
capabilities object:
```python
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
opts = Options()
opts.log.level = "trace"
driver = Firefox(options=opts)
```
The log output is stored in a file called _geckodriver.log_ in your
scripts current working directory.
[Python client]: https://selenium-python.readthedocs.io/
[`selenium.webdriver.firefox.options.Options`]: https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/firefox/options.py
## Ruby
The Selenium [Ruby client] comes with an [`Options`] helper to
generate the correct [`moz:firefoxOptions`] capabilities object:
```ruby
Selenium::WebDriver.logger.level = :debug
opts = Selenium::WebDriver::Firefox::Options.new(log_level: :trace)
driver = Selenium::WebDriver.for :firefox, options: opts
```
The log output is directed to stdout.
[Ruby client]: https://seleniumhq.github.io/selenium/docs/api/rb/
[`Options`]: https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/Firefox/Options.html

@ -1,143 +0,0 @@
# Usage
geckodriver is an implementation of WebDriver, and WebDriver can
be used for widely different purposes. How you invoke geckodriver
largely depends on your use case.
## Running Firefox in a container-based package
When Firefox is packaged inside a container (e.g. [Snap], [Flatpak]), it may
see a different filesystem to the host. This can affect access to the generated
profile directory, which may result in a hang when starting Firefox.
This is known to affect launching the default Firefox shipped with Ubuntu 22.04+.
There are several workarounds available for this problem:
- Do not use container-packaged Firefox builds with geckodriver. Instead
download a Firefox release from <https://download.mozilla.org/?product=firefox-latest&os=linux>
and a geckodriver release from <https://github.com/mozilla/geckodriver/releases>.
- Use a geckodriver that runs in the same container filesystem as the Firefox
package. For example on Ubuntu `/snap/bin/geckodriver` will work with the
default Firefox.
- Set the `--profile-root` command line option to write the profile to a
directory accessible to both Firefox and geckodriver, for example a non-hidden
directory under `$HOME`.
[Flatpak]: https://flatpak.org/
[Snap]: https://ubuntu.com/core/services/guide/snaps-intro
## Selenium
If you are using geckodriver through [Selenium], you must ensure that
you have version 3.11 or greater. Because geckodriver implements the
[W3C WebDriver standard][WebDriver] and not the same Selenium wire
protocol older drivers are using, you may experience incompatibilities
and migration problems when making the switch from FirefoxDriver to
geckodriver.
Generally speaking, Selenium 3 enabled geckodriver as the default
WebDriver implementation for Firefox. With the release of Firefox 47,
FirefoxDriver had to be discontinued for its lack of support for the
[new multi-processing architecture in Gecko][e10s].
Selenium client bindings will pick up the _geckodriver_ binary executable
from your [systems `PATH` environmental variable][PATH] unless you
override it by setting the `webdriver.gecko.driver` [Java VM system
property]:
```java
System.setProperty("webdriver.gecko.driver", "/home/user/bin");
```
Or by passing it as a flag to the [java(1)] launcher:
```shell
% java -Dwebdriver.gecko.driver=/home/user/bin YourApplication
```
Your mileage with this approach may vary based on which programming
language bindings you are using. It is in any case generally the case
that geckodriver will be picked up if it is available on the system path.
In a bash compatible shell, you can make other programs aware of its
location by exporting or setting the `PATH` variable:
```shell
% export PATH=$PATH:/home/user/bin
% whereis geckodriver
geckodriver: /home/user/bin/geckodriver
```
On Window systems you can change the system path by right-clicking **My
Computer** and choosing **Properties**. In the dialogue that appears,
navigate **Advanced****Environmental Variables****Path**.
Or in the Windows console window:
```shell
% set PATH=%PATH%;C:\bin\geckodriver
```
## Standalone
Since geckodriver is a separate HTTP server that is a complete remote end
implementation of [WebDriver], it is possible to avoid using the Selenium
remote server if you have no requirements to distribute processes across
a matrix of systems.
Given a W3C WebDriver conforming client library (or _local end_) you
may interact with the geckodriver HTTP server as if you were speaking
to any Selenium server.
Using [curl(1)]:
```shell
% geckodriver &
[1] 16010
% 1491834109194 geckodriver INFO Listening on 127.0.0.1:4444
% curl -H 'Content-Type: application/json' -d '{"capabilities": {"alwaysMatch": {"acceptInsecureCerts": true}}}' http://localhost:4444/session
{"value":{"sessionId":"d4605710-5a4e-4d64-a52a-778bb0c31e00","capabilities":{"acceptInsecureCerts":true,[...]}}}
% curl -H 'Content-Type: application/json' -d '{"url": "https://mozilla.org"}' http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url
{}
% curl http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url
{"value":"https://www.mozilla.org/en-US/"
% curl -X DELETE http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00
{}
% fg
geckodriver
^C
```
Using the Python [wdclient] library:
```python
import webdriver
with webdriver.Session("127.0.0.1", 4444) as session:
session.url = "https://mozilla.org"
print "The current URL is %s" % session.url
```
And to run:
```shell
% geckodriver &
[1] 16054
% python example.py
1491835308354 geckodriver INFO Listening on 127.0.0.1:4444
The current URL is https://www.mozilla.org/en-US/
% fg
geckodriver
^C
```
[Selenium]: http://seleniumhq.org/
[e10s]: https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox
[PATH]: https://en.wikipedia.org/wiki/PATH_(variable)
[Java VM system property]: http://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html
[java(1)]: http://www.manpagez.com/man/1/java/
[WebDriver]: https://w3c.github.io/webdriver/
[curl(1)]: http://www.manpagez.com/man/1/curl/
[wdclient]: https://github.com/web-platform-tests/wpt/tree/master/tools/webdriver

@ -1,55 +0,0 @@
===========
geckodriver
===========
Proxy for using W3C WebDriver-compatible clients to interact with
Gecko-based browsers.
This program provides the HTTP API described by the `WebDriver protocol`_.
to communicate with Gecko browsers, such as Firefox. It translates calls
into the :ref:`Firefox remote protocol <Protocol>` by acting as a proxy between the local-
and remote ends.
You can consult the `change log`_ for a record of all notable changes
to the program. Releases_ are made available on GitHub.
.. _WebDriver protocol: https://w3c.github.io/webdriver/#protocol
.. _change log: https://github.com/mozilla/geckodriver/releases
.. _Releases: https://github.com/mozilla/geckodriver/releases
.. toctree::
:maxdepth: 1
Support.md
WebDriver capabilities <https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities>
Capabilities.md
Usage.md
Flags.md
Profiles.md
Bugs.md
TraceLogs.md
CrashReports.md
Notarization.md
For developers
==============
.. toctree::
:maxdepth: 1
Building.md
Testing.md
Patches.md
Releasing.md
ARM.md
Communication
=============
The mailing list for geckodriver discussion is
https://groups.google.com/a/mozilla.org/g/dev-webdriver.
If you prefer real-time chat, ask your questions
on `#webdriver:mozilla.org <https://chat.mozilla.org/#/room/#webdriver:mozilla.org>`__.

@ -1,14 +0,0 @@
[package]
name = "marionette"
version = "0.4.0"
authors = ["Mozilla"]
description = "Library implementing the client side of Gecko's Marionette remote automation protocol."
edition = "2018"
keywords = ["mozilla", "firefox", "marionette", "webdriver"]
license = "MPL-2.0"
repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver/marionette"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"

@ -1,240 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde::ser::SerializeMap;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BoolValue {
value: bool,
}
impl BoolValue {
pub fn new(val: bool) -> Self {
BoolValue { value: val }
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Cookie {
pub name: String,
pub value: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default)]
pub secure: bool,
#[serde(default, rename = "httpOnly")]
pub http_only: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub expiry: Option<Date>,
#[serde(skip_serializing_if = "Option::is_none", rename = "sameSite")]
pub same_site: Option<String>,
}
pub fn to_cookie<T, S>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
#[derive(Serialize)]
struct Wrapper<T> {
cookie: T,
}
Wrapper { cookie: data }.serialize(serializer)
}
pub fn from_cookie<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: serde::de::DeserializeOwned,
T: std::fmt::Debug,
{
#[derive(Debug, Deserialize)]
struct Wrapper<T> {
cookie: T,
}
let w = Wrapper::deserialize(deserializer)?;
Ok(w.cookie)
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Date(pub u64);
#[derive(Clone, Debug, PartialEq)]
pub enum Frame {
Index(u16),
Element(String),
Parent,
}
impl Serialize for Frame {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
match self {
Frame::Index(nth) => map.serialize_entry("id", nth)?,
Frame::Element(el) => map.serialize_entry("element", el)?,
Frame::Parent => map.serialize_entry("id", &Value::Null)?,
}
map.end()
}
}
impl<'de> Deserialize<'de> for Frame {
fn deserialize<D>(deserializer: D) -> Result<Frame, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
struct JsonFrame {
id: Option<u16>,
element: Option<String>,
}
let json = JsonFrame::deserialize(deserializer)?;
match (json.id, json.element) {
(Some(_id), Some(_element)) => Err(de::Error::custom("conflicting frame identifiers")),
(Some(id), None) => Ok(Frame::Index(id)),
(None, Some(element)) => Ok(Frame::Element(element)),
(None, None) => Ok(Frame::Parent),
}
}
}
// TODO(nupur): Bug 1567165 - Make WebElement in Marionette a unit struct
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WebElement {
#[serde(rename = "element-6066-11e4-a52e-4f735466cecf")]
pub element: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Timeouts {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub implicit: Option<u64>,
#[serde(default, rename = "pageLoad", skip_serializing_if = "Option::is_none")]
pub page_load: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[allow(clippy::option_option)]
pub script: Option<Option<u64>>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Window {
pub handle: String,
}
pub fn to_name<T, S>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
#[derive(Serialize)]
struct Wrapper<T> {
name: T,
}
Wrapper { name: data }.serialize(serializer)
}
pub fn from_name<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: serde::de::DeserializeOwned,
T: std::fmt::Debug,
{
#[derive(Debug, Deserialize)]
struct Wrapper<T> {
name: T,
}
let w = Wrapper::deserialize(deserializer)?;
Ok(w.name)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::{assert_de, assert_ser, assert_ser_de, ELEMENT_KEY};
use serde_json::json;
#[test]
fn test_cookie_default_values() {
let data = Cookie {
name: "hello".into(),
value: "world".into(),
path: None,
domain: None,
secure: false,
http_only: false,
expiry: None,
same_site: None,
};
assert_de(&data, json!({"name":"hello", "value":"world"}));
}
#[test]
fn test_json_frame_index() {
assert_ser_de(&Frame::Index(1234), json!({"id": 1234}));
}
#[test]
fn test_json_frame_element() {
assert_ser_de(&Frame::Element("elem".into()), json!({"element": "elem"}));
}
#[test]
fn test_json_frame_parent() {
assert_ser_de(&Frame::Parent, json!({ "id": null }));
}
#[test]
fn test_web_element() {
let data = WebElement {
element: "foo".into(),
};
assert_ser_de(&data, json!({ELEMENT_KEY: "foo"}));
}
#[test]
fn test_timeouts_with_all_params() {
let data = Timeouts {
implicit: Some(1000),
page_load: Some(200000),
script: Some(Some(60000)),
};
assert_ser_de(
&data,
json!({"implicit":1000,"pageLoad":200000,"script":60000}),
);
}
#[test]
fn test_timeouts_with_missing_params() {
let data = Timeouts {
implicit: Some(1000),
page_load: None,
script: None,
};
assert_ser_de(&data, json!({"implicit":1000}));
}
#[test]
fn test_timeouts_setting_script_none() {
let data = Timeouts {
implicit: Some(1000),
page_load: None,
script: Some(None),
};
assert_ser(&data, json!({"implicit":1000, "script":null}));
}
}

@ -1,184 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::error;
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(untagged)]
pub(crate) enum Error {
Marionette(MarionetteError),
}
impl Error {
pub fn kind(&self) -> ErrorKind {
match *self {
Error::Marionette(ref err) => err.kind,
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Marionette(ref err) => fmt
.debug_struct("Marionette")
.field("kind", &err.kind)
.field("message", &err.message)
.field("stacktrace", &err.stack.clone())
.finish(),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Marionette(ref err) => write!(fmt, "{}: {}", err.kind, err.message),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match self {
Error::Marionette(_) => self.kind().as_str(),
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct MarionetteError {
#[serde(rename = "error")]
pub kind: ErrorKind,
#[serde(default = "empty_string")]
pub message: String,
#[serde(rename = "stacktrace", default = "empty_string")]
pub stack: String,
}
fn empty_string() -> String {
"".to_owned()
}
impl From<MarionetteError> for Error {
fn from(error: MarionetteError) -> Error {
Error::Marionette(error)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum ErrorKind {
#[serde(rename = "element click intercepted")]
ElementClickIntercepted,
#[serde(rename = "element not accessible")]
ElementNotAccessible,
#[serde(rename = "element not interactable")]
ElementNotInteractable,
#[serde(rename = "insecure certificate")]
InsecureCertificate,
#[serde(rename = "invalid argument")]
InvalidArgument,
#[serde(rename = "invalid cookie")]
InvalidCookieDomain,
#[serde(rename = "invalid element state")]
InvalidElementState,
#[serde(rename = "invalid selector")]
InvalidSelector,
#[serde(rename = "invalid session id")]
InvalidSessionId,
#[serde(rename = "javascript error")]
JavaScript,
#[serde(rename = "move target out of bounds")]
MoveTargetOutOfBounds,
#[serde(rename = "no such alert")]
NoSuchAlert,
#[serde(rename = "no such element")]
NoSuchElement,
#[serde(rename = "no such frame")]
NoSuchFrame,
#[serde(rename = "no such window")]
NoSuchWindow,
#[serde(rename = "script timeout")]
ScriptTimeout,
#[serde(rename = "session not created")]
SessionNotCreated,
#[serde(rename = "stale element reference")]
StaleElementReference,
#[serde(rename = "timeout")]
Timeout,
#[serde(rename = "unable to set cookie")]
UnableToSetCookie,
#[serde(rename = "unexpected alert open")]
UnexpectedAlertOpen,
#[serde(rename = "unknown command")]
UnknownCommand,
#[serde(rename = "unknown error")]
Unknown,
#[serde(rename = "unsupported operation")]
UnsupportedOperation,
#[serde(rename = "webdriver error")]
WebDriver,
}
impl ErrorKind {
pub(crate) fn as_str(self) -> &'static str {
use ErrorKind::*;
match self {
ElementClickIntercepted => "element click intercepted",
ElementNotAccessible => "element not accessible",
ElementNotInteractable => "element not interactable",
InsecureCertificate => "insecure certificate",
InvalidArgument => "invalid argument",
InvalidCookieDomain => "invalid cookie",
InvalidElementState => "invalid element state",
InvalidSelector => "invalid selector",
InvalidSessionId => "invalid session id",
JavaScript => "javascript error",
MoveTargetOutOfBounds => "move target out of bounds",
NoSuchAlert => "no such alert",
NoSuchElement => "no such element",
NoSuchFrame => "no such frame",
NoSuchWindow => "no such window",
ScriptTimeout => "script timeout",
SessionNotCreated => "session not created",
StaleElementReference => "stale eelement referencee",
Timeout => "timeout",
UnableToSetCookie => "unable to set cookie",
UnexpectedAlertOpen => "unexpected alert open",
UnknownCommand => "unknown command",
Unknown => "unknown error",
UnsupportedOperation => "unsupported operation",
WebDriver => "webdriver error",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::assert_ser_de;
use serde_json::json;
#[test]
fn test_json_error() {
let err = MarionetteError {
kind: ErrorKind::Timeout,
message: "".into(),
stack: "".into(),
};
assert_ser_de(
&err,
json!({"error": "timeout", "message": "", "stacktrace": ""}),
);
}
}

@ -1,14 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod error;
pub mod common;
pub mod marionette;
pub mod message;
pub mod result;
pub mod webdriver;
#[cfg(test)]
mod test;

@ -1,69 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde::{Deserialize, Serialize};
use crate::common::BoolValue;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
pub enum AppStatus {
eAttemptQuit,
eConsiderQuit,
eForceQuit,
eRestart,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Command {
#[serde(rename = "Marionette:AcceptConnections")]
AcceptConnections(BoolValue),
#[serde(rename = "Marionette:Quit")]
DeleteSession { flags: Vec<AppStatus> },
#[serde(rename = "Marionette:GetContext")]
GetContext,
#[serde(rename = "Marionette:GetScreenOrientation")]
GetScreenOrientation,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::assert_ser_de;
use serde_json::json;
#[test]
fn test_json_command_accept_connections() {
assert_ser_de(
&Command::AcceptConnections(BoolValue::new(false)),
json!({"Marionette:AcceptConnections": {"value": false }}),
);
}
#[test]
fn test_json_command_delete_session() {
let data = &Command::DeleteSession {
flags: vec![AppStatus::eForceQuit],
};
assert_ser_de(data, json!({"Marionette:Quit": {"flags": ["eForceQuit"]}}));
}
#[test]
fn test_json_command_get_context() {
assert_ser_de(&Command::GetContext, json!("Marionette:GetContext"));
}
#[test]
fn test_json_command_get_screen_orientation() {
assert_ser_de(
&Command::GetScreenOrientation,
json!("Marionette:GetScreenOrientation"),
);
}
#[test]
fn test_json_command_invalid() {
assert!(serde_json::from_value::<Command>(json!("foo")).is_err());
}
}

@ -1,336 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde::de::{self, SeqAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{Map, Value};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt;
use crate::error::MarionetteError;
use crate::marionette;
use crate::result::MarionetteResult;
use crate::webdriver;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Command {
WebDriver(webdriver::Command),
Marionette(marionette::Command),
}
impl Command {
pub fn name(&self) -> String {
let (command_name, _) = self.first_entry();
command_name
}
fn params(&self) -> Value {
let (_, params) = self.first_entry();
params
}
fn first_entry(&self) -> (String, serde_json::Value) {
match serde_json::to_value(self).unwrap() {
Value::String(cmd) => (cmd, Value::Object(Map::new())),
Value::Object(items) => {
let mut iter = items.iter();
let (cmd, params) = iter.next().unwrap();
(cmd.to_string(), params.clone())
}
_ => unreachable!(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
enum MessageDirection {
Incoming = 0,
Outgoing = 1,
}
pub type MessageId = u32;
#[derive(Debug, Clone, PartialEq)]
pub struct Request(pub MessageId, pub Command);
impl Request {
pub fn id(&self) -> MessageId {
self.0
}
pub fn command(&self) -> &Command {
&self.1
}
pub fn params(&self) -> Value {
self.command().params()
}
}
impl Serialize for Request {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
(
MessageDirection::Incoming,
self.id(),
self.command().name(),
self.params(),
)
.serialize(serializer)
}
}
#[derive(Debug, PartialEq)]
pub enum Response {
Result {
id: MessageId,
result: MarionetteResult,
},
Error {
id: MessageId,
error: MarionetteError,
},
}
impl Serialize for Response {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Response::Result { id, result } => {
(MessageDirection::Outgoing, id, Value::Null, &result).serialize(serializer)
}
Response::Error { id, error } => {
(MessageDirection::Outgoing, id, &error, Value::Null).serialize(serializer)
}
}
}
}
#[derive(Debug, PartialEq, Serialize)]
#[serde(untagged)]
pub enum Message {
Incoming(Request),
Outgoing(Response),
}
struct MessageVisitor;
impl<'de> Visitor<'de> for MessageVisitor {
type Value = Message;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("four-element array")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let direction = seq
.next_element::<MessageDirection>()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let id: MessageId = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let msg = match direction {
MessageDirection::Incoming => {
let name: String = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
let params: Value = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(3, &self))?;
let command = match params {
Value::Object(ref items) if !items.is_empty() => {
let command_to_params = {
let mut m = Map::new();
m.insert(name, params);
Value::Object(m)
};
serde_json::from_value(command_to_params).map_err(de::Error::custom)
}
Value::Object(_) | Value::Null => {
serde_json::from_value(Value::String(name)).map_err(de::Error::custom)
}
x => Err(de::Error::custom(format!("unknown params type: {}", x))),
}?;
Message::Incoming(Request(id, command))
}
MessageDirection::Outgoing => {
let maybe_error: Option<MarionetteError> = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
let response = if let Some(error) = maybe_error {
seq.next_element::<Value>()?
.ok_or_else(|| de::Error::invalid_length(3, &self))?
.as_null()
.ok_or_else(|| de::Error::invalid_type(Unexpected::Unit, &self))?;
Response::Error { id, error }
} else {
let result: MarionetteResult = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(3, &self))?;
Response::Result { id, result }
};
Message::Outgoing(response)
}
};
Ok(msg)
}
}
impl<'de> Deserialize<'de> for Message {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(MessageVisitor)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
use crate::common::*;
use crate::error::{ErrorKind, MarionetteError};
use crate::test::assert_ser_de;
#[test]
fn test_incoming() {
let json =
json!([0, 42, "WebDriver:FindElement", {"using": "css selector", "value": "value"}]);
let find_element = webdriver::Command::FindElement(webdriver::Locator {
using: webdriver::Selector::Css,
value: "value".into(),
});
let req = Request(42, Command::WebDriver(find_element));
let msg = Message::Incoming(req);
assert_ser_de(&msg, json);
}
#[test]
fn test_incoming_empty_params() {
let json = json!([0, 42, "WebDriver:GetTimeouts", {}]);
let req = Request(42, Command::WebDriver(webdriver::Command::GetTimeouts));
let msg = Message::Incoming(req);
assert_ser_de(&msg, json);
}
#[test]
fn test_incoming_common_params() {
let json = json!([0, 42, "Marionette:AcceptConnections", {"value": false}]);
let params = BoolValue::new(false);
let req = Request(
42,
Command::Marionette(marionette::Command::AcceptConnections(params)),
);
let msg = Message::Incoming(req);
assert_ser_de(&msg, json);
}
#[test]
fn test_incoming_params_derived() {
assert!(serde_json::from_value::<Message>(
json!([0,42,"WebDriver:FindElement",{"using":"foo","value":"foo"}])
)
.is_err());
assert!(serde_json::from_value::<Message>(
json!([0,42,"Marionette:AcceptConnections",{"value":"foo"}])
)
.is_err());
}
#[test]
fn test_incoming_no_params() {
assert!(serde_json::from_value::<Message>(
json!([0,42,"WebDriver:GetTimeouts",{"value":true}])
)
.is_err());
assert!(serde_json::from_value::<Message>(
json!([0,42,"Marionette:Context",{"value":"foo"}])
)
.is_err());
assert!(serde_json::from_value::<Message>(
json!([0,42,"Marionette:GetScreenOrientation",{"value":true}])
)
.is_err());
}
#[test]
fn test_outgoing_result() {
let json = json!([1, 42, null, { "value": null }]);
let result = MarionetteResult::Null;
let msg = Message::Outgoing(Response::Result { id: 42, result });
assert_ser_de(&msg, json);
}
#[test]
fn test_outgoing_error() {
let json =
json!([1, 42, {"error": "no such element", "message": "", "stacktrace": ""}, null]);
let error = MarionetteError {
kind: ErrorKind::NoSuchElement,
message: "".into(),
stack: "".into(),
};
let msg = Message::Outgoing(Response::Error { id: 42, error });
assert_ser_de(&msg, json);
}
#[test]
fn test_invalid_type() {
assert!(
serde_json::from_value::<Message>(json!([2, 42, "WebDriver:GetTimeouts", {}])).is_err()
);
assert!(serde_json::from_value::<Message>(json!([3, 42, "no such element", {}])).is_err());
}
#[test]
fn test_missing_fields() {
// all fields are required
assert!(
serde_json::from_value::<Message>(json!([2, 42, "WebDriver:GetTimeouts"])).is_err()
);
assert!(serde_json::from_value::<Message>(json!([2, 42])).is_err());
assert!(serde_json::from_value::<Message>(json!([2])).is_err());
assert!(serde_json::from_value::<Message>(json!([])).is_err());
}
#[test]
fn test_unknown_command() {
assert!(serde_json::from_value::<Message>(json!([0, 42, "hooba", {}])).is_err());
}
#[test]
fn test_unknown_error() {
assert!(serde_json::from_value::<Message>(json!([1, 42, "flooba", {}])).is_err());
}
#[test]
fn test_message_id_bounds() {
let overflow = i64::from(std::u32::MAX) + 1;
let underflow = -1;
fn get_timeouts(message_id: i64) -> Value {
json!([0, message_id, "WebDriver:GetTimeouts", {}])
}
assert!(serde_json::from_value::<Message>(get_timeouts(overflow)).is_err());
assert!(serde_json::from_value::<Message>(get_timeouts(underflow)).is_err());
}
}

@ -1,223 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use crate::common::{Cookie, Timeouts, WebElement};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NewWindow {
handle: String,
#[serde(rename = "type")]
type_hint: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WindowRect {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ElementRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MarionetteResult {
#[serde(deserialize_with = "from_value", serialize_with = "to_value")]
Bool(bool),
#[serde(deserialize_with = "from_value", serialize_with = "to_empty_value")]
Null,
NewWindow(NewWindow),
WindowRect(WindowRect),
ElementRect(ElementRect),
#[serde(deserialize_with = "from_value", serialize_with = "to_value")]
String(String),
Strings(Vec<String>),
#[serde(deserialize_with = "from_value", serialize_with = "to_value")]
WebElement(WebElement),
WebElements(Vec<WebElement>),
Cookies(Vec<Cookie>),
Timeouts(Timeouts),
}
fn to_value<T, S>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
#[derive(Serialize)]
struct Wrapper<T> {
value: T,
}
Wrapper { value: data }.serialize(serializer)
}
fn to_empty_value<S>(serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Wrapper {
value: Value,
}
Wrapper { value: Value::Null }.serialize(serializer)
}
fn from_value<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: serde::de::DeserializeOwned,
T: std::fmt::Debug,
{
#[derive(Debug, Deserialize)]
struct Wrapper<T> {
value: T,
}
let v = Value::deserialize(deserializer)?;
if v.is_object() {
let w = serde_json::from_value::<Wrapper<T>>(v).map_err(de::Error::custom)?;
Ok(w.value)
} else {
Err(de::Error::custom("Cannot be deserialized to struct"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::{assert_de, assert_ser_de, ELEMENT_KEY};
use serde_json::json;
#[test]
fn test_boolean_response() {
assert_ser_de(&MarionetteResult::Bool(true), json!({"value": true}));
}
#[test]
fn test_cookies_response() {
let mut data = Vec::new();
data.push(Cookie {
name: "foo".into(),
value: "bar".into(),
path: Some("/common".into()),
domain: Some("web-platform.test".into()),
secure: false,
http_only: false,
expiry: None,
same_site: Some("Strict".into()),
});
assert_ser_de(
&MarionetteResult::Cookies(data),
json!([{"name":"foo","value":"bar","path":"/common","domain":"web-platform.test","secure":false,"httpOnly":false,"sameSite":"Strict"}]),
);
}
#[test]
fn test_new_window_response() {
let data = NewWindow {
handle: "6442450945".into(),
type_hint: "tab".into(),
};
let json = json!({"handle": "6442450945", "type": "tab"});
assert_ser_de(&MarionetteResult::NewWindow(data), json);
}
#[test]
fn test_web_element_response() {
let data = WebElement {
element: "foo".into(),
};
assert_ser_de(
&MarionetteResult::WebElement(data),
json!({"value": {ELEMENT_KEY: "foo"}}),
);
}
#[test]
fn test_web_elements_response() {
let data = vec![
WebElement {
element: "foo".into(),
},
WebElement {
element: "bar".into(),
},
];
assert_ser_de(
&MarionetteResult::WebElements(data),
json!([{ELEMENT_KEY: "foo"}, {ELEMENT_KEY: "bar"}]),
);
}
#[test]
fn test_timeouts_response() {
let data = Timeouts {
implicit: Some(1000),
page_load: Some(200000),
script: Some(Some(60000)),
};
assert_ser_de(
&MarionetteResult::Timeouts(data),
json!({"implicit":1000,"pageLoad":200000,"script":60000}),
);
}
#[test]
fn test_string_response() {
assert_ser_de(
&MarionetteResult::String("foo".into()),
json!({"value": "foo"}),
);
}
#[test]
fn test_strings_response() {
assert_ser_de(
&MarionetteResult::Strings(vec!["2147483649".to_string()]),
json!(["2147483649"]),
);
}
#[test]
fn test_null_response() {
assert_ser_de(&MarionetteResult::Null, json!({ "value": null }));
}
#[test]
fn test_window_rect_response() {
let data = WindowRect {
x: 100,
y: 100,
width: 800,
height: 600,
};
let json = json!({"x": 100, "y": 100, "width": 800, "height": 600});
assert_ser_de(&MarionetteResult::WindowRect(data), json);
}
#[test]
fn test_element_rect_response() {
let data = ElementRect {
x: 8.0,
y: 8.0,
width: 148.6666717529297,
height: 22.0,
};
let json = json!({"x": 8, "y": 8, "width": 148.6666717529297, "height": 22});
assert_de(&MarionetteResult::ElementRect(data), json);
}
}

@ -1,35 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub static ELEMENT_KEY: &'static str = "element-6066-11e4-a52e-4f735466cecf";
pub fn assert_ser_de<T>(data: &T, json: serde_json::Value)
where
T: std::fmt::Debug,
T: std::cmp::PartialEq,
T: serde::de::DeserializeOwned,
T: serde::Serialize,
{
assert_eq!(serde_json::to_value(data).unwrap(), json);
assert_eq!(data, &serde_json::from_value::<T>(json).unwrap());
}
#[allow(dead_code)]
pub fn assert_ser<T>(data: &T, json: serde_json::Value)
where
T: std::fmt::Debug,
T: std::cmp::PartialEq,
T: serde::Serialize,
{
assert_eq!(serde_json::to_value(data).unwrap(), json);
}
pub fn assert_de<T>(data: &T, json: serde_json::Value)
where
T: std::fmt::Debug,
T: std::cmp::PartialEq,
T: serde::de::DeserializeOwned,
{
assert_eq!(data, &serde_json::from_value::<T>(json).unwrap());
}

@ -1,512 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::common::{from_cookie, from_name, to_cookie, to_name, Cookie, Frame, Timeouts, Window};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Url {
pub url: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Locator {
pub using: Selector,
pub value: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Selector {
#[serde(rename = "css selector")]
Css,
#[serde(rename = "link text")]
LinkText,
#[serde(rename = "partial link text")]
PartialLinkText,
#[serde(rename = "tag name")]
TagName,
#[serde(rename = "xpath")]
XPath,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NewWindow {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_hint: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WindowRect {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub x: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub y: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub width: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub height: Option<i32>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Keys {
pub text: String,
pub value: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PrintParameters {
pub orientation: PrintOrientation,
pub scale: f64,
pub background: bool,
pub page: PrintPage,
pub margin: PrintMargins,
pub page_ranges: Vec<String>,
pub shrink_to_fit: bool,
}
impl Default for PrintParameters {
fn default() -> Self {
PrintParameters {
orientation: PrintOrientation::default(),
scale: 1.0,
background: false,
page: PrintPage::default(),
margin: PrintMargins::default(),
page_ranges: Vec::new(),
shrink_to_fit: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PrintOrientation {
Landscape,
Portrait,
}
impl Default for PrintOrientation {
fn default() -> Self {
PrintOrientation::Portrait
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PrintPage {
pub width: f64,
pub height: f64,
}
impl Default for PrintPage {
fn default() -> Self {
PrintPage {
width: 21.59,
height: 27.94,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PrintMargins {
pub top: f64,
pub bottom: f64,
pub left: f64,
pub right: f64,
}
impl Default for PrintMargins {
fn default() -> Self {
PrintMargins {
top: 1.0,
bottom: 1.0,
left: 1.0,
right: 1.0,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ScreenshotOptions {
pub id: Option<String>,
pub highlights: Vec<Option<String>>,
pub full: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Script {
pub script: String,
pub args: Option<Vec<Value>>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Command {
#[serde(rename = "WebDriver:AcceptAlert")]
AcceptAlert,
#[serde(
rename = "WebDriver:AddCookie",
serialize_with = "to_cookie",
deserialize_with = "from_cookie"
)]
AddCookie(Cookie),
#[serde(rename = "WebDriver:CloseWindow")]
CloseWindow,
#[serde(
rename = "WebDriver:DeleteCookie",
serialize_with = "to_name",
deserialize_with = "from_name"
)]
DeleteCookie(String),
#[serde(rename = "WebDriver:DeleteAllCookies")]
DeleteCookies,
#[serde(rename = "WebDriver:DeleteSession")]
DeleteSession,
#[serde(rename = "WebDriver:DismissAlert")]
DismissAlert,
#[serde(rename = "WebDriver:ElementClear")]
ElementClear { id: String },
#[serde(rename = "WebDriver:ElementClick")]
ElementClick { id: String },
#[serde(rename = "WebDriver:ElementSendKeys")]
ElementSendKeys {
id: String,
text: String,
value: Vec<String>,
},
#[serde(rename = "WebDriver:ExecuteAsyncScript")]
ExecuteAsyncScript(Script),
#[serde(rename = "WebDriver:ExecuteScript")]
ExecuteScript(Script),
#[serde(rename = "WebDriver:FindElement")]
FindElement(Locator),
#[serde(rename = "WebDriver:FindElements")]
FindElements(Locator),
#[serde(rename = "WebDriver:FindElement")]
FindElementElement {
element: String,
using: Selector,
value: String,
},
#[serde(rename = "WebDriver:FindElements")]
FindElementElements {
element: String,
using: Selector,
value: String,
},
#[serde(rename = "WebDriver:FindElementFromShadowRoot")]
FindShadowRootElement {
#[serde(rename = "shadowRoot")]
shadow_root: String,
using: Selector,
value: String,
},
#[serde(rename = "WebDriver:FindElementsFromShadowRoot")]
FindShadowRootElements {
#[serde(rename = "shadowRoot")]
shadow_root: String,
using: Selector,
value: String,
},
#[serde(rename = "WebDriver:FullscreenWindow")]
FullscreenWindow,
#[serde(rename = "WebDriver:Navigate")]
Get(Url),
#[serde(rename = "WebDriver:GetActiveElement")]
GetActiveElement,
#[serde(rename = "WebDriver:GetAlertText")]
GetAlertText,
#[serde(rename = "WebDriver:GetComputedLabel")]
GetComputedLabel { id: String },
#[serde(rename = "WebDriver:GetComputedRole")]
GetComputedRole { id: String },
#[serde(rename = "WebDriver:GetCookies")]
GetCookies,
#[serde(rename = "WebDriver:GetElementCSSValue")]
GetCSSValue {
id: String,
#[serde(rename = "propertyName")]
property: String,
},
#[serde(rename = "WebDriver:GetCurrentURL")]
GetCurrentUrl,
#[serde(rename = "WebDriver:GetElementAttribute")]
GetElementAttribute { id: String, name: String },
#[serde(rename = "WebDriver:GetElementProperty")]
GetElementProperty { id: String, name: String },
#[serde(rename = "WebDriver:GetElementRect")]
GetElementRect { id: String },
#[serde(rename = "WebDriver:GetElementTagName")]
GetElementTagName { id: String },
#[serde(rename = "WebDriver:GetElementText")]
GetElementText { id: String },
#[serde(rename = "WebDriver:GetPageSource")]
GetPageSource,
#[serde(rename = "WebDriver:GetShadowRoot")]
GetShadowRoot { id: String },
#[serde(rename = "WebDriver:GetTimeouts")]
GetTimeouts,
#[serde(rename = "WebDriver:GetTitle")]
GetTitle,
#[serde(rename = "WebDriver:GetWindowHandle")]
GetWindowHandle,
#[serde(rename = "WebDriver:GetWindowHandles")]
GetWindowHandles,
#[serde(rename = "WebDriver:GetWindowRect")]
GetWindowRect,
#[serde(rename = "WebDriver:Back")]
GoBack,
#[serde(rename = "WebDriver:Forward")]
GoForward,
#[serde(rename = "WebDriver:IsElementDisplayed")]
IsDisplayed { id: String },
#[serde(rename = "WebDriver:IsElementEnabled")]
IsEnabled { id: String },
#[serde(rename = "WebDriver:IsElementSelected")]
IsSelected { id: String },
#[serde(rename = "WebDriver:MaximizeWindow")]
MaximizeWindow,
#[serde(rename = "WebDriver:MinimizeWindow")]
MinimizeWindow,
#[serde(rename = "WebDriver:NewWindow")]
NewWindow(NewWindow),
#[serde(rename = "WebDriver:Print")]
Print(PrintParameters),
#[serde(rename = "WebDriver:Refresh")]
Refresh,
#[serde(rename = "WebDriver:ReleaseActions")]
ReleaseActions,
#[serde(rename = "WebDriver:SendAlertText")]
SendAlertText(Keys),
#[serde(rename = "WebDriver:SetTimeouts")]
SetTimeouts(Timeouts),
#[serde(rename = "WebDriver:SetWindowRect")]
SetWindowRect(WindowRect),
#[serde(rename = "WebDriver:SwitchToFrame")]
SwitchToFrame(Frame),
#[serde(rename = "WebDriver:SwitchToParentFrame")]
SwitchToParentFrame,
#[serde(rename = "WebDriver:SwitchToWindow")]
SwitchToWindow(Window),
#[serde(rename = "WebDriver:TakeScreenshot")]
TakeElementScreenshot(ScreenshotOptions),
#[serde(rename = "WebDriver:TakeScreenshot")]
TakeFullScreenshot(ScreenshotOptions),
#[serde(rename = "WebDriver:TakeScreenshot")]
TakeScreenshot(ScreenshotOptions),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::Date;
use crate::test::{assert_ser, assert_ser_de};
use serde_json::json;
#[test]
fn test_json_screenshot() {
let data = ScreenshotOptions {
id: None,
highlights: vec![],
full: false,
};
let json = json!({"full":false,"highlights":[],"id":null});
assert_ser_de(&data, json);
}
#[test]
fn test_json_selector_css() {
assert_ser_de(&Selector::Css, json!("css selector"));
}
#[test]
fn test_json_selector_link_text() {
assert_ser_de(&Selector::LinkText, json!("link text"));
}
#[test]
fn test_json_selector_partial_link_text() {
assert_ser_de(&Selector::PartialLinkText, json!("partial link text"));
}
#[test]
fn test_json_selector_tag_name() {
assert_ser_de(&Selector::TagName, json!("tag name"));
}
#[test]
fn test_json_selector_xpath() {
assert_ser_de(&Selector::XPath, json!("xpath"));
}
#[test]
fn test_json_selector_invalid() {
assert!(serde_json::from_value::<Selector>(json!("foo")).is_err());
}
#[test]
fn test_json_locator() {
let json = json!({
"using": "partial link text",
"value": "link text",
});
let data = Locator {
using: Selector::PartialLinkText,
value: "link text".into(),
};
assert_ser_de(&data, json);
}
#[test]
fn test_json_keys() {
let data = Keys {
text: "Foo".into(),
value: vec!["F".into(), "o".into(), "o".into()],
};
let json = json!({"text": "Foo", "value": ["F", "o", "o"]});
assert_ser_de(&data, json);
}
#[test]
fn test_json_new_window() {
let data = NewWindow {
type_hint: Some("foo".into()),
};
assert_ser_de(&data, json!({ "type": "foo" }));
}
#[test]
fn test_json_window_rect() {
let data = WindowRect {
x: Some(123),
y: None,
width: None,
height: None,
};
assert_ser_de(&data, json!({"x": 123}));
}
#[test]
fn test_command_with_params() {
let locator = Locator {
using: Selector::Css,
value: "value".into(),
};
let json = json!({"WebDriver:FindElement": {"using": "css selector", "value": "value"}});
assert_ser_de(&Command::FindElement(locator), json);
}
#[test]
fn test_command_with_wrapper_params() {
let cookie = Cookie {
name: "hello".into(),
value: "world".into(),
path: None,
domain: None,
secure: false,
http_only: false,
expiry: Some(Date(1564488092)),
same_site: None,
};
let json = json!({"WebDriver:AddCookie": {"cookie": {"name": "hello", "value": "world", "secure": false, "httpOnly": false, "expiry": 1564488092}}});
assert_ser_de(&Command::AddCookie(cookie), json);
}
#[test]
fn test_empty_commands() {
assert_ser_de(&Command::GetTimeouts, json!("WebDriver:GetTimeouts"));
}
#[test]
fn test_json_command_invalid() {
assert!(serde_json::from_value::<Command>(json!("foo")).is_err());
}
#[test]
fn test_json_delete_cookie_command() {
let json = json!({"WebDriver:DeleteCookie": {"name": "foo"}});
assert_ser_de(&Command::DeleteCookie("foo".into()), json);
}
#[test]
fn test_json_new_window_command() {
let data = NewWindow {
type_hint: Some("foo".into()),
};
let json = json!({"WebDriver:NewWindow": {"type": "foo"}});
assert_ser_de(&Command::NewWindow(data), json);
}
#[test]
fn test_json_new_window_command_with_none_value() {
let data = NewWindow { type_hint: None };
let json = json!({"WebDriver:NewWindow": {}});
assert_ser_de(&Command::NewWindow(data), json);
}
#[test]
fn test_json_command_as_struct() {
assert_ser(
&Command::FindElementElement {
element: "foo".into(),
using: Selector::XPath,
value: "bar".into(),
},
json!({"WebDriver:FindElement": {"element": "foo", "using": "xpath", "value": "bar" }}),
);
}
#[test]
fn test_json_get_computed_label_command() {
assert_ser_de(
&Command::GetComputedLabel { id: "foo".into() },
json!({"WebDriver:GetComputedLabel": {"id": "foo"}}),
);
}
#[test]
fn test_json_get_computed_role_command() {
assert_ser_de(
&Command::GetComputedRole { id: "foo".into() },
json!({"WebDriver:GetComputedRole": {"id": "foo"}}),
);
}
#[test]
fn test_json_get_css_value() {
assert_ser_de(
&Command::GetCSSValue {
id: "foo".into(),
property: "bar".into(),
},
json!({"WebDriver:GetElementCSSValue": {"id": "foo", "propertyName": "bar"}}),
);
}
#[test]
fn test_json_find_shadow_root_element() {
assert_ser_de(
&Command::FindShadowRootElement {
shadow_root: "foo".into(),
using: Selector::Css,
value: "bar".into(),
},
json!({"WebDriver:FindElementFromShadowRoot": {"shadowRoot": "foo", "using": "css selector", "value": "bar"}}),
);
}
#[test]
fn test_json_find_shadow_root_elements() {
assert_ser_de(
&Command::FindShadowRootElements {
shadow_root: "foo".into(),
using: Selector::Css,
value: "bar".into(),
},
json!({"WebDriver:FindElementsFromShadowRoot": {"shadowRoot": "foo", "using": "css selector", "value": "bar"}}),
);
}
}

@ -1,533 +0,0 @@
use crate::capabilities::AndroidOptions;
use mozdevice::{AndroidStorage, Device, Host, UnixPathBuf};
use mozprofile::profile::Profile;
use serde::Serialize;
use serde_yaml::{Mapping, Value};
use std::fmt;
use std::io;
use std::time;
use webdriver::error::{ErrorStatus, WebDriverError};
// TODO: avoid port clashes across GeckoView-vehicles.
// For now, we always use target port 2829, leading to issues like bug 1533704.
const MARIONETTE_TARGET_PORT: u16 = 2829;
const CONFIG_FILE_HEADING: &str = r#"## GeckoView configuration YAML
##
## Auto-generated by geckodriver.
## See https://mozilla.github.io/geckoview/consumer/docs/automation.
"#;
pub type Result<T> = std::result::Result<T, AndroidError>;
#[derive(Debug)]
pub enum AndroidError {
ActivityNotFound(String),
Device(mozdevice::DeviceError),
IO(io::Error),
PackageNotFound(String),
Serde(serde_yaml::Error),
}
impl fmt::Display for AndroidError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
AndroidError::ActivityNotFound(ref package) => {
write!(f, "Activity for package '{}' not found", package)
}
AndroidError::Device(ref message) => message.fmt(f),
AndroidError::IO(ref message) => message.fmt(f),
AndroidError::PackageNotFound(ref package) => {
write!(f, "Package '{}' not found", package)
}
AndroidError::Serde(ref message) => message.fmt(f),
}
}
}
impl From<io::Error> for AndroidError {
fn from(value: io::Error) -> AndroidError {
AndroidError::IO(value)
}
}
impl From<mozdevice::DeviceError> for AndroidError {
fn from(value: mozdevice::DeviceError) -> AndroidError {
AndroidError::Device(value)
}
}
impl From<serde_yaml::Error> for AndroidError {
fn from(value: serde_yaml::Error) -> AndroidError {
AndroidError::Serde(value)
}
}
impl From<AndroidError> for WebDriverError {
fn from(value: AndroidError) -> WebDriverError {
WebDriverError::new(ErrorStatus::UnknownError, value.to_string())
}
}
/// A remote Gecko instance.
///
/// Host refers to the device running `geckodriver`. Target refers to the
/// Android device running Gecko in a GeckoView-based vehicle.
#[derive(Debug)]
pub struct AndroidProcess {
pub device: Device,
pub package: String,
pub activity: String,
}
impl AndroidProcess {
pub fn new(
device: Device,
package: String,
activity: String,
) -> mozdevice::Result<AndroidProcess> {
Ok(AndroidProcess {
device,
package,
activity,
})
}
}
#[derive(Debug)]
pub struct AndroidHandler {
pub config: UnixPathBuf,
pub options: AndroidOptions,
pub process: AndroidProcess,
pub profile: UnixPathBuf,
pub test_root: UnixPathBuf,
// Port forwarding for Marionette: host => target
pub marionette_host_port: u16,
pub marionette_target_port: u16,
// Port forwarding for WebSocket connections (WebDriver BiDi and CDP)
pub websocket_port: Option<u16>,
}
impl Drop for AndroidHandler {
fn drop(&mut self) {
// Try to clean up various settings
let clear_command = format!("am clear-debug-app {}", self.process.package);
match self
.process
.device
.execute_host_shell_command(&clear_command)
{
Ok(_) => debug!("Disabled reading from configuration file"),
Err(e) => error!("Failed disabling from configuration file: {}", e),
}
match self.process.device.remove(&self.config) {
Ok(_) => debug!("Deleted GeckoView configuration file"),
Err(e) => error!("Failed deleting GeckoView configuration file: {}", e),
}
match self.process.device.remove(&self.test_root) {
Ok(_) => debug!("Deleted test root folder: {}", &self.test_root.display()),
Err(e) => error!("Failed deleting test root folder: {}", e),
}
match self
.process
.device
.kill_forward_port(self.marionette_host_port)
{
Ok(_) => debug!(
"Marionette port forward ({} -> {}) stopped",
&self.marionette_host_port, &self.marionette_target_port
),
Err(e) => error!(
"Marionette port forward ({} -> {}) failed to stop: {}",
&self.marionette_host_port, &self.marionette_target_port, e
),
}
if let Some(port) = self.websocket_port {
match self.process.device.kill_forward_port(port) {
Ok(_) => debug!("WebSocket port forward ({0} -> {0}) stopped", &port),
Err(e) => error!(
"WebSocket port forward ({0} -> {0}) failed to stop: {1}",
&port, e
),
}
}
}
}
impl AndroidHandler {
pub fn new(
options: &AndroidOptions,
marionette_host_port: u16,
websocket_port: Option<u16>,
) -> Result<AndroidHandler> {
// We need to push profile.pathbuf to a safe space on the device.
// Make it per-Android package to avoid clashes and confusion.
// This naming scheme follows GeckoView's configuration file naming scheme,
// see bug 1533385.
let host = Host {
host: None,
port: None,
read_timeout: Some(time::Duration::from_millis(5000)),
write_timeout: Some(time::Duration::from_millis(5000)),
};
let mut device = host.device_or_default(options.device_serial.as_ref(), options.storage)?;
// Set up port forwarding for Marionette.
device.forward_port(marionette_host_port, MARIONETTE_TARGET_PORT)?;
debug!(
"Marionette port forward ({} -> {}) started",
marionette_host_port, MARIONETTE_TARGET_PORT
);
if let Some(port) = websocket_port {
// Set up port forwarding for WebSocket connections (WebDriver BiDi, and CDP).
device.forward_port(port, port)?;
debug!("WebSocket port forward ({} -> {}) started", port, port);
}
let test_root = match device.storage {
AndroidStorage::App => {
device.run_as_package = Some(options.package.to_owned());
let mut buf = UnixPathBuf::from("/data/data");
buf.push(&options.package);
buf.push("test_root");
buf
}
AndroidStorage::Internal => UnixPathBuf::from("/data/local/tmp/test_root"),
AndroidStorage::Sdcard => {
// We need to push the profile to a location on the device that can also
// be read and write by the application, and works for unrooted devices.
// The only location that meets this criteria is under:
// $EXTERNAL_STORAGE/Android/data/%options.package%/files
let response = device.execute_host_shell_command("echo $EXTERNAL_STORAGE")?;
let mut buf = UnixPathBuf::from(response.trim_end_matches('\n'));
buf.push("Android/data");
buf.push(&options.package);
buf.push("files/test_root");
buf
}
};
debug!(
"Connecting: options={:?}, storage={:?}) test_root={}, run_as_package={:?}",
options,
device.storage,
test_root.display(),
device.run_as_package
);
let mut profile = test_root.clone();
profile.push(format!("{}-geckodriver-profile", &options.package));
// Check if the specified package is installed
let response =
device.execute_host_shell_command(&format!("pm list packages {}", &options.package))?;
let mut packages = response
.trim()
.split_terminator('\n')
.filter(|line| line.starts_with("package:"))
.map(|line| line.rsplit(':').next().expect("Package name found"));
if !packages.any(|x| x == options.package.as_str()) {
return Err(AndroidError::PackageNotFound(options.package.clone()));
}
let config = UnixPathBuf::from(format!(
"/data/local/tmp/{}-geckoview-config.yaml",
&options.package
));
// If activity hasn't been specified default to the main activity of the package
let activity = match options.activity {
Some(ref activity) => activity.clone(),
None => {
let response = device.execute_host_shell_command(&format!(
"cmd package resolve-activity --brief {}",
&options.package
))?;
let activities = response
.split_terminator('\n')
.filter(|line| line.starts_with(&options.package))
.map(|line| line.rsplit('/').next().unwrap())
.collect::<Vec<&str>>();
if activities.is_empty() {
return Err(AndroidError::ActivityNotFound(options.package.clone()));
}
activities[0].to_owned()
}
};
let process = AndroidProcess::new(device, options.package.clone(), activity)?;
Ok(AndroidHandler {
config,
process,
profile,
test_root,
marionette_host_port,
marionette_target_port: MARIONETTE_TARGET_PORT,
options: options.clone(),
websocket_port,
})
}
pub fn generate_config_file<I, K, V>(
&self,
args: Option<Vec<String>>,
envs: I,
) -> Result<String>
where
I: IntoIterator<Item = (K, V)>,
K: ToString,
V: ToString,
{
// To configure GeckoView, we use the automation techniques documented at
// https://mozilla.github.io/geckoview/consumer/docs/automation.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
pub struct Config {
pub env: Mapping,
pub args: Vec<String>,
}
let mut config = Config {
args: vec![
"--marionette".into(),
"--profile".into(),
self.profile.display().to_string(),
],
env: Mapping::new(),
};
config.args.append(&mut args.unwrap_or_default());
for (key, value) in envs {
config.env.insert(
Value::String(key.to_string()),
Value::String(value.to_string()),
);
}
config.env.insert(
Value::String("MOZ_CRASHREPORTER".to_owned()),
Value::String("1".to_owned()),
);
config.env.insert(
Value::String("MOZ_CRASHREPORTER_NO_REPORT".to_owned()),
Value::String("1".to_owned()),
);
config.env.insert(
Value::String("MOZ_CRASHREPORTER_SHUTDOWN".to_owned()),
Value::String("1".to_owned()),
);
let mut contents: Vec<String> = vec![CONFIG_FILE_HEADING.to_owned()];
contents.push(serde_yaml::to_string(&config)?);
Ok(contents.concat())
}
pub fn prepare<I, K, V>(
&self,
profile: &Profile,
args: Option<Vec<String>>,
env: I,
) -> Result<()>
where
I: IntoIterator<Item = (K, V)>,
K: ToString,
V: ToString,
{
self.process.device.clear_app_data(&self.process.package)?;
// These permissions, at least, are required to read profiles in /mnt/sdcard.
for perm in &["READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"] {
self.process.device.execute_host_shell_command(&format!(
"pm grant {} android.permission.{}",
&self.process.package, perm
))?;
}
// Make sure to create the test root.
self.process.device.create_dir(&self.test_root)?;
self.process.device.chmod(&self.test_root, "777", true)?;
// Replace the profile
self.process.device.remove(&self.profile)?;
self.process
.device
.push_dir(&profile.path, &self.profile, 0o777)?;
let contents = self.generate_config_file(args, env)?;
debug!("Content of generated GeckoView config file:\n{}", contents);
let reader = &mut io::BufReader::new(contents.as_bytes());
debug!(
"Pushing GeckoView configuration file to {}",
self.config.display()
);
self.process.device.push(reader, &self.config, 0o777)?;
// Tell GeckoView to read configuration even when `android:debuggable="false"`.
self.process.device.execute_host_shell_command(&format!(
"am set-debug-app --persistent {}",
self.process.package
))?;
Ok(())
}
pub fn launch(&self) -> Result<()> {
// TODO: Remove the usage of intent arguments once Fennec is no longer
// supported. Packages which are using GeckoView always read the arguments
// via the YAML configuration file.
let mut intent_arguments = self
.options
.intent_arguments
.clone()
.unwrap_or_else(|| Vec::with_capacity(3));
intent_arguments.push("--es".to_owned());
intent_arguments.push("args".to_owned());
intent_arguments.push(format!("--marionette --profile {}", self.profile.display()));
debug!(
"Launching {}/{}",
self.process.package, self.process.activity
);
self.process
.device
.launch(
&self.process.package,
&self.process.activity,
&intent_arguments,
)
.map_err(|e| {
let message = format!(
"Could not launch Android {}/{}: {}",
self.process.package, self.process.activity, e
);
mozdevice::DeviceError::Adb(message)
})?;
Ok(())
}
pub fn force_stop(&self) -> Result<()> {
debug!(
"Force stopping the Android package: {}",
&self.process.package
);
self.process.device.force_stop(&self.process.package)?;
Ok(())
}
}
#[cfg(test)]
mod test {
// To successfully run those tests the geckoview_example package needs to
// be installed on the device or emulator. After setting up the build
// environment (https://mzl.la/3muLv5M), the following mach commands have to
// be executed:
//
// $ ./mach build && ./mach install
//
// Currently the mozdevice API is not safe for multiple requests at the same
// time. It is recommended to run each of the unit tests on its own. Also adb
// specific tests cannot be run in CI yet. To check those locally, also run
// the ignored tests.
//
// Use the following command to accomplish that:
//
// $ cargo test -- --ignored --test-threads=1
use crate::android::AndroidHandler;
use crate::capabilities::AndroidOptions;
use mozdevice::{AndroidStorage, AndroidStorageInput, UnixPathBuf};
fn run_handler_storage_test(package: &str, storage: AndroidStorageInput) {
let options = AndroidOptions::new(package.to_owned(), storage);
let handler = AndroidHandler::new(&options, 4242, None).expect("has valid Android handler");
assert_eq!(handler.options, options);
assert_eq!(handler.process.package, package);
let expected_config_path = UnixPathBuf::from(format!(
"/data/local/tmp/{}-geckoview-config.yaml",
&package
));
assert_eq!(handler.config, expected_config_path);
if handler.process.device.storage == AndroidStorage::App {
assert_eq!(
handler.process.device.run_as_package,
Some(package.to_owned())
);
} else {
assert_eq!(handler.process.device.run_as_package, None);
}
let test_root = match handler.process.device.storage {
AndroidStorage::App => {
let mut buf = UnixPathBuf::from("/data/data");
buf.push(&package);
buf.push("test_root");
buf
}
AndroidStorage::Internal => UnixPathBuf::from("/data/local/tmp/test_root"),
AndroidStorage::Sdcard => {
let response = handler
.process
.device
.execute_host_shell_command("echo $EXTERNAL_STORAGE")
.unwrap();
let mut buf = UnixPathBuf::from(response.trim_end_matches('\n'));
buf.push("Android/data/");
buf.push(&package);
buf.push("files/test_root");
buf
}
};
assert_eq!(handler.test_root, test_root);
let mut profile = test_root;
profile.push(format!("{}-geckodriver-profile", &package));
assert_eq!(handler.profile, profile);
}
#[test]
#[ignore]
fn android_handler_storage_as_app() {
let package = "org.mozilla.geckoview_example";
run_handler_storage_test(package, AndroidStorageInput::App);
}
#[test]
#[ignore]
fn android_handler_storage_as_auto() {
let package = "org.mozilla.geckoview_example";
run_handler_storage_test(package, AndroidStorageInput::Auto);
}
#[test]
#[ignore]
fn android_handler_storage_as_internal() {
let package = "org.mozilla.geckoview_example";
run_handler_storage_test(package, AndroidStorageInput::Internal);
}
#[test]
#[ignore]
fn android_handler_storage_as_sdcard() {
let package = "org.mozilla.geckoview_example";
run_handler_storage_test(package, AndroidStorageInput::Sdcard);
}
}

@ -1,554 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::android::AndroidHandler;
use crate::capabilities::{FirefoxOptions, ProfileType};
use crate::logging;
use crate::prefs;
use mozprofile::preferences::Pref;
use mozprofile::profile::{PrefFile, Profile};
use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time;
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
/// A running Gecko instance.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum Browser {
Local(LocalBrowser),
Remote(RemoteBrowser),
/// An existing browser instance not controlled by GeckoDriver
Existing(u16),
}
impl Browser {
pub(crate) fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> {
match self {
Browser::Local(x) => x.close(wait_for_shutdown),
Browser::Remote(x) => x.close(),
Browser::Existing(_) => Ok(()),
}
}
pub(crate) fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
match self {
Browser::Local(x) => x.marionette_port(),
Browser::Remote(x) => x.marionette_port(),
Browser::Existing(x) => Ok(Some(*x)),
}
}
pub(crate) fn update_marionette_port(&mut self, port: u16) {
match self {
Browser::Local(x) => x.update_marionette_port(port),
Browser::Remote(x) => x.update_marionette_port(port),
Browser::Existing(x) => {
if port != *x {
error!(
"Cannot re-assign Marionette port when connected to an existing browser"
);
}
}
}
}
}
#[derive(Debug)]
/// A local Firefox process, running on this (host) device.
pub(crate) struct LocalBrowser {
marionette_port: u16,
prefs_backup: Option<PrefsBackup>,
process: FirefoxProcess,
profile_path: Option<PathBuf>,
}
impl LocalBrowser {
pub(crate) fn new(
options: FirefoxOptions,
marionette_port: u16,
jsdebugger: bool,
profile_root: Option<&Path>,
) -> WebDriverResult<LocalBrowser> {
let binary = options.binary.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
"Expected browser binary location, but unable to find \
binary in default location, no \
'moz:firefoxOptions.binary' capability provided, and \
no binary flag set on the command line",
)
})?;
let is_custom_profile = matches!(options.profile, ProfileType::Path(_));
let mut profile = match options.profile {
ProfileType::Named => None,
ProfileType::Path(x) => Some(x),
ProfileType::Temporary => Some(Profile::new(profile_root)?),
};
let (profile_path, prefs_backup) = if let Some(ref mut profile) = profile {
let profile_path = profile.path.clone();
let prefs_backup = set_prefs(
marionette_port,
profile,
is_custom_profile,
options.prefs,
jsdebugger,
)
.map_err(|e| {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
format!("Failed to set preferences: {}", e),
)
})?;
(Some(profile_path), prefs_backup)
} else {
warn!("Unable to set geckodriver prefs when using a named profile");
(None, None)
};
let mut runner = FirefoxRunner::new(&binary, profile);
runner.arg("--marionette");
if jsdebugger {
runner.arg("--jsdebugger");
}
if let Some(args) = options.args.as_ref() {
runner.args(args);
}
// https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
runner
.env("MOZ_CRASHREPORTER", "1")
.env("MOZ_CRASHREPORTER_NO_REPORT", "1")
.env("MOZ_CRASHREPORTER_SHUTDOWN", "1");
let process = match runner.start() {
Ok(process) => process,
Err(e) => {
if let Some(backup) = prefs_backup {
backup.restore();
}
return Err(WebDriverError::new(
ErrorStatus::SessionNotCreated,
format!("Failed to start browser {}: {}", binary.display(), e),
));
}
};
Ok(LocalBrowser {
marionette_port,
prefs_backup,
process,
profile_path,
})
}
fn close(mut self, wait_for_shutdown: bool) -> WebDriverResult<()> {
if wait_for_shutdown {
// TODO(https://bugzil.la/1443922):
// Use toolkit.asyncshutdown.crash_timout pref
let duration = time::Duration::from_secs(70);
match self.process.wait(duration) {
Ok(x) => debug!("Browser process stopped: {}", x),
Err(e) => error!("Failed to stop browser process: {}", e),
}
}
self.process.kill()?;
// Restoring the prefs if the browser fails to stop perhaps doesn't work anyway
if let Some(prefs_backup) = self.prefs_backup {
prefs_backup.restore();
};
Ok(())
}
fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
if self.marionette_port != 0 {
return Ok(Some(self.marionette_port));
}
if let Some(profile_path) = self.profile_path.as_ref() {
return Ok(read_marionette_port(profile_path));
}
// This should be impossible, but it isn't enforced
Err(WebDriverError::new(
ErrorStatus::SessionNotCreated,
"Port not known when using named profile",
))
}
fn update_marionette_port(&mut self, port: u16) {
self.marionette_port = port;
}
pub(crate) fn check_status(&mut self) -> Option<String> {
match self.process.try_wait() {
Ok(Some(status)) => Some(
status
.code()
.map(|c| c.to_string())
.unwrap_or_else(|| "signal".into()),
),
Ok(None) => None,
Err(_) => Some("{unknown}".into()),
}
}
}
fn read_marionette_port(profile_path: &Path) -> Option<u16> {
let port_file = profile_path.join("MarionetteActivePort");
let mut port_str = String::with_capacity(6);
let mut file = match fs::File::open(&port_file) {
Ok(file) => file,
Err(_) => {
trace!("Failed to open {}", &port_file.to_string_lossy());
return None;
}
};
if let Err(e) = file.read_to_string(&mut port_str) {
trace!("Failed to read {}: {}", &port_file.to_string_lossy(), e);
return None;
};
println!("Read port: {}", port_str);
let port = port_str.parse::<u16>().ok();
if port.is_none() {
warn!("Failed fo convert {} to u16", &port_str);
}
port
}
#[derive(Debug)]
/// A remote instance, running on a (target) Android device.
pub(crate) struct RemoteBrowser {
handler: AndroidHandler,
marionette_port: u16,
prefs_backup: Option<PrefsBackup>,
}
impl RemoteBrowser {
pub(crate) fn new(
options: FirefoxOptions,
marionette_port: u16,
websocket_port: Option<u16>,
profile_root: Option<&Path>,
) -> WebDriverResult<RemoteBrowser> {
let android_options = options.android.unwrap();
let handler = AndroidHandler::new(&android_options, marionette_port, websocket_port)?;
// Profile management.
let (mut profile, is_custom_profile) = match options.profile {
ProfileType::Named => {
return Err(WebDriverError::new(
ErrorStatus::SessionNotCreated,
"Cannot use a named profile on Android",
));
}
ProfileType::Path(x) => (x, true),
ProfileType::Temporary => (Profile::new(profile_root)?, false),
};
let prefs_backup = set_prefs(
handler.marionette_target_port,
&mut profile,
is_custom_profile,
options.prefs,
false,
)
.map_err(|e| {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
format!("Failed to set preferences: {}", e),
)
})?;
handler.prepare(&profile, options.args, options.env.unwrap_or_default())?;
handler.launch()?;
Ok(RemoteBrowser {
handler,
marionette_port,
prefs_backup,
})
}
fn close(self) -> WebDriverResult<()> {
self.handler.force_stop()?;
// Restoring the prefs if the browser fails to stop perhaps doesn't work anyway
if let Some(prefs_backup) = self.prefs_backup {
prefs_backup.restore();
};
Ok(())
}
fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
Ok(Some(self.marionette_port))
}
fn update_marionette_port(&mut self, port: u16) {
self.marionette_port = port;
}
}
fn set_prefs(
port: u16,
profile: &mut Profile,
custom_profile: bool,
extra_prefs: Vec<(String, Pref)>,
js_debugger: bool,
) -> WebDriverResult<Option<PrefsBackup>> {
let prefs = profile.user_prefs().map_err(|_| {
WebDriverError::new(
ErrorStatus::UnknownError,
"Unable to read profile preferences file",
)
})?;
let backup_prefs = if custom_profile && prefs.path.exists() {
Some(PrefsBackup::new(prefs)?)
} else {
None
};
for &(name, ref value) in prefs::DEFAULT.iter() {
if !custom_profile || !prefs.contains_key(name) {
prefs.insert(name.to_string(), (*value).clone());
}
}
prefs.insert_slice(&extra_prefs[..]);
if js_debugger {
prefs.insert("devtools.browsertoolbox.panel", Pref::new("jsdebugger"));
prefs.insert("devtools.debugger.remote-enabled", Pref::new(true));
prefs.insert("devtools.chrome.enabled", Pref::new(true));
prefs.insert("devtools.debugger.prompt-connection", Pref::new(false));
}
prefs.insert("marionette.port", Pref::new(port));
prefs.insert("remote.log.level", logging::max_level().into());
prefs.write().map_err(|e| {
WebDriverError::new(
ErrorStatus::UnknownError,
format!("Unable to write Firefox profile: {}", e),
)
})?;
Ok(backup_prefs)
}
#[derive(Debug)]
struct PrefsBackup {
orig_path: PathBuf,
backup_path: PathBuf,
}
impl PrefsBackup {
fn new(prefs: &PrefFile) -> WebDriverResult<PrefsBackup> {
let mut prefs_backup_path = prefs.path.clone();
let mut counter = 0;
while {
let ext = if counter > 0 {
format!("geckodriver_backup_{}", counter)
} else {
"geckodriver_backup".to_string()
};
prefs_backup_path.set_extension(ext);
prefs_backup_path.exists()
} {
counter += 1
}
debug!("Backing up prefs to {:?}", prefs_backup_path);
fs::copy(&prefs.path, &prefs_backup_path)?;
Ok(PrefsBackup {
orig_path: prefs.path.clone(),
backup_path: prefs_backup_path,
})
}
fn restore(self) {
if self.backup_path.exists() {
let _ = fs::rename(self.backup_path, self.orig_path);
}
}
}
#[cfg(test)]
mod tests {
use super::set_prefs;
use crate::browser::read_marionette_port;
use crate::capabilities::{FirefoxOptions, ProfileType};
use mozprofile::preferences::{Pref, PrefValue};
use mozprofile::profile::Profile;
use serde_json::{Map, Value};
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use tempfile::tempdir;
fn example_profile() -> Value {
let mut profile_data = Vec::with_capacity(1024);
let mut profile = File::open("src/tests/profile.zip").unwrap();
profile.read_to_end(&mut profile_data).unwrap();
Value::String(base64::encode(&profile_data))
}
// This is not a pretty test, mostly due to the nature of
// mozprofile's and MarionetteHandler's APIs, but we have had
// several regressions related to remote.log.level.
#[test]
fn test_remote_log_level() {
let mut profile = Profile::new(None).unwrap();
set_prefs(2828, &mut profile, false, vec![], false).ok();
let user_prefs = profile.user_prefs().unwrap();
let pref = user_prefs.get("remote.log.level").unwrap();
let value = match pref.value {
PrefValue::String(ref s) => s,
_ => panic!(),
};
for (i, ch) in value.chars().enumerate() {
if i == 0 {
assert!(ch.is_uppercase());
} else {
assert!(ch.is_lowercase());
}
}
}
#[test]
fn test_prefs() {
let marionette_settings = Default::default();
let encoded_profile = example_profile();
let mut prefs: Map<String, Value> = Map::new();
prefs.insert(
"browser.display.background_color".into(),
Value::String("#00ff00".into()),
);
let mut firefox_opts = Map::new();
firefox_opts.insert("profile".into(), encoded_profile);
firefox_opts.insert("prefs".into(), Value::Object(prefs));
let mut caps = Map::new();
caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts));
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
.expect("Valid profile and prefs");
let mut profile = match opts.profile {
ProfileType::Path(profile) => profile,
_ => panic!("Expected ProfileType::Path"),
};
set_prefs(2828, &mut profile, true, opts.prefs, false).expect("set preferences");
let prefs_set = profile.user_prefs().expect("valid user preferences");
println!("{:#?}", prefs_set.prefs);
assert_eq!(
prefs_set.get("startup.homepage_welcome_url"),
Some(&Pref::new("data:text/html,PASS"))
);
assert_eq!(
prefs_set.get("browser.display.background_color"),
Some(&Pref::new("#00ff00"))
);
assert_eq!(prefs_set.get("marionette.port"), Some(&Pref::new(2828)));
}
#[test]
fn test_pref_backup() {
let mut profile = Profile::new(None).unwrap();
// Create some prefs in the profile
let initial_prefs = profile.user_prefs().unwrap();
initial_prefs.insert("geckodriver.example", Pref::new("example"));
initial_prefs.write().unwrap();
let prefs_path = initial_prefs.path.clone();
let mut conflicting_backup_path = initial_prefs.path.clone();
conflicting_backup_path.set_extension("geckodriver_backup");
println!("{:?}", conflicting_backup_path);
let mut file = File::create(&conflicting_backup_path).unwrap();
file.write_all(b"test").unwrap();
assert!(conflicting_backup_path.exists());
let mut initial_prefs_data = String::new();
File::open(&prefs_path)
.expect("Initial prefs exist")
.read_to_string(&mut initial_prefs_data)
.unwrap();
let backup = set_prefs(2828, &mut profile, true, vec![], false)
.unwrap()
.unwrap();
let user_prefs = profile.user_prefs().unwrap();
assert!(user_prefs.path.exists());
let mut backup_path = user_prefs.path.clone();
backup_path.set_extension("geckodriver_backup_1");
assert!(backup_path.exists());
// Ensure the actual prefs contain both the existing ones and the ones we added
let pref = user_prefs.get("marionette.port").unwrap();
assert_eq!(pref.value, PrefValue::Int(2828));
let pref = user_prefs.get("geckodriver.example").unwrap();
assert_eq!(pref.value, PrefValue::String("example".into()));
// Ensure the backup prefs don't contain the new settings
let mut backup_data = String::new();
File::open(&backup_path)
.expect("Backup prefs exist")
.read_to_string(&mut backup_data)
.unwrap();
assert_eq!(backup_data, initial_prefs_data);
backup.restore();
assert!(!backup_path.exists());
let mut final_prefs_data = String::new();
File::open(&prefs_path)
.expect("Initial prefs exist")
.read_to_string(&mut final_prefs_data)
.unwrap();
assert_eq!(final_prefs_data, initial_prefs_data);
}
#[test]
fn test_local_read_marionette_port() {
fn create_port_file(profile_path: &Path, data: &[u8]) {
let port_path = profile_path.join("MarionetteActivePort");
let mut file = File::create(&port_path).unwrap();
file.write_all(data).unwrap();
}
let profile_dir = tempdir().unwrap();
let profile_path = profile_dir.path();
assert_eq!(read_marionette_port(profile_path), None);
assert_eq!(read_marionette_port(profile_path), None);
create_port_file(profile_path, b"");
assert_eq!(read_marionette_port(profile_path), None);
create_port_file(profile_path, b"1234");
assert_eq!(read_marionette_port(profile_path), Some(1234));
create_port_file(profile_path, b"1234abc");
assert_eq!(read_marionette_port(profile_path), None);
}
}

@ -1,47 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde_json::Value;
use std::fmt;
include!(concat!(env!("OUT_DIR"), "/build-info.rs"));
pub struct BuildInfo;
impl BuildInfo {
pub fn version() -> &'static str {
crate_version!()
}
pub fn hash() -> Option<&'static str> {
COMMIT_HASH
}
pub fn date() -> Option<&'static str> {
COMMIT_DATE
}
}
impl fmt::Display for BuildInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", BuildInfo::version())?;
match (BuildInfo::hash(), BuildInfo::date()) {
(Some(hash), Some(date)) => write!(f, " ({} {})", hash, date)?,
(Some(hash), None) => write!(f, " ({})", hash)?,
_ => {}
}
Ok(())
}
}
impl From<BuildInfo> for Value {
fn from(_: BuildInfo) -> Value {
Value::String(BuildInfo::version().to_string())
}
}
/// Returns build-time information about geckodriver.
pub fn build_info() -> BuildInfo {
BuildInfo {}
}

File diff suppressed because it is too large Load Diff

@ -1,339 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::logging;
use hyper::Method;
use serde::de::{self, Deserialize, Deserializer};
use serde_json::{self, Value};
use std::env;
use std::fs::File;
use std::io::prelude::*;
use uuid::Uuid;
use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand};
use webdriver::error::WebDriverResult;
use webdriver::httpapi::WebDriverExtensionRoute;
use webdriver::Parameters;
pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
vec![
(
Method::GET,
"/session/{sessionId}/moz/context",
GeckoExtensionRoute::GetContext,
),
(
Method::POST,
"/session/{sessionId}/moz/context",
GeckoExtensionRoute::SetContext,
),
(
Method::POST,
"/session/{sessionId}/moz/addon/install",
GeckoExtensionRoute::InstallAddon,
),
(
Method::POST,
"/session/{sessionId}/moz/addon/uninstall",
GeckoExtensionRoute::UninstallAddon,
),
(
Method::GET,
"/session/{sessionId}/moz/screenshot/full",
GeckoExtensionRoute::TakeFullScreenshot,
),
]
}
#[derive(Clone, PartialEq, Eq)]
pub enum GeckoExtensionRoute {
GetContext,
SetContext,
InstallAddon,
UninstallAddon,
TakeFullScreenshot,
}
impl WebDriverExtensionRoute for GeckoExtensionRoute {
type Command = GeckoExtensionCommand;
fn command(
&self,
_params: &Parameters,
body_data: &Value,
) -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> {
use self::GeckoExtensionRoute::*;
let command = match *self {
GetContext => GeckoExtensionCommand::GetContext,
SetContext => {
GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?)
}
InstallAddon => {
GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?)
}
UninstallAddon => {
GeckoExtensionCommand::UninstallAddon(serde_json::from_value(body_data.clone())?)
}
TakeFullScreenshot => GeckoExtensionCommand::TakeFullScreenshot,
};
Ok(WebDriverCommand::Extension(command))
}
}
#[derive(Clone)]
pub enum GeckoExtensionCommand {
GetContext,
SetContext(GeckoContextParameters),
InstallAddon(AddonInstallParameters),
UninstallAddon(AddonUninstallParameters),
TakeFullScreenshot,
}
impl WebDriverExtensionCommand for GeckoExtensionCommand {
fn parameters_json(&self) -> Option<Value> {
use self::GeckoExtensionCommand::*;
match self {
GetContext => None,
InstallAddon(x) => Some(serde_json::to_value(x).unwrap()),
SetContext(x) => Some(serde_json::to_value(x).unwrap()),
UninstallAddon(x) => Some(serde_json::to_value(x).unwrap()),
TakeFullScreenshot => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct AddonInstallParameters {
pub path: String,
pub temporary: Option<bool>,
}
impl<'de> Deserialize<'de> for AddonInstallParameters {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Base64 {
addon: String,
temporary: Option<bool>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Path {
path: String,
temporary: Option<bool>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Helper {
Base64(Base64),
Path(Path),
}
let params = match Helper::deserialize(deserializer)? {
Helper::Path(ref mut data) => AddonInstallParameters {
path: data.path.clone(),
temporary: data.temporary,
},
Helper::Base64(ref mut data) => {
let content = base64::decode(&data.addon).map_err(de::Error::custom)?;
let path = env::temp_dir()
.as_path()
.join(format!("addon-{}.xpi", Uuid::new_v4()));
let mut xpi_file = File::create(&path).map_err(de::Error::custom)?;
xpi_file
.write(content.as_slice())
.map_err(de::Error::custom)?;
let path = match path.to_str() {
Some(path) => path.to_string(),
None => return Err(de::Error::custom("could not write addon to file")),
};
AddonInstallParameters {
path,
temporary: data.temporary,
}
}
};
Ok(params)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AddonUninstallParameters {
pub id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GeckoContext {
Content,
Chrome,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GeckoContextParameters {
pub context: GeckoContext,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct XblLocatorParameters {
pub name: String,
pub value: String,
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct LogOptions {
pub level: Option<logging::Level>,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
use crate::test::assert_de;
#[test]
fn test_json_addon_install_parameters_invalid() {
assert!(serde_json::from_str::<AddonInstallParameters>("").is_err());
assert!(serde_json::from_value::<AddonInstallParameters>(json!(null)).is_err());
assert!(serde_json::from_value::<AddonInstallParameters>(json!({})).is_err());
}
#[test]
fn test_json_addon_install_parameters_with_path_and_temporary() {
let params = AddonInstallParameters {
path: "/path/to.xpi".to_string(),
temporary: Some(true),
};
assert_de(&params, json!({"path": "/path/to.xpi", "temporary": true}));
}
#[test]
fn test_json_addon_install_parameters_with_path() {
let params = AddonInstallParameters {
path: "/path/to.xpi".to_string(),
temporary: None,
};
assert_de(&params, json!({"path": "/path/to.xpi"}));
}
#[test]
fn test_json_addon_install_parameters_with_path_invalid_type() {
let json = json!({"path": true, "temporary": true});
assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
}
#[test]
fn test_json_addon_install_parameters_with_path_and_temporary_invalid_type() {
let json = json!({"path": "/path/to.xpi", "temporary": "foo"});
assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
}
#[test]
fn test_json_addon_install_parameters_with_addon() {
let json = json!({"addon": "aGVsbG8=", "temporary": true});
let data = serde_json::from_value::<AddonInstallParameters>(json).unwrap();
assert_eq!(data.temporary, Some(true));
let mut file = File::open(data.path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
assert_eq!(contents, "hello");
}
#[test]
fn test_json_addon_install_parameters_with_addon_only() {
let json = json!({"addon": "aGVsbG8="});
let data = serde_json::from_value::<AddonInstallParameters>(json).unwrap();
assert_eq!(data.temporary, None);
let mut file = File::open(data.path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
assert_eq!(contents, "hello");
}
#[test]
fn test_json_addon_install_parameters_with_addon_invalid_type() {
let json = json!({"addon": true, "temporary": true});
assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
}
#[test]
fn test_json_addon_install_parameters_with_addon_and_temporary_invalid_type() {
let json = json!({"addon": "aGVsbG8=", "temporary": "foo"});
assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
}
#[test]
fn test_json_install_parameters_with_temporary_only() {
let json = json!({"temporary": true});
assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
}
#[test]
fn test_json_addon_install_parameters_with_both_path_and_addon() {
let json = json!({
"path": "/path/to.xpi",
"addon": "aGVsbG8=",
"temporary": true,
});
assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
}
#[test]
fn test_json_addon_uninstall_parameters_invalid() {
assert!(serde_json::from_str::<AddonUninstallParameters>("").is_err());
assert!(serde_json::from_value::<AddonUninstallParameters>(json!(null)).is_err());
assert!(serde_json::from_value::<AddonUninstallParameters>(json!({})).is_err());
}
#[test]
fn test_json_addon_uninstall_parameters() {
let params = AddonUninstallParameters {
id: "foo".to_string(),
};
assert_de(&params, json!({"id": "foo"}));
}
#[test]
fn test_json_addon_uninstall_parameters_id_invalid_type() {
let json = json!({"id": true});
assert!(serde_json::from_value::<AddonUninstallParameters>(json).is_err());
}
#[test]
fn test_json_gecko_context_parameters_content() {
let params = GeckoContextParameters {
context: GeckoContext::Content,
};
assert_de(&params, json!({"context": "content"}));
}
#[test]
fn test_json_gecko_context_parameters_chrome() {
let params = GeckoContextParameters {
context: GeckoContext::Chrome,
};
assert_de(&params, json!({"context": "chrome"}));
}
#[test]
fn test_json_gecko_context_parameters_context_invalid() {
type P = GeckoContextParameters;
assert!(serde_json::from_value::<P>(json!({})).is_err());
assert!(serde_json::from_value::<P>(json!({ "context": null })).is_err());
assert!(serde_json::from_value::<P>(json!({"context": "foo"})).is_err());
}
}

@ -1,403 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Gecko-esque logger implementation for the [`log`] crate.
//!
//! The [`log`] crate provides a single logging API that abstracts over the
//! actual logging implementation. This module uses the logging API
//! to provide a log implementation that shares many aesthetical traits with
//! [Log.sys.mjs] from Gecko.
//!
//! Using the [`error!`], [`warn!`], [`info!`], [`debug!`], and
//! [`trace!`] macros from `log` will output a timestamp field, followed by the
//! log level, and then the message. The fields are separated by a tab
//! character, making the output suitable for further text processing with
//! `awk(1)`.
//!
//! This module shares the same API as `log`, except it provides additional
//! entry functions [`init`] and [`init_with_level`] and additional log levels
//! `Level::Fatal` and `Level::Config`. Converting these into the
//! [`log::Level`] is lossy so that `Level::Fatal` becomes `log::Level::Error`
//! and `Level::Config` becomes `log::Level::Debug`.
//!
//! [`log`]: https://docs.rs/log/newest/log/
//! [Log.sys.mjs]: https://searchfox.org/mozilla-central/source/toolkit/modules/Log.sys.mjs
//! [`error!`]: https://docs.rs/log/newest/log/macro.error.html
//! [`warn!`]: https://docs.rs/log/newest/log/macro.warn.html
//! [`info!`]: https://docs.rs/log/newest/log/macro.info.html
//! [`debug!`]: https://docs.rs/log/newest/log/macro.debug.html
//! [`trace!`]: https://docs.rs/log/newest/log/macro.trace.html
//! [`init`]: fn.init.html
//! [`init_with_level`]: fn.init_with_level.html
use std::fmt;
use std::io;
use std::io::Write;
use std::str;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use unicode_segmentation::UnicodeSegmentation;
use mozprofile::preferences::Pref;
static LOG_TRUNCATE: AtomicBool = AtomicBool::new(true);
static MAX_LOG_LEVEL: AtomicUsize = AtomicUsize::new(0);
const MAX_STRING_LENGTH: usize = 250;
const LOGGED_TARGETS: &[&str] = &[
"geckodriver",
"mozdevice",
"mozprofile",
"mozrunner",
"mozversion",
"webdriver",
];
/// Logger levels from [Log.sys.mjs].
///
/// [Log.sys.mjs]: https://searchfox.org/mozilla-central/source/toolkit/modules/Log.sys.mjs
#[repr(usize)]
#[derive(Clone, Copy, Eq, Debug, Hash, PartialEq)]
pub enum Level {
Fatal = 70,
Error = 60,
Warn = 50,
Info = 40,
Config = 30,
Debug = 20,
Trace = 10,
}
impl From<usize> for Level {
fn from(n: usize) -> Level {
use self::Level::*;
match n {
70 => Fatal,
60 => Error,
50 => Warn,
40 => Info,
30 => Config,
20 => Debug,
10 => Trace,
_ => Info,
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Level::*;
let s = match *self {
Fatal => "FATAL",
Error => "ERROR",
Warn => "WARN",
Info => "INFO",
Config => "CONFIG",
Debug => "DEBUG",
Trace => "TRACE",
};
write!(f, "{}", s)
}
}
impl str::FromStr for Level {
type Err = ();
fn from_str(s: &str) -> Result<Level, ()> {
use self::Level::*;
match s.to_lowercase().as_ref() {
"fatal" => Ok(Fatal),
"error" => Ok(Error),
"warn" => Ok(Warn),
"info" => Ok(Info),
"config" => Ok(Config),
"debug" => Ok(Debug),
"trace" => Ok(Trace),
_ => Err(()),
}
}
}
impl From<Level> for log::Level {
fn from(level: Level) -> log::Level {
use self::Level::*;
match level {
Fatal | Error => log::Level::Error,
Warn => log::Level::Warn,
Info => log::Level::Info,
Config | Debug => log::Level::Debug,
Trace => log::Level::Trace,
}
}
}
impl From<Level> for Pref {
fn from(level: Level) -> Pref {
use self::Level::*;
Pref::new(match level {
Fatal => "Fatal",
Error => "Error",
Warn => "Warn",
Info => "Info",
Config => "Config",
Debug => "Debug",
Trace => "Trace",
})
}
}
impl From<log::Level> for Level {
fn from(log_level: log::Level) -> Level {
use log::Level::*;
match log_level {
Error => Level::Error,
Warn => Level::Warn,
Info => Level::Info,
Debug => Level::Debug,
Trace => Level::Trace,
}
}
}
struct Logger;
impl log::Log for Logger {
fn enabled(&self, meta: &log::Metadata) -> bool {
LOGGED_TARGETS.iter().any(|&x| meta.target().starts_with(x))
&& meta.level() <= log::max_level()
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
if let Some((s1, s2)) = truncate_message(record.args()) {
println!(
"{}\t{}\t{}\t{} ... {}",
format_ts(chrono::Local::now()),
record.target(),
record.level(),
s1,
s2
);
} else {
println!(
"{}\t{}\t{}\t{}",
format_ts(chrono::Local::now()),
record.target(),
record.level(),
record.args()
)
}
}
}
fn flush(&self) {
io::stdout().flush().unwrap();
}
}
/// Initialises the logging subsystem with the default log level.
pub fn init(truncate: bool) -> Result<(), log::SetLoggerError> {
init_with_level(Level::Info, truncate)
}
/// Initialises the logging subsystem.
pub fn init_with_level(level: Level, truncate: bool) -> Result<(), log::SetLoggerError> {
let logger = Logger {};
set_max_level(level);
set_truncate(truncate);
log::set_boxed_logger(Box::new(logger))?;
Ok(())
}
/// Returns the current maximum log level.
pub fn max_level() -> Level {
MAX_LOG_LEVEL.load(Ordering::Relaxed).into()
}
/// Sets the global maximum log level.
pub fn set_max_level(level: Level) {
MAX_LOG_LEVEL.store(level as usize, Ordering::SeqCst);
let slevel: log::Level = level.into();
log::set_max_level(slevel.to_level_filter())
}
/// Sets the global maximum log level.
pub fn set_truncate(truncate: bool) {
LOG_TRUNCATE.store(truncate, Ordering::SeqCst);
}
/// Returns the truncation flag.
pub fn truncate() -> bool {
LOG_TRUNCATE.load(Ordering::Relaxed)
}
/// Produces a 13-digit Unix Epoch timestamp similar to Gecko.
fn format_ts(ts: chrono::DateTime<chrono::Local>) -> String {
format!("{}{:03}", ts.timestamp(), ts.timestamp_subsec_millis())
}
/// Truncate a log message if it's too long
fn truncate_message(args: &fmt::Arguments) -> Option<(String, String)> {
// Don't truncate the message if requested.
if !truncate() {
return None;
}
let message = format!("{}", args);
let chars = message.graphemes(true).collect::<Vec<&str>>();
if chars.len() > MAX_STRING_LENGTH {
let middle: usize = MAX_STRING_LENGTH / 2;
let s1 = chars[0..middle].concat();
let s2 = chars[chars.len() - middle..].concat();
Some((s1, s2))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use std::sync::Mutex;
use mozprofile::preferences::{Pref, PrefValue};
lazy_static! {
static ref LEVEL_MUTEX: Mutex<()> = Mutex::new(());
}
#[test]
fn test_level_repr() {
assert_eq!(Level::Fatal as usize, 70);
assert_eq!(Level::Error as usize, 60);
assert_eq!(Level::Warn as usize, 50);
assert_eq!(Level::Info as usize, 40);
assert_eq!(Level::Config as usize, 30);
assert_eq!(Level::Debug as usize, 20);
assert_eq!(Level::Trace as usize, 10);
}
#[test]
fn test_level_from_log() {
assert_eq!(Level::from(log::Level::Error), Level::Error);
assert_eq!(Level::from(log::Level::Warn), Level::Warn);
assert_eq!(Level::from(log::Level::Info), Level::Info);
assert_eq!(Level::from(log::Level::Debug), Level::Debug);
assert_eq!(Level::from(log::Level::Trace), Level::Trace);
}
#[test]
fn test_level_into_log() {
assert_eq!(Into::<log::Level>::into(Level::Fatal), log::Level::Error);
assert_eq!(Into::<log::Level>::into(Level::Error), log::Level::Error);
assert_eq!(Into::<log::Level>::into(Level::Warn), log::Level::Warn);
assert_eq!(Into::<log::Level>::into(Level::Info), log::Level::Info);
assert_eq!(Into::<log::Level>::into(Level::Config), log::Level::Debug);
assert_eq!(Into::<log::Level>::into(Level::Debug), log::Level::Debug);
assert_eq!(Into::<log::Level>::into(Level::Trace), log::Level::Trace);
}
#[test]
fn test_level_into_pref() {
let tests = [
(Level::Fatal, "Fatal"),
(Level::Error, "Error"),
(Level::Warn, "Warn"),
(Level::Info, "Info"),
(Level::Config, "Config"),
(Level::Debug, "Debug"),
(Level::Trace, "Trace"),
];
for &(lvl, s) in tests.iter() {
let expected = Pref {
value: PrefValue::String(s.to_string()),
sticky: false,
};
assert_eq!(Into::<Pref>::into(lvl), expected);
}
}
#[test]
fn test_level_from_str() {
assert_eq!(Level::from_str("fatal"), Ok(Level::Fatal));
assert_eq!(Level::from_str("error"), Ok(Level::Error));
assert_eq!(Level::from_str("warn"), Ok(Level::Warn));
assert_eq!(Level::from_str("info"), Ok(Level::Info));
assert_eq!(Level::from_str("config"), Ok(Level::Config));
assert_eq!(Level::from_str("debug"), Ok(Level::Debug));
assert_eq!(Level::from_str("trace"), Ok(Level::Trace));
assert_eq!(Level::from_str("INFO"), Ok(Level::Info));
assert!(Level::from_str("foo").is_err());
}
#[test]
fn test_level_to_str() {
assert_eq!(Level::Fatal.to_string(), "FATAL");
assert_eq!(Level::Error.to_string(), "ERROR");
assert_eq!(Level::Warn.to_string(), "WARN");
assert_eq!(Level::Info.to_string(), "INFO");
assert_eq!(Level::Config.to_string(), "CONFIG");
assert_eq!(Level::Debug.to_string(), "DEBUG");
assert_eq!(Level::Trace.to_string(), "TRACE");
}
#[test]
fn test_max_level() {
let _guard = LEVEL_MUTEX.lock();
set_max_level(Level::Info);
assert_eq!(max_level(), Level::Info);
}
#[test]
fn test_set_max_level() {
let _guard = LEVEL_MUTEX.lock();
set_max_level(Level::Error);
assert_eq!(max_level(), Level::Error);
set_max_level(Level::Fatal);
assert_eq!(max_level(), Level::Fatal);
}
#[test]
fn test_init_with_level() {
let _guard = LEVEL_MUTEX.lock();
init_with_level(Level::Debug, false).unwrap();
assert_eq!(max_level(), Level::Debug);
assert!(init_with_level(Level::Warn, false).is_err());
}
#[test]
fn test_format_ts() {
let ts = chrono::Local::now();
let s = format_ts(ts);
assert_eq!(s.len(), 13);
}
#[test]
fn test_truncate() {
let short_message = (0..MAX_STRING_LENGTH).map(|_| "x").collect::<String>();
// A message up to MAX_STRING_LENGTH is not truncated
assert_eq!(truncate_message(&format_args!("{}", short_message)), None);
let long_message = (0..MAX_STRING_LENGTH + 1).map(|_| "x").collect::<String>();
let part = (0..MAX_STRING_LENGTH / 2).map(|_| "x").collect::<String>();
// A message longer than MAX_STRING_LENGTH is not truncated if requested
set_truncate(false);
assert_eq!(truncate_message(&format_args!("{}", long_message)), None);
// A message longer than MAX_STRING_LENGTH is truncated if requested
set_truncate(true);
assert_eq!(
truncate_message(&format_args!("{}", long_message)),
Some((part.to_owned(), part))
);
}
}

@ -1,549 +0,0 @@
#![forbid(unsafe_code)]
extern crate chrono;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate lazy_static;
extern crate hyper;
extern crate marionette as marionette_rs;
extern crate mozdevice;
extern crate mozprofile;
extern crate mozrunner;
extern crate mozversion;
extern crate regex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate serde_yaml;
extern crate tempfile;
extern crate url;
extern crate uuid;
extern crate webdriver;
extern crate zip;
#[macro_use]
extern crate log;
use std::env;
use std::fmt;
use std::io;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use std::result;
use std::str::FromStr;
use clap::{AppSettings, Arg, Command};
macro_rules! try_opt {
($expr:expr, $err_type:expr, $err_msg:expr) => {{
match $expr {
Some(x) => x,
None => return Err(WebDriverError::new($err_type, $err_msg)),
}
}};
}
mod android;
mod browser;
mod build;
mod capabilities;
mod command;
mod logging;
mod marionette;
mod prefs;
#[cfg(test)]
pub mod test;
use crate::command::extension_routes;
use crate::logging::Level;
use crate::marionette::{MarionetteHandler, MarionetteSettings};
use mozdevice::AndroidStorageInput;
use url::{Host, Url};
const EXIT_SUCCESS: i32 = 0;
const EXIT_USAGE: i32 = 64;
const EXIT_UNAVAILABLE: i32 = 69;
enum FatalError {
Parsing(clap::Error),
Usage(String),
Server(io::Error),
}
impl FatalError {
fn exit_code(&self) -> i32 {
use FatalError::*;
match *self {
Parsing(_) | Usage(_) => EXIT_USAGE,
Server(_) => EXIT_UNAVAILABLE,
}
}
fn help_included(&self) -> bool {
matches!(*self, FatalError::Parsing(_))
}
}
impl From<clap::Error> for FatalError {
fn from(err: clap::Error) -> FatalError {
FatalError::Parsing(err)
}
}
impl From<io::Error> for FatalError {
fn from(err: io::Error) -> FatalError {
FatalError::Server(err)
}
}
// harmonise error message from clap to avoid duplicate "error:" prefix
impl fmt::Display for FatalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use FatalError::*;
let s = match *self {
Parsing(ref err) => err.to_string(),
Usage(ref s) => format!("error: {}", s),
Server(ref err) => format!("error: {}", err),
};
write!(f, "{}", s)
}
}
macro_rules! usage {
($msg:expr) => {
return Err(FatalError::Usage($msg.to_string()))
};
($fmt:expr, $($arg:tt)+) => {
return Err(FatalError::Usage(format!($fmt, $($arg)+)))
};
}
type ProgramResult<T> = result::Result<T, FatalError>;
#[allow(clippy::large_enum_variant)]
enum Operation {
Help,
Version,
Server {
log_level: Option<Level>,
log_truncate: bool,
address: SocketAddr,
allow_hosts: Vec<Host>,
allow_origins: Vec<Url>,
settings: MarionetteSettings,
deprecated_storage_arg: bool,
},
}
/// Get a socket address from the provided host and port
///
/// # Arguments
/// * `webdriver_host` - The hostname on which the server will listen
/// * `webdriver_port` - The port on which the server will listen
///
/// When the host and port resolve to multiple addresses, prefer
/// IPv4 addresses vs IPv6.
fn server_address(webdriver_host: &str, webdriver_port: u16) -> ProgramResult<SocketAddr> {
let mut socket_addrs = match format!("{}:{}", webdriver_host, webdriver_port).to_socket_addrs()
{
Ok(addrs) => addrs.collect::<Vec<_>>(),
Err(e) => usage!("{}: {}:{}", e, webdriver_host, webdriver_port),
};
if socket_addrs.is_empty() {
usage!(
"Unable to resolve host: {}:{}",
webdriver_host,
webdriver_port
)
}
// Prefer ipv4 address
socket_addrs.sort_by(|a, b| {
let a_val = i32::from(!a.ip().is_ipv4());
let b_val = i32::from(!b.ip().is_ipv4());
a_val.partial_cmp(&b_val).expect("Comparison failed")
});
Ok(socket_addrs.remove(0))
}
/// Parse a given string into a Host
fn parse_hostname(webdriver_host: &str) -> Result<Host, url::ParseError> {
let host_str = if let Ok(ip_addr) = IpAddr::from_str(webdriver_host) {
// In this case we have an IP address as the host
if ip_addr.is_ipv6() {
// Convert to quoted form
format!("[{}]", &webdriver_host)
} else {
webdriver_host.into()
}
} else {
webdriver_host.into()
};
Host::parse(&host_str)
}
/// Get a list of default hostnames to allow
///
/// This only covers domain names, not IP addresses, since IP adresses
/// are always accepted.
fn get_default_allowed_hosts(ip: IpAddr) -> Vec<Result<Host, url::ParseError>> {
let localhost_is_loopback = ("localhost".to_string(), 80)
.to_socket_addrs()
.map(|addr_iter| {
addr_iter
.map(|addr| addr.ip())
.filter(|ip| ip.is_loopback())
})
.iter()
.len()
> 0;
if ip.is_loopback() && localhost_is_loopback {
vec![Host::parse("localhost")]
} else {
vec![]
}
}
fn get_allowed_hosts(
host: Host,
allow_hosts: Option<clap::Values>,
) -> Result<Vec<Host>, url::ParseError> {
allow_hosts
.map(|hosts| hosts.map(Host::parse).collect::<Vec<_>>())
.unwrap_or_else(|| match host {
Host::Domain(_) => {
vec![Ok(host.clone())]
}
Host::Ipv4(ip) => get_default_allowed_hosts(IpAddr::V4(ip)),
Host::Ipv6(ip) => get_default_allowed_hosts(IpAddr::V6(ip)),
})
.into_iter()
.collect::<Result<Vec<Host>, url::ParseError>>()
}
fn get_allowed_origins(allow_origins: Option<clap::Values>) -> Result<Vec<Url>, url::ParseError> {
allow_origins
.map(|origins| {
origins
.map(Url::parse)
.collect::<Result<Vec<Url>, url::ParseError>>()
})
.unwrap_or_else(|| Ok(vec![]))
}
fn parse_args(cmd: &mut Command) -> ProgramResult<Operation> {
let args = cmd.try_get_matches_from_mut(env::args())?;
if args.is_present("help") {
return Ok(Operation::Help);
} else if args.is_present("version") {
return Ok(Operation::Version);
}
let log_level = if args.is_present("log_level") {
Level::from_str(args.value_of("log_level").unwrap()).ok()
} else {
Some(match args.occurrences_of("verbosity") {
0 => Level::Info,
1 => Level::Debug,
_ => Level::Trace,
})
};
let webdriver_host = args.value_of("webdriver_host").unwrap();
let webdriver_port = {
let s = args.value_of("webdriver_port").unwrap();
match u16::from_str(s) {
Ok(n) => n,
Err(e) => usage!("invalid --port: {}: {}", e, s),
}
};
let android_storage = args
.value_of_t::<AndroidStorageInput>("android_storage")
.unwrap_or(AndroidStorageInput::Auto);
let binary = args.value_of("binary").map(PathBuf::from);
let profile_root = args.value_of("profile_root").map(PathBuf::from);
// Try to create a temporary directory on startup to check that the directory exists and is writable
{
let tmp_dir = if let Some(ref tmp_root) = profile_root {
tempfile::tempdir_in(tmp_root)
} else {
tempfile::tempdir()
};
if tmp_dir.is_err() {
usage!("Unable to write to temporary directory; consider --profile-root with a writeable directory")
}
}
let marionette_host = args.value_of("marionette_host").unwrap();
let marionette_port = match args.value_of("marionette_port") {
Some(s) => match u16::from_str(s) {
Ok(n) => Some(n),
Err(e) => usage!("invalid --marionette-port: {}", e),
},
None => None,
};
// For Android the port on the device must be the same as the one on the
// host. For now default to 9222, which is the default for --remote-debugging-port.
let websocket_port = match args.value_of("websocket_port") {
Some(s) => match u16::from_str(s) {
Ok(n) => n,
Err(e) => usage!("invalid --websocket-port: {}", e),
},
None => 9222,
};
let host = match parse_hostname(webdriver_host) {
Ok(name) => name,
Err(e) => usage!("invalid --host {}: {}", webdriver_host, e),
};
let allow_hosts = match get_allowed_hosts(host, args.values_of("allow_hosts")) {
Ok(hosts) => hosts,
Err(e) => usage!("invalid --allow-hosts {}", e),
};
let allow_origins = match get_allowed_origins(args.values_of("allow_origins")) {
Ok(origins) => origins,
Err(e) => usage!("invalid --allow-origins {}", e),
};
let address = server_address(webdriver_host, webdriver_port)?;
let settings = MarionetteSettings {
binary,
profile_root,
connect_existing: args.is_present("connect_existing"),
host: marionette_host.into(),
port: marionette_port,
websocket_port,
allow_hosts: allow_hosts.clone(),
allow_origins: allow_origins.clone(),
jsdebugger: args.is_present("jsdebugger"),
android_storage,
};
Ok(Operation::Server {
log_level,
log_truncate: !args.is_present("log_no_truncate"),
allow_hosts,
allow_origins,
address,
settings,
deprecated_storage_arg: args.is_present("android_storage"),
})
}
fn inner_main(cmd: &mut Command) -> ProgramResult<()> {
match parse_args(cmd)? {
Operation::Help => print_help(cmd),
Operation::Version => print_version(),
Operation::Server {
log_level,
log_truncate,
address,
allow_hosts,
allow_origins,
settings,
deprecated_storage_arg,
} => {
if let Some(ref level) = log_level {
logging::init_with_level(*level, log_truncate).unwrap();
} else {
logging::init(log_truncate).unwrap();
}
if deprecated_storage_arg {
warn!("--android-storage argument is deprecated and will be removed soon.");
};
let handler = MarionetteHandler::new(settings);
let listening = webdriver::server::start(
address,
allow_hosts,
allow_origins,
handler,
extension_routes(),
)?;
info!("Listening on {}", listening.socket);
}
}
Ok(())
}
fn main() {
use std::process::exit;
let mut cmd = make_command();
// use std::process:Termination when it graduates
exit(match inner_main(&mut cmd) {
Ok(_) => EXIT_SUCCESS,
Err(e) => {
eprintln!("{}: {}", get_program_name(), e);
if !e.help_included() {
print_help(&mut cmd);
}
e.exit_code()
}
});
}
fn make_command<'a>() -> Command<'a> {
Command::new(format!("geckodriver {}", build::build_info()))
.setting(AppSettings::NoAutoHelp)
.setting(AppSettings::NoAutoVersion)
.about("WebDriver implementation for Firefox")
.arg(
Arg::new("webdriver_host")
.long("host")
.takes_value(true)
.value_name("HOST")
.default_value("127.0.0.1")
.help("Host IP to use for WebDriver server"),
)
.arg(
Arg::new("webdriver_port")
.short('p')
.long("port")
.takes_value(true)
.value_name("PORT")
.default_value("4444")
.help("Port to use for WebDriver server"),
)
.arg(
Arg::new("binary")
.short('b')
.long("binary")
.takes_value(true)
.value_name("BINARY")
.help("Path to the Firefox binary"),
)
.arg(
Arg::new("marionette_host")
.long("marionette-host")
.takes_value(true)
.value_name("HOST")
.default_value("127.0.0.1")
.help("Host to use to connect to Gecko"),
)
.arg(
Arg::new("marionette_port")
.long("marionette-port")
.takes_value(true)
.value_name("PORT")
.help("Port to use to connect to Gecko [default: system-allocated port]"),
)
.arg(
Arg::new("websocket_port")
.long("websocket-port")
.takes_value(true)
.value_name("PORT")
.conflicts_with("connect_existing")
.help("Port to use to connect to WebDriver BiDi [default: 9222]"),
)
.arg(
Arg::new("connect_existing")
.long("connect-existing")
.requires("marionette_port")
.help("Connect to an existing Firefox instance"),
)
.arg(
Arg::new("jsdebugger")
.long("jsdebugger")
.help("Attach browser toolbox debugger for Firefox"),
)
.arg(
Arg::new("verbosity")
.multiple_occurrences(true)
.conflicts_with("log_level")
.short('v')
.help("Log level verbosity (-v for debug and -vv for trace level)"),
)
.arg(
Arg::new("log_level")
.long("log")
.takes_value(true)
.value_name("LEVEL")
.possible_values(["fatal", "error", "warn", "info", "config", "debug", "trace"])
.help("Set Gecko log level"),
)
.arg(
Arg::new("log_no_truncate")
.long("log-no-truncate")
.help("Disable truncation of long log lines"),
)
.arg(
Arg::new("help")
.short('h')
.long("help")
.help("Prints this message"),
)
.arg(
Arg::new("version")
.short('V')
.long("version")
.help("Prints version and copying information"),
)
.arg(
Arg::new("profile_root")
.long("profile-root")
.takes_value(true)
.value_name("PROFILE_ROOT")
.help("Directory in which to create profiles. Defaults to the system temporary directory."),
)
.arg(
Arg::new("android_storage")
.long("android-storage")
.possible_values(["auto", "app", "internal", "sdcard"])
.value_name("ANDROID_STORAGE")
.help("Selects storage location to be used for test data (deprecated)."),
)
.arg(
Arg::new("allow_hosts")
.long("allow-hosts")
.takes_value(true)
.multiple_values(true)
.value_name("ALLOW_HOSTS")
.help("List of hostnames to allow. By default the value of --host is allowed, and in addition if that's a well known local address, other variations on well known local addresses are allowed. If --allow-hosts is provided only exactly those hosts are allowed."),
)
.arg(
Arg::new("allow_origins")
.long("allow-origins")
.takes_value(true)
.multiple_values(true)
.value_name("ALLOW_ORIGINS")
.help("List of request origins to allow. These must be formatted as scheme://host:port. By default any request with an origin header is rejected. If --allow-origins is provided then only exactly those origins are allowed."),
)
}
fn get_program_name() -> String {
env::args().next().unwrap()
}
fn print_help(cmd: &mut Command) {
cmd.print_help().ok();
println!();
}
fn print_version() {
println!("geckodriver {}", build::build_info());
println!();
println!("The source code of this program is available from");
println!("testing/geckodriver in https://hg.mozilla.org/mozilla-central.");
println!();
println!("This program is subject to the terms of the Mozilla Public License 2.0.");
println!("You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.");
}

File diff suppressed because it is too large Load Diff

@ -1,158 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use mozprofile::preferences::Pref;
// ALL CHANGES TO THIS FILE MUST HAVE REVIEW FROM A GECKODRIVER PEER!
//
// Please refer to INSTRUCTIONS TO ADD A NEW PREFERENCE in
// remote/shared/RecommendedPreferences.sys.mjs
//
// Note: geckodriver is used out-of-tree with various builds of Firefox.
// Removing a preference from this file will cause regressions,
// so please be careful and get review from a Testing :: geckodriver peer
// before you make any changes to this file.
lazy_static! {
pub static ref DEFAULT: Vec<(&'static str, Pref)> = vec![
// Make sure Shield doesn't hit the network.
("app.normandy.api_url", Pref::new("")),
// Disable Firefox old build background check
("app.update.checkInstallTime", Pref::new(false)),
// Disable automatically upgrading Firefox
//
// Note: Possible update tests could reset or flip the value to allow
// updates to be downloaded and applied.
("app.update.disabledForTesting", Pref::new(true)),
// Enable the dump function, which sends messages to the system
// console
("browser.dom.window.dump.enabled", Pref::new(true)),
("devtools.console.stdout.chrome", Pref::new(true)),
// Disable safebrowsing components
("browser.safebrowsing.blockedURIs.enabled", Pref::new(false)),
("browser.safebrowsing.downloads.enabled", Pref::new(false)),
("browser.safebrowsing.passwords.enabled", Pref::new(false)),
("browser.safebrowsing.malware.enabled", Pref::new(false)),
("browser.safebrowsing.phishing.enabled", Pref::new(false)),
// Do not restore the last open set of tabs if the browser crashed
("browser.sessionstore.resume_from_crash", Pref::new(false)),
// Skip check for default browser on startup
("browser.shell.checkDefaultBrowser", Pref::new(false)),
// Do not redirect user when a milestone upgrade of Firefox
// is detected
("browser.startup.homepage_override.mstone", Pref::new("ignore")),
// Start with a blank page (about:blank)
("browser.startup.page", Pref::new(0)),
// Disable the UI tour
("browser.uitour.enabled", Pref::new(false)),
// Do not warn on quitting Firefox
("browser.warnOnQuit", Pref::new(false)),
// Defensively disable data reporting systems
("datareporting.healthreport.documentServerURI", Pref::new("http://%(server)s/dummy/healthreport/")),
("datareporting.healthreport.logging.consoleEnabled", Pref::new(false)),
("datareporting.healthreport.service.enabled", Pref::new(false)),
("datareporting.healthreport.service.firstRun", Pref::new(false)),
("datareporting.healthreport.uploadEnabled", Pref::new(false)),
// Do not show datareporting policy notifications which can
// interfere with tests
("datareporting.policy.dataSubmissionEnabled", Pref::new(false)),
("datareporting.policy.dataSubmissionPolicyBypassNotification", Pref::new(true)),
// Disable the ProcessHangMonitor
("dom.ipc.reportProcessHangs", Pref::new(false)),
// Only load extensions from the application and user profile
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
("extensions.autoDisableScopes", Pref::new(0)),
("extensions.enabledScopes", Pref::new(5)),
// Disable intalling any distribution extensions or add-ons
("extensions.installDistroAddons", Pref::new(false)),
// Turn off extension updates so they do not bother tests
("extensions.update.enabled", Pref::new(false)),
("extensions.update.notifyUser", Pref::new(false)),
// Allow the application to have focus even it runs in the
// background
("focusmanager.testmode", Pref::new(true)),
// Disable useragent updates
("general.useragent.updates.enabled", Pref::new(false)),
// Always use network provider for geolocation tests so we bypass
// the macOS dialog raised by the corelocation provider
("geo.provider.testing", Pref::new(true)),
// Do not scan wi-fi
("geo.wifi.scan", Pref::new(false)),
// No hang monitor
("hangmonitor.timeout", Pref::new(0)),
// Disable idle-daily notifications to avoid expensive operations
// that may cause unexpected test timeouts.
("idle.lastDailyNotification", Pref::new(-1)),
// Disable download and usage of OpenH264, and Widevine plugins
("media.gmp-manager.updateEnabled", Pref::new(false)),
// Disable the GFX sanity window
("media.sanity-test.disabled", Pref::new(true)),
// Do not automatically switch between offline and online
("network.manage-offline-status", Pref::new(false)),
// Make sure SNTP requests do not hit the network
("network.sntp.pools", Pref::new("%(server)s")),
// Disable Flash. The plugin container it is run in is
// causing problems when quitting Firefox from geckodriver,
// c.f. https://github.com/mozilla/geckodriver/issues/225.
("plugin.state.flash", Pref::new(0)),
// Don't do network connections for mitm priming
("security.certerrors.mitm.priming.enabled", Pref::new(false)),
// Ensure blocklist updates don't hit the network
("services.settings.server", Pref::new("")),
// Disable first run pages
("startup.homepage_welcome_url", Pref::new("about:blank")),
("startup.homepage_welcome_url.additional", Pref::new("")),
// asrouter expects a plain object or null
("browser.newtabpage.activity-stream.asrouter.providers.cfr", Pref::new("null")),
// TODO: Remove once minimum supported Firefox release is 93.
("browser.newtabpage.activity-stream.asrouter.providers.cfr-fxa", Pref::new("null")),
("browser.newtabpage.activity-stream.asrouter.providers.snippets", Pref::new("null")),
("browser.newtabpage.activity-stream.asrouter.providers.message-groups", Pref::new("null")),
("browser.newtabpage.activity-stream.asrouter.providers.whats-new-panel", Pref::new("null")),
("browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments", Pref::new("null")),
("browser.newtabpage.activity-stream.feeds.system.topstories", Pref::new(false)),
("browser.newtabpage.activity-stream.feeds.snippets", Pref::new(false)),
("browser.newtabpage.activity-stream.tippyTop.service.endpoint", Pref::new("")),
("browser.newtabpage.activity-stream.discoverystream.config", Pref::new("[]")),
// For Activity Stream firstrun page, use an empty string to avoid fetching.
("browser.newtabpage.activity-stream.fxaccounts.endpoint", Pref::new("")),
// Prevent starting into safe mode after application crashes
("toolkit.startup.max_resumed_crashes", Pref::new(-1)),
// Disable webapp updates.
("browser.webapps.checkForUpdates", Pref::new(0)),
];
}

@ -1,12 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub fn assert_de<T>(data: &T, json: serde_json::Value)
where
T: std::fmt::Debug,
T: std::cmp::PartialEq,
T: serde::de::DeserializeOwned,
{
assert_eq!(data, &serde_json::from_value::<T>(json).unwrap());
}

@ -1,11 +0,0 @@
#!/bin/bash
INSTALL_DIR="/usr/local/bin"
json=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest)
url=$(echo "$json" | jq -r '.assets[].browser_download_url | select(contains("linux64") and endswith("gz"))')
curl -s -L "$url" | tar -xz
chmod +x geckodriver
sudo mv geckodriver "$INSTALL_DIR"
export PATH=$PATH:$INSTALL_DIR/geckodriver
echo "installed geckodriver binary in $INSTALL_DIR"

@ -2,7 +2,6 @@ apscheduler==3.10.1
async-timeout==4.0.2
beautifulsoup4==4.12.2
Brotli==1.0.9
bs4==0.0.1
certifi==2023.5.7
charset-normalizer==3.1.0
click==8.1.3
@ -19,8 +18,6 @@ pysondb-v2==2.0.0
redis==4.5.5
requests==2.31.0
requests-file==1.5.1
selenium==3.14.1
selenium-requests==1.3
schedule==1.2.0
six==1.16.0
soupsieve==2.4.1

@ -1 +0,0 @@
If you use the icons publicly, please link to https://icons8.com/line-awesome somewhere on your page or artwork, so that more creators could know about it and use it for free.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,159 +0,0 @@
body {
background-color: #f5f5f5;
background-image: url('/static/img/bg.png');
background-size: cover;
}
.align-center {
vertical-align: middle;
text-align: center;
}
/* Layout */
.centered {
position: fixed;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
}
#disk {
width: 66vw;
border-radius: 1200px;
aspect-ratio: 1 / 1 !important;
background-image: url('/static/img/vinyl.png');
background-position: center;
background-size: cover;
}
.card {
box-shadow: 0 0 36px 0px rgba(128, 128, 128, 0.128);
background-color: white;
padding: 32px;
border-radius: 20px;
}
.modal-body {
background-color: #f5f5f5;
}
.navbar-avatar {
max-height: 40px;
border-radius: 30px;
height: 40px;
}
.main-menu-link {
/*font-size: 24px;*/
/*font-family: RobotoSlab;*/
}
a {
text-decoration: none;
transition: 0.3s;
}
#header {
position: fixed;
top: 0;
width: 100%;
}
#action_list {
float: right;
padding: 20px;
color: white;
position: fixed;
right: 0;
}
#action_list a {
color: white;
}
#logo {
display: flex;
align-items: baseline;
}
#search_bar {
background: url('../svg/search-solid.svg') no-repeat scroll 12px 7px;
padding-left: 48px;
background-size: 20px;
border-radius: 66px;
}
#navigation {
/*padding-top: 32px;*/
}
.navbar {
padding-left: 12px;
padding-right: 12px;
}
#navigation_logo {
max-width: 216px;
}
#content {
min-height: 100vh;
}
#footer {
text-align: center;
position: fixed;
bottom: 0;
width: 100%;
}
.logo {
width: 100%;
}
.dl_queue_img {
position: relative;
}
.icn-spinner {
animation: spin-animation 0.9s infinite;
display: inline-block;
}
.icn-downloading {
top: 50%;
left: 50%;
position: absolute;
transform: translate(-50%, -50%);
}
.vinyl-card {
background-image: url('/static/img/vinyl-card.png');
background-position: right;
border-radius: 20px;
}
@keyframes spin-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
@keyframes spin{
from{transform:rotate(0deg)}
to{transform:rotate(360deg)}
}
@-webkit-keyframes spin {
from {-webkit-transform:rotate(0deg);}
to { -webkit-transform:rotate(360deg);}
}
@-moz-keyframes spin {
from {-moz-transform:rotate(0deg);}
to { -moz-transform:rotate(360deg);}
}

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 16 4 C 9.382813 4 4 9.382813 4 16 C 4 22.617188 9.382813 28 16 28 C 22.617188 28 28 22.617188 28 16 C 28 9.382813 22.617188 4 16 4 Z M 16 6 C 21.535156 6 26 10.464844 26 16 C 26 21.535156 21.535156 26 16 26 C 10.464844 26 6 21.535156 6 16 C 6 10.464844 10.464844 6 16 6 Z M 13.21875 8.5 C 11.042969 9.308594 9.308594 11.042969 8.5 13.21875 L 10.375 13.90625 C 10.980469 12.277344 12.277344 10.980469 13.90625 10.375 Z M 16 13 C 14.355469 13 13 14.355469 13 16 C 13 17.644531 14.355469 19 16 19 C 17.644531 19 19 17.644531 19 16 C 19 14.355469 17.644531 13 16 13 Z M 16 15 C 16.5625 15 17 15.4375 17 16 C 17 16.5625 16.5625 17 16 17 C 15.4375 17 15 16.5625 15 16 C 15 15.4375 15.4375 15 16 15 Z M 21.625 18.09375 C 21.019531 19.722656 19.722656 21.019531 18.09375 21.625 L 18.78125 23.5 C 20.957031 22.691406 22.691406 20.957031 23.5 18.78125 Z"/></svg>

Before

Width:  |  Height:  |  Size: 923 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 906 KiB

@ -1,467 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Created by Icons8</metadata>
<defs>
<font id="la-regular-400" horiz-adv-x="1024">
<font-face font-family="la-regular-400"
units-per-em="1024" ascent="896"
descent="128"
font-weight="400" />
<missing-glyph horiz-adv-x="0" />
<glyph glyph-name="building"
unicode="&#xF1AD;"
horiz-adv-x="1024" d="M128 800L128 -32L480 -32L480 96L544 96L544 -32L896 -32L896 800zM192 736L832 736L832 32L608 32L608 160L416 160L416 32L192 32zM256 672L256 608L384 608L384 672zM448 672L448 608L576 608L576 672zM640 672L640 608L768 608L768 672zM256 544L256 480L384 480L384 544zM448 544L448 480L576 480L576 544zM640 544L640 480L768 480L768 544zM256 416L256 352L384 352L384 416zM448 416L448 352L576 352L576 416zM640 416L640 352L768 352L768 416zM256 288L256 224L384 224L384 288zM448 288L448 224L576 224L576 288zM640 288L640 224L768 224L768 288zM256 160L256 96L384 96L384 160zM640 160L640 96L768 96L768 160z" />
<glyph glyph-name="map"
unicode="&#xF279;"
horiz-adv-x="1024" d="M896 753L851 733L639 642L395 734L383 738L371 733L147 637L128 629L128 15L173 35L385 126L629 34L641 30L653 35L877 131L896 139zM416 658L608 586L608 110L416 182zM352 656L352 181L192 112L192 587zM832 656L832 181L672 112L672 587z" />
<glyph glyph-name="neutral-face"
unicode="&#xF11A;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 512C341.5 512 320 490.5 320 464C320 437.5 341.5 416 368 416C394.5 416 416 437.5 416 464C416 490.5 394.5 512 368 512zM656 512C629.5 512 608 490.5 608 464C608 437.5 629.5 416 656 416C682.5 416 704 437.5 704 464C704 490.5 682.5 512 656 512zM352 256L352 192L672 192L672 256z" />
<glyph glyph-name="eye-slash"
unicode="&#xF070;"
horiz-adv-x="1024" d="M119 823L73 777L272 579L627 224L688 162L905 -55L951 -9L752 190C887.5 257.124992 978.375008 356.7499840000001 984 363L1003 384L984 405C975.375008 414.624992 770.750016 640 512 640C449.375008 640 390.250016 626.249984 336 606zM512 576C580.875008 576 646 556.624992 704 530C724.624992 495.5 736 456.5 736 416C736 357.875008 713.5 304.7499840000001 677 265L586 356C599.375008 372.375008 608 393.124992 608 416C608 469 565 512 512 512C489.124992 512 468.375008 503.375008 452 490L387 555C426.750016 567.249984 468.375008 576 512 576zM214 547C111.375008 483.875008 44.750016 410.375008 40 405L21 384L40 363C48.250016 353.7499840000001 237.750016 146.375008 482 130C491.875008 129 501.875008 128 512 128C522.124992 128 532.124992 129 542 130C568.375008 131.7499840000001 594.124992 135.2499840000001 619 141L562 198C545.750016 194.2499840000001 529.250016 192 512 192C388.5 192 288 292.5 288 416C288 433 290.250016 449.624992 294 466zM232 482C226.875008 460.375008 224 438.375008 224 416C224 360.375008 239.624992 309.124992 267 265C193.750016 307 137.750016 356.2499840000001 109 384C132.875008 407.124992 175.875008 445.5 232 482zM792 482C848.124992 445.5 891 407.124992 915 384C886.250016 356.2499840000001 829.375008 306 756 264C783.5 308.124992 800 360.375008 800 416C800 438.375008 797.124992 460.5 792 482z" />
<glyph glyph-name="caret-square-up"
unicode="&#xF151;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM512 541L489 519L297 327L343 281L512 450L681 281L727 327L535 519z" />
<glyph glyph-name="copy"
unicode="&#xF0C5;"
horiz-adv-x="1024" d="M128 768L128 128L352 128L352 192L192 192L192 704L576 704L576 672L640 672L640 768zM384 640L384 0L896 0L896 640zM448 576L832 576L832 64L448 64z" />
<glyph glyph-name="dot-circle"
unicode="&#xF192;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM512 480C459 480 416 437 416 384C416 331 459 288 512 288C565 288 608 331 608 384C608 437 565 480 512 480z" />
<glyph glyph-name="grinning-face-with-big-eyes"
unicode="&#xF599;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="save"
unicode="&#xF0C7;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 589L855 599L727 727L717 736zM224 672L320 672L320 480L704 480L704 658L800 562L800 96L736 96L736 384L288 384L288 96L224 96zM384 672L512 672L512 608L576 608L576 672L640 672L640 544L384 544zM352 320L672 320L672 96L352 96z" />
<glyph glyph-name="face-with-tears-of-joy"
unicode="&#xF588;"
horiz-adv-x="1024" d="M512 800C283.84 800 98.1395008 615.3064992 96.1875008 387.562496C114.5555008 394.922496 136.2884992 402.327488 161.3124992 409.687488C174.5284992 591.7995008 326.56 736 512 736C697.44 736 849.471488 591.7995008 862.687488 409.687488C887.711488 402.327488 909.444512 394.922496 927.812512 387.562496C925.860512 615.3064992 740.16 800 512 800zM288 448L288 384L448 384L448 448L288 448zM576 448L576 384L736 384L736 448L576 448zM192 352C192 352 96.9729984 325.285504 78.1249984 306.437504C59.2770016 287.5895040000001 59.2770016 256.972992 78.1249984 238.124992C96.9729984 219.276992 127.5895008 219.276992 146.4375008 238.124992C165.2855008 256.972992 192 352 192 352zM832 352C832 352 858.714496 256.972992 877.562496 238.124992C896.410496 219.276992 927.027008 219.276992 945.875008 238.124992C964.723008 256.972992 964.723008 287.5895040000001 945.875008 306.437504C927.027008 325.285504 832 352 832 352zM288 288C288 288 339.52 128 512 128C684.48 128 736 288 736 288L288 288zM205.6875008 211.187488C201.0155008 203.731488 196.2955008 197.4830080000001 191.6875008 192.875008C181.4155008 182.6030080000001 169.46 174.692992 156.5 169.124992C229.492 48.804992 361.312 -32 512 -32C662.688 -32 794.508 48.804992 867.5 169.124992C854.54 174.692992 842.584512 182.571008 832.312512 192.875008C827.672512 197.515008 822.984512 203.7634880000001 818.312512 211.187488C757.800512 104.371488 643.296 32 512 32C380.704 32 266.1995008 104.371488 205.6875008 211.187488z" />
<glyph glyph-name="star-half"
unicode="&#xF089;"
horiz-adv-x="1024" d="M512 827.749984L378.750016 528.875008L53.250016 494.5L296.375008 275.375008L228.5 -44.750016L512 118.7499840000001z" />
<glyph glyph-name="smiling-face"
unicode="&#xF118;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 512C341.5 512 320 490.5 320 464C320 437.5 341.5 416 368 416C394.5 416 416 437.5 416 464C416 490.5 394.5 512 368 512zM656 512C629.5 512 608 490.5 608 464C608 437.5 629.5 416 656 416C682.5 416 704 437.5 704 464C704 490.5 682.5 512 656 512zM346 288L291 256C335.250016 179.624992 417.5 128 512 128C606.5 128 688.750016 179.624992 733 256L678 288C644.750016 230.624992 583.250016 192 512 192C440.750016 192 379.250016 230.624992 346 288z" />
<glyph glyph-name="grinning-face-with-smiling-eyes"
unicode="&#xF582;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM352 512C284.992 512 236.5 473.375008 236.5 473.375008L275.5 422.624992C275.5 422.624992 310.1744992 448 352.062496 448C393.950496 448 428.562496 422.624992 428.562496 422.624992L467.562496 473.375008C467.498496 473.375008 419.008 512 352 512zM672 512C604.992 512 556.5 473.375008 556.5 473.375008L595.5 422.624992C595.5 422.624992 630.174496 448 672.062496 448C713.950496 448 748.562496 422.624992 748.562496 422.624992L787.562496 473.375008C787.498496 473.375008 739.008 512 672 512zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="audio-file"
unicode="&#xF1C7;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 800zM256 736L768 736L768 32L256 32zM512 585L512 378C501.875008 381.624992 491.250016 384 480 384C427.375008 384 384 340.624992 384 288C384 235.375008 427.375008 192 480 192C532.624992 192 576 235.375008 576 288L576 503L664 481L680 543zM480 320C498 320 512 306 512 288C512 270 498 256 480 256C462 256 448 270 448 288C448 306 462 320 480 320z" />
<glyph glyph-name="window-maximize"
unicode="&#xF2D0;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM288 576L288 192L736 192L736 576zM352 512L672 512L672 448L352 448zM352 384L672 384L672 256L352 256z" />
<glyph glyph-name="loudly-crying-face"
unicode="&#xF5B4;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM384 576C344.375008 576 314.250016 553.875008 294 535C273.750016 516.124992 261 498 261 498L315 462C315 462 322.875008 475.875008 337 489C351.124992 502.124992 369.5 512 384 512L480 512L480 576zM544 576L544 512L640 512C654.5 512 672.875008 502.124992 687 489C701.124992 475.875008 709 462 709 462L763 498C763 498 750.250016 516.124992 730 535C709.750016 553.875008 679.624992 576 640 576zM272 448C272 448 224 362.5 224 336C224 309.5 245.5 288 272 288C298.5 288 320 309.5 320 336C320 362.5 272 448 272 448zM512 384C437.250016 384 386.124992 344.5 358 307C329.875008 269.5 321 232 321 232L311 192L713 192L703 232C703 232 694.124992 269.5 666 307C637.875008 344.5 586.750016 384 512 384zM512 320C565.5 320 594.124992 295.5 614 269C618.875008 262.375008 617.624992 262.124992 621 256L403 256C406.375008 262.124992 405.124992 262.375008 410 269C429.875008 295.5 458.5 320 512 320z" />
<glyph glyph-name="sticky-note"
unicode="&#xF249;"
horiz-adv-x="1024" d="M160 736L160 32L653 32L663 41L855 233L864 243L864 736zM224 672L800 672L800 288L608 288L608 96L224 96zM672 224L754 224L672 142z" />
<glyph glyph-name="angry-face"
unicode="&#xF556;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM342 512C328.5 498.375008 320 479.749984 320 459C320 417.624992 353.624992 384 395 384C415.624992 384 434.5 392.375008 448 406zM682 512L576 406C589.5 392.375008 608.375008 384 629 384C670.375008 384 704 417.624992 704 459C704 479.624992 695.624992 498.5 682 512zM594 325C589.375008 324.875008 584.5 324.624992 580 324C544.124992 318.624992 517 299 493 283C469 267 447.875008 254.5 432 252C416.124992 249.5 401.875008 252 375 279L329 233C365.250016 196.5 406.5 183.375008 442 189C477.5 194.624992 504.124992 214.124992 528 230C551.875008 245.875008 572.375008 257.5 589 260C605.624992 262.5 622 260.375008 650 233L694 279C661.250016 311 626.375008 325.5 594 325z" />
<glyph glyph-name="window-restore"
unicode="&#xF2D2;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM384 608L384 480L288 480L288 160L640 160L640 288L736 288L736 608zM448 544L672 544L672 512L448 512zM448 448L672 448L672 352L448 352zM352 416L384 416L384 383L352 383zM352 319L384 319L384 288L576 288L576 224L352 224z" />
<glyph glyph-name="star"
unicode="&#xF005;"
horiz-adv-x="1024" d="M512 828L483 762L379 529L125 502L53 494L107 446L296 275L243 25L228 -45L291 -9L512 119L733 -9L796 -45L781 25L728 275L917 446L971 494L899 502L645 529L541 762zM512 671L594 486L602 469L620 467L821 446L671 311L657 298L661 280L703 83L528 183L512 193L496 183L321 83L363 280L367 298L353 311L203 446L404 467L422 469L430 486z" />
<glyph glyph-name="address-card"
unicode="&#xF2BB;"
horiz-adv-x="1024" d="M96 704L96 64L928 64L928 704zM160 640L864 640L864 128L755 128C752.5 132.375008 753.624992 138.375008 750 142C737.875008 154.124992 720.750016 160 704 160C687.250016 160 670.124992 154.124992 658 142C654.375008 138.375008 655.5 132.375008 653 128L371 128C368.5 132.375008 369.624992 138.375008 366 142C353.875008 154.124992 336.750016 160 320 160C303.250016 160 286.124992 154.124992 274 142C270.375008 138.375008 271.5 132.375008 269 128L160 128zM384 576C313.624992 576 256 518.375008 256 448C256 412.375008 271.250016 380.2499840000001 295 357C252.375008 328.124992 224 278.875008 224 224L288 224C288 277 331 320 384 320C437 320 480 277 480 224L544 224C544 278.875008 515.624992 328.124992 473 357C496.750016 380.2499840000001 512 412.375008 512 448C512 518.375008 454.375008 576 384 576zM384 512C419.750016 512 448 483.749984 448 448C448 412.249984 419.750016 384 384 384C348.250016 384 320 412.249984 320 448C320 483.749984 348.250016 512 384 512zM608 480L608 416L800 416L800 480zM608 352L608 288L800 288L800 352z" />
<glyph glyph-name="comment"
unicode="&#xF075;"
horiz-adv-x="1024" d="M96 704L96 64L402.750016 64L512 -45.250016L621.250016 64L928 64L928 704zM160 640L864 640L864 128L594.750016 128L512 45.249984L429.250016 128L160 128zM288 544L288 480L736 480L736 544zM288 416L288 352L736 352L736 416zM288 288L288 224L608 224L608 288z" />
<glyph glyph-name="object-ungroup"
unicode="&#xF248;"
horiz-adv-x="1024" d="M160 736L160 608L192 608L192 288L160 288L160 160L288 160L288 192L352 192L352 128L320 128L320 0L448 0L448 32L768 32L768 0L896 0L896 128L864 128L864 448L896 448L896 576L768 576L768 544L704 544L704 608L736 608L736 736L608 736L608 704L288 704L288 736zM288 640L608 640L608 608L640 608L640 288L608 288L608 256L288 256L288 288L256 288L256 608L288 608zM704 480L768 480L768 448L800 448L800 128L768 128L768 96L448 96L448 128L416 128L416 192L608 192L608 160L736 160L736 288L704 288z" />
<glyph glyph-name="winking-face"
unicode="&#xF4DA;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 512C341.5 512 320 490.5 320 464C320 437.5 341.5 416 368 416C394.5 416 416 437.5 416 464C416 490.5 394.5 512 368 512zM576 480L576 416L736 416L736 480zM671 351C671 297.124992 649.375008 259.7499840000001 618 233C586.624992 206.2499840000001 545.124992 192 512 192C444.124992 192 398 219.5 362 255L317 209C361.624992 165.124992 426.250016 128 512 128C561.750016 128 616.375008 146.7499840000001 660 184C703.624992 221.2499840000001 735 278.7499840000001 735 351z" />
<glyph glyph-name="window-close"
unicode="&#xF410;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM374 566L329 521L466 384L327 245L372 200L511 339L649 201L694 246L556 384L692 520L647 565L511 429z" />
<glyph glyph-name="video-file"
unicode="&#xF1C8;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 589L823 599L631 791L621 800zM256 736L576 736L576 544L768 544L768 32L256 32zM640 690L722 608L640 608zM416 473L416 167L464 197L624 293L670 320L624 347L464 443zM480 360L547 320L480 280z" />
<glyph glyph-name="hushed-face"
unicode="&#xF5C2;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 544C341.5 544 320 508.124992 320 464C320 419.875008 341.5 384 368 384C394.5 384 416 419.875008 416 464C416 508.124992 394.5 544 368 544zM656 544C629.5 544 608 508.124992 608 464C608 419.875008 629.5 384 656 384C682.5 384 704 419.875008 704 464C704 508.124992 682.5 544 656 544zM512 352C438.624992 352 384 292.624992 384 224C384 192.375008 395.375008 160.875008 421 145C446.624992 129.124992 475 128 512 128C549 128 577.375008 129.124992 603 145C628.624992 160.875008 640 192.375008 640 224C640 292.624992 585.375008 352 512 352zM512 288C553.375008 288 576 261.5 576 224C576 202.624992 574.750016 201.875008 570 199C565.250016 196.124992 545.750016 192 512 192C478.250016 192 458.750016 196.124992 454 199C449.250016 201.875008 448 202.624992 448 224C448 261.5 470.624992 288 512 288z" />
<glyph glyph-name="envelope"
unicode="&#xF0E0;"
horiz-adv-x="1024" d="M96 640L96 64L928 64L928 640zM234 576L790 576L512 391zM160 548L494 325L512 314L530 325L864 548L864 128L160 128z" />
<glyph glyph-name="face-with-tongue"
unicode="&#xF589;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512zM376.5 340.562496L327.437504 299.437504C355.494176 266.009728 398.443392 242.3571520000001 448 231.2499840000001L448 192C448 156.8 476.8 128 512 128C547.2 128 576 156.8 576 192L576 231.2499840000001C625.554688 242.359072 668.465984 265.98704 696.5 299.437504L647.437504 340.562496C620.301504 308.1144960000001 568.416 288 512 288C455.584 288 403.668 308.146496 376.5 340.562496z" />
<glyph glyph-name="caret-square-down"
unicode="&#xF150;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM343 487L297 441L489 249L512 227L535 249L727 441L681 487L512 318z" />
<glyph glyph-name="pause-circle"
unicode="&#xF28B;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM384 544L384 224L448 224L448 544zM576 544L576 224L640 224L640 544z" />
<glyph glyph-name="folder-open"
unicode="&#xF07C;"
horiz-adv-x="1024" d="M160 800L160 6L185 1L569 -79L608 -87L608 0L800 0L800 402L855 457L864 467L864 800zM452 736L800 736L800 494L745 439L736 429L736 64L608 64L608 349L599 359L544 414L544 713zM224 727L480 663L480 387L489 377L544 322L544 -9L224 58z" />
<glyph glyph-name="folder"
unicode="&#xF07B;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 402L887 457L896 467L896 800zM256 736L704 736L704 467L713 457L768 402L768 32L256 32zM768 736L832 736L832 494L800 462L768 494z" />
<glyph glyph-name="flag"
unicode="&#xF024;"
horiz-adv-x="1024" d="M160 736L160 -32L224 -32L224 288L480 288L480 192L864 192L864 640L544 640L544 736zM224 672L480 672L480 352L224 352zM544 576L800 576L800 256L544 256z" />
<glyph glyph-name="hand-pointing-right"
unicode="&#xF0A4;"
horiz-adv-x="1024" d="M470 800L460 791L243 576L64 576L64 64L603 64C648.124992 64 687.5 95.875008 697 140L750 384L864 384C916.624992 384 960 427.375008 960 480C960 532.624992 916.624992 576 864 576L520 576L526 600C532.5 605 536.624992 607.124992 546 620C561 640.5 576 672.249984 576 715C576 760.624992 534.750016 800 483 800zM493 733C506.5 730.375008 512 724.875008 512 715C512 686.124992 503.250016 668.249984 495 657C486.750016 645.749984 481 643 481 643L470 637L466 624L447 552L437 512L864 512C882.124992 512 896 498.124992 896 480C896 461.875008 882.124992 448 864 448L698 448L692 423L635 153C631.750016 138 618.250016 128 603 128L288 128L288 531zM128 512L224 512L224 128L128 128z" />
<glyph glyph-name="minus-square"
unicode="&#xF146;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM352 416L352 352L672 352L672 416z" />
<glyph glyph-name="alternate-arrow-circle-down"
unicode="&#xF358;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM480 608L480 288L384 288L512 160L640 288L544 288L544 608z" />
<glyph glyph-name="excel-file"
unicode="&#xF1C3;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 588.8124992L822.375008 598.3750016L630.375008 790.3750016L620.812512 800L192 800zM256 736L576 736L576 544L768 544L768 32L256 32L256 736zM640 691.1875008L723.187488 608L640 608L640 691.1875008zM352 480L473.624992 304L352 128L428.8125120000001 128L512 249.624992L595.187488 128L672 128L550.375008 304L672 480L595.187488 480L512 358.375008L428.8125120000001 480L352 480z" />
<glyph glyph-name="caret-square-left"
unicode="&#xF191;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM569 599L377 407L355 384L377 361L569 169L615 215L446 384L615 553z" />
<glyph glyph-name="pointer--hand-"
unicode="&#xF25A;"
horiz-adv-x="1024" d="M416 832C363.375008 832 320 788.624992 320 736L320 358L299 380L291 387C254.124992 423.875008 193.875008 423.875008 157 387C120.124992 350.124992 120.124992 289.875008 157 253L157 252L419 -7L421 -8L422 -10C465.124992 -42.375008 520.124992 -64 582 -64L637 -64C782.124992 -64 899 52.875008 899 198L899 448C899 500.624992 855.624992 544 803 544C789.375008 544 776.750016 540.249984 765 535C754.5 576.624992 716.624992 608 672 608C647.5 608 625 598.375008 608 583C591 598.375008 568.5 608 544 608C532.750016 608 522.124992 605.624992 512 602L512 736C512 788.624992 468.624992 832 416 832zM416 768C433.750016 768 448 753.749984 448 736L448 384L512 384L512 512C512 529.749984 526.250016 544 544 544C561.750016 544 576 529.749984 576 512L576 384L640 384L640 512C640 529.749984 654.250016 544 672 544C689.750016 544 704 529.749984 704 512L704 384L771 384L771 448C771 465.749984 785.250016 480 803 480C820.750016 480 835 465.749984 835 448L835 198C835 87.124992 747.875008 0 637 0L582 0C535.124992 0 494.624992 16.749984 461 42L202 298C187.750016 312.2499840000001 187.750016 327.7499840000001 202 342C216.250016 356.2499840000001 231.750016 356.2499840000001 246 342L384 204L384 736C384 753.749984 398.250016 768 416 768z" />
<glyph glyph-name="laugh-face-with-beaming-eyes"
unicode="&#xF59A;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM352 512C284.992 512 236.5 473.375008 236.5 473.375008L275.5 422.624992C275.5 422.624992 310.1744992 448 352.062496 448C393.950496 448 428.562496 422.624992 428.562496 422.624992L467.562496 473.375008C467.498496 473.375008 419.008 512 352 512zM672 512C604.992 512 556.5 473.375008 556.5 473.375008L595.5 422.624992C595.5 422.624992 630.174496 448 672.062496 448C713.950496 448 748.562496 422.624992 748.562496 422.624992L787.562496 473.375008C787.498496 473.375008 739.008 512 672 512zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="snowflake"
unicode="&#xF2DC;"
horiz-adv-x="1024" d="M480 800L480 686L407 759L361 713L480 594L480 440L348 519L306 682L244 666L270 566L172 625L139 571L239 511L136 485L152 423L316 465L451 384L316 303L152 345L136 283L239 257L139 197L172 143L270 202L244 102L306 86L348 249L480 328L480 174L361 55L407 9L480 82L480 -32L544 -32L544 82L617 9L663 55L544 174L544 328L676 249L718 86L780 102L754 202L852 143L885 197L785 257L888 283L872 345L708 303L573 384L708 465L872 423L888 485L785 511L885 571L852 625L754 566L780 666L718 682L676 519L544 440L544 594L663 713L617 759L544 686L544 800z" />
<glyph glyph-name="star-struck"
unicode="&#xF587;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM384 567.437504L356 504.562496L288 497.124992L338.875008 451.437504L324.562496 384L384 418.249984L443.437504 384L429.124992 451.437504L480 497.124992L412 504.562496L384 567.437504zM640 567.437504L612 504.562496L544 497.124992L594.875008 451.437504L580.562496 384L640 418.249984L699.437504 384L685.124992 451.437504L736 497.124992L668 504.562496L640 567.437504zM345.937504 288L290.8750016 256C296.4350016 246.4400000000001 302.56 237.290624 309.25 228.562496C315.94 219.8343680000001 323.217504 211.542496 330.937504 203.750016C338.657504 195.957504 346.850016 188.628128 355.5 181.875008C372.8 168.368736 391.829984 157.0387520000001 412.249984 148.312512C422.46 143.949376 433.035008 140.2624960000001 443.875008 137.2499840000001C465.555008 131.2249920000001 488.4 128 512 128C582.8 128 646.742496 156.995008 693.062496 203.750016C700.782496 211.542496 708.06 219.8343680000001 714.750016 228.562496C721.44 237.290624 727.564992 246.4400000000001 733.124992 256L678.062496 288C648.942496 237.88 598.270624 201.944384 538.249984 193.750016C529.675616 192.57936 520.92 192 512 192C503.08 192 494.324384 192.57936 485.750016 193.750016C477.175616 194.9206400000001 468.782496 196.6750080000001 460.624992 198.937504C444.310016 203.462496 428.907488 210.0800000000001 414.687488 218.5C407.577504 222.710016 400.729376 227.3718720000001 394.249984 232.437504C381.291264 242.568736 369.696256 254.3562560000001 359.687488 267.437504C354.683136 273.978112 350.097504 280.8400000000001 345.937504 288z" />
<glyph glyph-name="stop-circle"
unicode="&#xF28D;"
horiz-adv-x="1024" d="M512 800C283.2 800 96 612.8 96 384C96 155.2000000000001 283.2 -32 512 -32C740.8 -32 928 155.2000000000001 928 384C928 612.8 740.8 800 512 800zM512 736C705.6 736 864 577.6 864 384C864 190.4 705.6 32 512 32C318.4 32 160 190.4 160 384C160 577.6 318.4 736 512 736zM352 544L352 464L352 304L352 224L432 224L592 224L672 224L672 304L672 464L672 544L592 544L432 544L352 544zM416 480L608 480L608 288L416 288L416 480z" />
<glyph glyph-name="comments"
unicode="&#xF086;"
horiz-adv-x="1024" d="M64 736L64 224L192 224L192 61L244 103L395 224L704 224L704 736zM128 672L640 672L640 288L373 288L364 281L256 195L256 288L128 288zM768 608L768 544L896 544L896 160L768 160L768 67L651 160L411 160L331 96L629 96L832 -67L832 96L960 96L960 608z" />
<glyph glyph-name="closed-captioning"
unicode="&#xF20A;"
horiz-adv-x="1024" d="M64 704L64 131L960 131L960 704zM128 640L896 640L896 195L128 195zM384 576C296 576 224 504 224 416C224 328 296 256 384 256C422.250016 256 456.875008 270.124992 484 292L444 342C427.124992 328.375008 406.624992 320 384 320C330.624992 320 288 362.624992 288 416C288 469.375008 330.624992 512 384 512C406.624992 512 427.124992 503.624992 444 490L484 540C456.875008 561.875008 422.250016 576 384 576zM704 576C616 576 544 504 544 416C544 328 616 256 704 256C742.250016 256 776.875008 270.124992 804 292L764 342C747.124992 328.375008 726.624992 320 704 320C650.624992 320 608 362.624992 608 416C608 469.375008 650.624992 512 704 512C726.624992 512 747.124992 503.624992 764 490L804 540C776.875008 561.875008 742.250016 576 704 576z" />
<glyph glyph-name="thumbs-down"
unicode="&#xF165;"
horiz-adv-x="1024" d="M325 704C279.875008 704 240.5 672.124992 231 628L162 308C149.375008 248.875008 195.624992 192 256 192L440 192L434 168C427.5 163 423.375008 160.875008 414 148C399 127.5 384 95.749984 384 53C384 7.375008 425.250016 -32 477 -32L490 -32L500 -23L717 192L864 192L864 704zM325 640L672 640L672 237L467 35C453.5 37.624992 448 43.124992 448 53C448 81.875008 456.750016 99.749984 465 111C473.250016 122.2499840000001 479 125 479 125L490 131L494 144L513 216L523 256L256 256C234.875008 256 220.624992 274.375008 225 295L293 615C296.250016 630 309.750016 640 325 640zM736 640L800 640L800 256L736 256z" />
<glyph glyph-name="grinning-winking-face"
unicode="&#xF58C;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 512C341.5 512 320 490.5 320 464C320 437.5 341.5 416 368 416C394.5 416 416 437.5 416 464C416 490.5 394.5 512 368 512zM576 480L576 416L736 416L736 480zM671 351C671 297.124992 649.375008 259.7499840000001 618 233C586.624992 206.2499840000001 545.124992 192 512 192C444.124992 192 398 219.5 362 255L317 209C361.624992 165.124992 426.250016 128 512 128C561.750016 128 616.375008 146.7499840000001 660 184C703.624992 221.2499840000001 735 278.7499840000001 735 351z" />
<glyph glyph-name="images"
unicode="&#xF302;"
horiz-adv-x="1024" d="M64 736L64 160L192 160L192 32L960 32L960 608L832 608L832 736zM128 672L768 672L768 224L128 224zM192 608L192 288L704 288L704 608zM256 544L640 544L640 352L256 352zM832 544L896 544L896 96L256 96L256 160L832 160z" />
<glyph glyph-name="compass"
unicode="&#xF14E;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM479 702C479.375008 702 479.624992 702 480 702L480 672L544 672L544 702C695.624992 687.124992 815.124992 567.624992 830 416L800 416L800 352L830 352C815.124992 200.375008 695.624992 80.875008 544 66L544 96L480 96L480 66C328.375008 80.875008 208.875008 200.375008 194 352L224 352L224 416L194 416C208.875008 567.375008 327.875008 686.749984 479 702zM720 592L453 443L304 176L571 325zM512 432C538.5 432 560 410.5 560 384C560 357.5 538.5 336 512 336C485.5 336 464 357.5 464 384C464 410.5 485.5 432 512 432z" />
<glyph glyph-name="code-file"
unicode="&#xF1C9;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 589L823 599L631 791L621 800zM256 736L576 736L576 544L768 544L768 32L256 32zM640 690L722 608L640 608zM512 480L448 96L512 96L576 480zM391 404L311 308L294 288L311 268L391 172L441 212L378 288L441 364zM633 404L583 364L646 288L583 212L633 172L713 268L730 288L713 308z" />
<glyph glyph-name="squinting-face-with-tongue"
unicode="&#xF58A;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM352 512C284.992 512 236.5 473.375008 236.5 473.375008L275.5 422.624992C275.5 422.624992 310.1744992 448 352.062496 448C393.950496 448 428.562496 422.624992 428.562496 422.624992L467.562496 473.375008C467.498496 473.375008 419.008 512 352 512zM672 512C604.992 512 556.5 473.375008 556.5 473.375008L595.5 422.624992C595.5 422.624992 630.174496 448 672.062496 448C713.950496 448 748.562496 422.624992 748.562496 422.624992L787.562496 473.375008C787.498496 473.375008 739.008 512 672 512zM376.5 340.562496L327.437504 299.437504C355.494176 266.009728 398.443392 242.3571520000001 448 231.2499840000001L448 192C448 156.8 476.8 128 512 128C547.2 128 576 156.8 576 192L576 231.2499840000001C625.554688 242.359072 668.465984 265.98704 696.5 299.437504L647.437504 340.562496C620.301504 308.1144960000001 568.416 288 512 288C455.584 288 403.668 308.146496 376.5 340.562496z" />
<glyph glyph-name="clipboard"
unicode="&#xF328;"
horiz-adv-x="1024" d="M512 800C471.750016 800 443.124992 771.5 429 736L192 736L192 0L832 0L832 736L595 736C580.875008 771.5 552.250016 800 512 800zM512 736C529.750016 736 544 721.749984 544 704L544 672L640 672L640 608L384 608L384 672L480 672L480 704C480 721.749984 494.250016 736 512 736zM256 672L320 672L320 544L704 544L704 672L768 672L768 64L256 64z" />
<glyph glyph-name="newspaper"
unicode="&#xF1EA;"
horiz-adv-x="1024" d="M96 736L96 160C96 89.249984 153.250016 32 224 32L800 32C870.750016 32 928 89.249984 928 160L928 512L736 512L736 736zM160 672L672 672L672 160C672 136.624992 679.124992 114.875008 690 96L224 96C185.875008 96 160 121.875008 160 160zM224 608L224 448L608 448L608 608zM288 544L544 544L544 512L288 512zM736 448L864 448L864 160C864 121.875008 838.124992 96 800 96C761.875008 96 736 121.875008 736 160zM224 416L224 352L384 352L384 416zM448 416L448 352L608 352L608 416zM224 320L224 256L384 256L384 320zM448 320L448 256L608 256L608 320zM224 224L224 160L384 160L384 224zM448 224L448 160L608 160L608 224z" />
<glyph glyph-name="hand-pointing-left"
unicode="&#xF0A5;"
horiz-adv-x="1024" d="M541 800C489.250016 800 448 760.624992 448 715C448 672.249984 463 640.5 478 620C487.375008 607.124992 491.5 605 498 600L504 576L160 576C107.375008 576 64 532.624992 64 480C64 427.375008 107.375008 384 160 384L274 384L327 140C336.5 95.875008 375.875008 64 421 64L960 64L960 576L781 576L564 791L554 800zM531 733L736 531L736 128L421 128C405.750016 128 392.250016 138 389 153L332 423L326 448L160 448C141.875008 448 128 461.875008 128 480C128 498.124992 141.875008 512 160 512L587 512L577 552L558 624L554 637L543 643C543 643 537.250016 645.749984 529 657C520.750016 668.249984 512 686.124992 512 715C512 724.875008 517.5 730.375008 531 733zM800 512L896 512L896 128L800 128z" />
<glyph glyph-name="object-group"
unicode="&#xF247;"
horiz-adv-x="1024" d="M160 736L160 608L192 608L192 160L160 160L160 32L288 32L288 64L736 64L736 32L864 32L864 160L832 160L832 608L864 608L864 736L736 736L736 704L288 704L288 736zM288 640L736 640L736 608L768 608L768 160L736 160L736 128L288 128L288 160L256 160L256 608L288 608zM320 576L320 320L448 320L448 192L704 192L704 448L576 448L576 576zM384 512L512 512L512 384L384 384zM576 384L640 384L640 256L512 256L512 320L576 320z" />
<glyph glyph-name="image"
unicode="&#xF03E;"
horiz-adv-x="1024" d="M64 736L64 32L960 32L960 736zM128 672L896 672L896 227L727 397L704 420L559 275L375 461L352 484L128 260zM768 608C732.624992 608 704 579.375008 704 544C704 508.624992 732.624992 480 768 480C803.375008 480 832 508.624992 832 544C832 579.375008 803.375008 608 768 608zM352 393L646 96L128 96L128 169zM704 329L896 137L896 96L737 96L604 230z" />
<glyph glyph-name="keyboard"
unicode="&#xF11C;"
horiz-adv-x="1024" d="M96 672C61 672 32 643 32 608L32 160C32 125 61 96 96 96L928 96C963 96 992 125 992 160L992 608C992 643 963 672 928 672zM96 608L928 608L928 160L96 160zM160 544L160 480L224 480L224 544zM288 544L288 480L352 480L352 544zM416 544L416 480L480 480L480 544zM544 544L544 480L608 480L608 544zM672 544L672 480L736 480L736 544zM800 544L800 480L864 480L864 544zM160 416L160 352L288 352L288 416zM352 416L352 352L416 352L416 416zM480 416L480 352L544 352L544 416zM608 416L608 352L672 352L672 416zM736 416L736 352L864 352L864 416zM160 288L160 224L288 224L288 288zM352 288L352 224L672 224L672 288zM736 288L736 224L864 224L864 288z" />
<glyph glyph-name="grinning-squinting-face"
unicode="&#xF585;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM321.750016 538.624992L286.25 485.375008L342.3125120000001 448L286.25 410.624992L321.750016 357.375008L457.687488 448L321.750016 538.624992zM702.249984 538.624992L566.312512 448L702.249984 357.375008L737.750016 410.624992L681.687488 448L737.750016 485.375008L702.249984 538.624992zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="hand-pointing-down"
unicode="&#xF0A7;"
horiz-adv-x="1024" d="M320 832L320 653L105 436L96 426L96 413C96 361.2499840000001 135.375008 320 181 320C223.750016 320 255.5 335 276 350C288.875008 359.375008 291 363.5 296 370L320 376L320 32C320 -20.624992 363.375008 -64 416 -64C468.624992 -64 512 -20.624992 512 32L512 146L756 199C800.124992 208.5 832 247.875008 832 293L832 832zM384 768L768 768L768 672L384 672zM365 608L768 608L768 293C768 277.7499840000001 758 264.2499840000001 743 261L473 204L448 198L448 32C448 13.875008 434.124992 0 416 0C397.875008 0 384 13.875008 384 32L384 459L344 449L272 430L259 426L253 415C253 415 250.250016 409.249984 239 401C227.750016 392.749984 209.875008 384 181 384C171.124992 384 165.624992 389.5 163 403z" />
<glyph glyph-name="gem"
unicode="&#xF3A5;"
horiz-adv-x="1024" d="M305 704L135 500L119 481L135 460L487 12L512 -20L537 12L889 460L905 481L889 500L719 704zM335 640L452 640L366 512L228 512zM572 640L689 640L796 512L658 512zM512 613L580 512L444 512zM225 448L360 448L436 179zM426 448L597 448L512 150zM664 448L799 448L588 180z" />
<glyph glyph-name="kissing-face-with-smiling-eyes"
unicode="&#xF597;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM352 512C284.992 512 236.5 473.375008 236.5 473.375008L275.5 422.624992C275.5 422.624992 310.1744992 448 352.062496 448C393.950496 448 428.562496 422.624992 428.562496 422.624992L467.562496 473.375008C467.498496 473.375008 419.008 512 352 512zM672 512C604.992 512 556.5 473.375008 556.5 473.375008L595.5 422.624992C595.5 422.624992 630.174496 448 672.062496 448C713.950496 448 748.562496 422.624992 748.562496 422.624992L787.562496 473.375008C787.498496 473.375008 739.008 512 672 512zM480 351.750016L480 304.062496C508.864 304.062496 527.249984 290.787008 527.249984 283.875008C527.250016 276.99088 508.944576 263.856096 480.312512 263.750016C480.205792 263.75024 480.106784 263.750016 480 263.750016L480 263.6874880000001L480 216.062496L480 216C480.106784 216 480.205792 215.999776 480.312512 216C508.975936 215.893888 527.249984 202.759136 527.249984 195.875008C527.250016 188.963008 508.832 175.687488 480 175.687488L480 128C533.408 128 575.249984 157.827008 575.249984 195.875008C575.249984 212.90816 566.558144 228.076192 552.5 239.875008C566.558144 251.6737920000001 575.249984 266.841824 575.249984 283.875008C575.250016 321.923008 533.408 351.750016 480 351.750016z" />
<glyph glyph-name="address-book"
unicode="&#xF2B9;"
horiz-adv-x="1024" d="M192 768L192 608L160 608L160 544L256 544L256 704L768 704L768 64L256 64L256 160L192 160L192 0L832 0L832 768zM512 576C441.624992 576 384 518.375008 384 448C384 412.375008 399.250016 380.2499840000001 423 357C380.375008 328.124992 352 278.875008 352 224L416 224C416 277 459 320 512 320C565 320 608 277 608 224L672 224C672 278.875008 643.624992 328.124992 601 357C624.750016 380.2499840000001 640 412.375008 640 448C640 518.375008 582.375008 576 512 576zM192 512L192 448L160 448L160 384L256 384L256 512zM512 512C547.750016 512 576 483.749984 576 448C576 412.249984 547.750016 384 512 384C476.250016 384 448 412.249984 448 448C448 483.749984 476.250016 512 512 512zM192 352L192 288L160 288L160 224L256 224L256 352z" />
<glyph glyph-name="face-without-mouth"
unicode="&#xF5A4;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512z" />
<glyph glyph-name="paper-plane"
unicode="&#xF1D8;"
horiz-adv-x="1024" d="M115 725L129 665L191 384L129 103L115 43L172 66L876 354L949 384L876 414L172 702zM204 619L701 416L249 416zM249 352L701 352L204 149z" />
<glyph glyph-name="alternate-arrow-circle-up"
unicode="&#xF35B;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM512 608L384 480L480 480L480 160L544 160L544 480L640 480z" />
<glyph glyph-name="play-circle"
unicode="&#xF144;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM384 604L384 164L432 192L720 356L768 384L720 412L432 576zM448 494L639 384L448 274z" />
<glyph glyph-name="image-file"
unicode="&#xF1C5;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 589L823 599L631 791L621 800zM256 736L576 736L576 544L768 544L768 32L256 32zM640 690L722 608L640 608zM675 448C657.375008 448 643 433.624992 643 416C643 398.375008 657.375008 384 675 384C692.624992 384 707 398.375008 707 416C707 433.624992 692.624992 448 675 448zM448 397L425 375L297 247L343 201L448 306L521 233L544 211L567 233L608 274L681 201L727 247L631 343L608 365L585 343L544 302L471 375z" />
<glyph glyph-name="dizzy-face"
unicode="&#xF567;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM310.6249984 566.624992L265.3750016 521.375008L322.750016 464L265.3750016 406.624992L310.6249984 361.375008L368 418.750016L425.375008 361.375008L470.624992 406.624992L413.249984 464L470.624992 521.375008L425.375008 566.624992L368 509.249984L310.6249984 566.624992zM598.624992 566.624992L553.375008 521.375008L610.750016 464L553.375008 406.624992L598.624992 361.375008L656 418.750016L713.375008 361.375008L758.624992 406.624992L701.249984 464L758.624992 521.375008L713.375008 566.624992L656 509.249984L598.624992 566.624992zM512 320C441.408 320 384 271.1874880000001 384 211.187488C384 155.987488 427.072 128 512 128C596.928 128 640 155.987488 640 211.187488C640 271.1874880000001 582.592 320 512 320zM512 256C546.688 256 576 235.4754880000001 576 211.187488C576 205.939488 576 192 512 192C448 192 448 205.939488 448 211.187488C448 235.4754880000001 477.312 256 512 256z" />
<glyph glyph-name="laughing-winking-face"
unicode="&#xF59C;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM576 480L576 416L736 416L736 480L576 480zM288 288C288 288 339.52 128 512 128C684.48 128 736 288 736 288L288 288z" />
<glyph glyph-name="alternate-money-bill"
unicode="&#xF3D1;"
horiz-adv-x="1024" d="M64 672L64 128L960 128L960 672L64 672zM192 608L832 608C832 572.64 860.64 544 896 544L896 256C860.64 256 832 227.36 832 192L192 192C192 227.36 163.36 256 128 256L128 544C163.36 544 192 572.64 192 608zM480 544C480 497.952 462.048 480 416 480L416 416C440.824032 416 461.977152 420.837408 480 429.187488L480 256L544 256L544 544L480 544zM272 448C245.504 448 224 426.496 224 400C224 373.504 245.504 352 272 352C298.496 352 320 373.504 320 400C320 426.496 298.496 448 272 448zM752 448C725.504 448 704 426.496 704 400C704 373.504 725.504 352 752 352C778.496 352 800 373.504 800 400C800 426.496 778.496 448 752 448z" />
<glyph glyph-name="calendar-times"
unicode="&#xF273;"
horiz-adv-x="1024" d="M288 736L288 704L160 704L160 0L864 0L864 704L736 704L736 736L672 736L672 704L352 704L352 736zM224 640L288 640L288 608L352 608L352 640L672 640L672 608L736 608L736 640L800 640L800 576L224 576zM224 512L800 512L800 64L224 64zM423 423L377 377L466 288L377 199L423 153L512 242L601 153L647 199L558 288L647 377L601 423L512 334z" />
<glyph glyph-name="rolling-on-the-floor-laughing"
unicode="&#xF586;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 259.8400000000001 150.972 148.538016 237.5 72.249984C243.644 87.930016 252.9149984 108.045984 265.8750016 132.750016C200.6270016 196.685984 160 285.664 160 384C160 578.0799999999999 317.92 736 512 736C610.336 736 699.314016 695.3730016 763.249984 630.1249984C787.985984 643.0850015999999 808.070016 652.388 823.750016 658.5C747.462016 745.028 636.16 800 512 800zM880 608C853.504 608 768 560 768 560C768 560 853.504 512 880 512C906.496 512 928 533.504 928 560C928 586.496 906.496 608 880 608zM617.375008 598.6249984L489.375008 470.624992L534.624992 425.375008L662.624992 553.375008L617.375008 598.6249984zM754.249984 466.249984L429.750016 141.750016C455.669984 132.790016 483.2 128 512 128C653.44 128 768 242.56 768 384C768 412.8 763.210016 440.329984 754.249984 466.249984zM921.312512 456.124992C908.512512 451.004992 894.624 448 880 448C876.128 448 869.276 448.094016 857.5 450.750016C861.66 429.117984 864 406.848 864 384C864 189.92 706.08 32 512 32C489.152 32 466.882016 34.34 445.249984 38.5C447.906016 26.724 448 19.872 448 16C448 1.376 444.995008 -12.512512 439.875008 -25.312512C463.363008 -29.440512 487.36 -32 512 -32C741.376 -32 928 154.624 928 384C928 408.64 925.440512 432.636992 921.312512 456.124992zM425.375008 406.624992L297.3750016 278.624992L342.624992 233.375008L470.624992 361.375008L425.375008 406.624992zM336 128C336 128 288 42.496 288 16C288 -10.496 309.504 -32 336 -32C362.496 -32 384 -10.496 384 16C384 42.496 336 128 336 128z" />
<glyph glyph-name="times-circle"
unicode="&#xF057;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM391 551L345 505L466 384L345 263L391 217L512 338L633 217L679 263L558 384L679 505L633 551L512 430z" />
<glyph glyph-name="calendar-plus"
unicode="&#xF271;"
horiz-adv-x="1024" d="M288 736L288 704L160 704L160 0L864 0L864 704L736 704L736 736L672 736L672 704L352 704L352 736zM224 640L288 640L288 608L352 608L352 640L672 640L672 608L736 608L736 640L800 640L800 576L224 576zM224 512L800 512L800 64L224 64zM480 448L480 320L352 320L352 256L480 256L480 128L544 128L544 256L672 256L672 320L544 320L544 448z" />
<glyph glyph-name="crying-face"
unicode="&#xF5B3;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 274.956736 814.106592 177.368576 736 112.750016L736 384C736 401.664 721.696 416 704 416C686.304 416 672 401.664 672 384L672 70.875008C623.92704 46.204512 569.642304 32 512 32C454.357696 32 400.07296 46.204512 352 70.875008L352 384C352 401.664 337.6960000000001 416 320 416C302.304 416 288 401.664 288 384L288 112.750016C209.8934208 177.368576 160 274.956736 160 384C160 578.0799999999999 317.92 736 512 736zM384 576C344.32 576 314.2224992 553.942496 294.0624992 535.062496C273.5824992 516.182496 261.1249984 497.937504 261.1249984 497.937504L314.8750016 462.062496C314.8749984 462.062496 322.857504 475.817504 336.937504 488.937504C351.017504 502.057504 369.6 512 384 512L480 512L480 576L384 576zM544 576L544 512L640 512C654.4 512 672.982496 502.057504 687.062496 488.937504C701.142496 475.817504 709.124992 462.062496 709.124992 462.062496L762.875008 497.937504C762.875008 497.937504 750.417504 516.182496 729.937504 535.062496C709.777504 553.942496 679.68 576 640 576L544 576zM512 384C459.072 384 416 333.76 416 272C416 210.24 459.072 160 512 160C564.928 160 608 210.24 608 272C608 333.76 564.928 384 512 384zM512 320C529.024 320 544 297.568 544 272C544 246.432 529.024 224 512 224C494.976 224 480 246.432 480 272C480 297.568 494.976 320 512 320z" />
<glyph glyph-name="powerpoint-file"
unicode="&#xF1C4;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 588.8124992L822.375008 598.3750016L630.375008 790.3750016L620.812512 800L192 800zM256 736L576 736L576 544L768 544L768 32L256 32L256 736zM640 691.1875008L723.187488 608L640 608L640 691.1875008zM416 480L416 416L544 416C582.4 416 608 390.4 608 352C608 313.6 582.4 288 544 288C505.6 288 480 313.6 480 352L416 352L416 128L480 128L480 243.1874880000001C499.2 230.387488 521.6 224 544 224C614.4 224 672 281.6 672 352C672 422.4 614.4 480 544 480L416 480z" />
<glyph glyph-name="hdd"
unicode="&#xF0A0;"
horiz-adv-x="1024" d="M199 704L96 292L96 64L928 64L928 292L825 704zM249 640L775 640L855 320L169 320zM160 256L864 256L864 128L160 128zM768 224C750.375008 224 736 209.624992 736 192C736 174.375008 750.375008 160 768 160C785.624992 160 800 174.375008 800 192C800 209.624992 785.624992 224 768 224z" />
<glyph glyph-name="hourglass"
unicode="&#xF254;"
horiz-adv-x="1024" d="M224 768L224 704L288 704L288 576C288 494.624992 331.750016 423.249984 397 384C331.750016 344.7499840000001 288 273.375008 288 192L288 64L224 64L224 0L800 0L800 64L736 64L736 192C736 273.375008 692.250016 344.7499840000001 627 384C692.250016 423.249984 736 494.624992 736 576L736 704L800 704L800 768zM352 704L672 704L672 576C672 487.249984 600.750016 416 512 416C423.250016 416 352 487.249984 352 576zM512 352C600.750016 352 672 280.7499840000001 672 192L672 64L352 64L352 192C352 280.7499840000001 423.250016 352 512 352z" />
<glyph glyph-name="credit-card"
unicode="&#xF09D;"
horiz-adv-x="1024" d="M160 704C107.375008 704 64 660.624992 64 608L64 160C64 107.375008 107.375008 64 160 64L864 64C916.624992 64 960 107.375008 960 160L960 608C960 660.624992 916.624992 704 864 704zM160 640L864 640C882.124992 640 896 626.124992 896 608L896 544L160 544L160 480L896 480L896 160C896 141.875008 882.124992 128 864 128L160 128C141.875008 128 128 141.875008 128 160L128 608C128 626.124992 141.875008 640 160 640z" />
<glyph glyph-name="bell-slash"
unicode="&#xF1F6;"
horiz-adv-x="1024" d="M119 823L73 777L905 -55L951 -9L832 110L832 160L800 160C782.375008 160 768 174.375008 768 192L768 471C768 593.5 685.375008 697.5 575 727C575.375008 730 576 732.875008 576 736C576 771.375008 547.375008 800 512 800C476.624992 800 448 771.375008 448 736C448 733.249984 448.624992 730.624992 449 728C406.124992 717.124992 366.624992 695.624992 334 664C324.375008 654.749984 315.875008 644.375008 308 634zM498 672C504.5 672.5 511.5 672.249984 518 672C620.624992 668.875008 704 578.875008 704 471L704 238L354 588C361.124992 598.5 368.624992 609 378 618C410.875008 650 452.875008 668.875008 498 672zM257 504C256.250016 495.875008 256 488.249984 256 480L256 192C256 174.375008 241.624992 160 224 160L192 160L192 96L422 96C418.375008 85.875008 416 75.249984 416 64C416 11.375008 459.375008 -32 512 -32C564.624992 -32 608 11.375008 608 64C608 75.249984 605.624992 85.875008 602 96L666 96L602 160L314 160C317.624992 170 320 180.7499840000001 320 192L320 442zM512 96C530 96 544 82 544 64C544 46 530 32 512 32C494 32 480 46 480 64C480 82 494 96 512 96z" />
<glyph glyph-name="lemon"
unicode="&#xF094;"
horiz-adv-x="1024" d="M416 768C333.375008 768 259.875008 722 209 652C158.124992 582 128 487.5 128 384C128 280.5 158.124992 186 209 116C259.875008 46 333.375008 0 416 0C679.375008 0 819.750016 150.624992 854 309C878.124992 326.375008 896 352.124992 896 384C896 415.875008 878.124992 441.624992 854 459C817.875008 621.375008 650 768 416 768zM416 704C474.750016 704 529.250016 671.5 571 614C612.750016 556.5 640 474.875008 640 384C640 293.124992 612.750016 211.5 571 154C529.250016 96.5 474.750016 64 416 64C357.250016 64 302.750016 96.5 261 154C219.250016 211.5 192 293.124992 192 384C192 474.875008 219.250016 556.5 261 614C302.750016 671.5 357.250016 704 416 704zM620 655C717.250016 603.5 779.750016 518.749984 795 436L798 420L813 413C824.124992 407.875008 832 397.124992 832 384C832 370.875008 824.124992 360.124992 813 355L798 348L795 332C778 239.375008 722.124992 148.5 610 100C614.375008 105.249984 618.875008 110.375008 623 116C673.875008 186 704 280.5 704 384C704 487.5 673.875008 582 623 652C622.124992 653.124992 620.875008 653.875008 620 655zM416 640C393.124992 640 371.624992 631.749984 352 618L417 465L483 617C462.750016 631.875008 439.750016 640 416 640zM304 567C278.875008 527.624992 261.624992 474.875008 257 416L368 416zM530 564L465 416L575 416C570.5 473.5 554.250016 524.875008 530 564zM257 352C261.5 294.5 277.750016 243.124992 302 204L367 352zM464 352L528 201C553.124992 240.375008 570.375008 293.124992 575 352zM415 303L349 151C369.250016 136.124992 392.250016 128 416 128C438.875008 128 460.375008 136.2499840000001 480 150z" />
<glyph glyph-name="alternate-grinning-face"
unicode="&#xF581;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 544A48 96 0 0 1 368 352A48 96 0 0 1 368 544zM656 544A48 96 0 0 1 656 352A48 96 0 0 1 656 544zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="alternate-arrow-circle-right"
unicode="&#xF35A;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM608 512L608 416L288 416L288 352L608 352L608 256L736 384z" />
<glyph glyph-name="archive-file"
unicode="&#xF1C6;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 800zM256 736L480 736L480 704L544 704L544 736L768 736L768 32L256 32zM480 672L480 608L544 608L544 672zM480 576L480 512L544 512L544 576zM480 480L480 410C443 396.624992 416 361.375008 416 320C416 267.375008 459.375008 224 512 224C564.624992 224 608 267.375008 608 320C608 361.375008 581 396.624992 544 410L544 480zM512 352C530 352 544 338 544 320C544 302 530 288 512 288C494 288 480 302 480 320C480 338 494 352 512 352z" />
<glyph glyph-name="lizard--hand-"
unicode="&#xF258;"
horiz-adv-x="1024" d="M458 768C401.124992 768 347.624992 738.124992 319 689L150 398C135.750016 373.5 128 345.375008 128 317L128 0L448 0L448 138C532.124992 163.2499840000001 575.5 207.5 589 224L791 224C831.875008 224 862.875008 264.375008 853 304L849 319C834.750016 375.7499840000001 783.5 416 725 416L505 416L489 480L693 480C751.5 480 802.750016 520.249984 817 577L828 619C865.750016 632 896 662.124992 896 704L896 768zM458 704L832 704C832 685.875008 818.124992 672 800 672L512 672L480 608L759 608L755 592C747.875008 563.375008 722.5 544 693 544L489 544C448.124992 544 417.124992 503.624992 427 464L443 400C450.124992 371.624992 475.750016 352 505 352L725 352C754.5 352 779.875008 332.624992 787 304L791 288L560 288L550 275C550 275 503.875008 209.5 410 191L384 186L384 64L192 64L192 317C192 334 196.375008 351.2499840000001 205 366L375 656C392.250016 685.5 423.750016 704 458 704z" />
<glyph glyph-name="laughing-squinting-face"
unicode="&#xF59B;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM321.750016 538.624992L286.25 485.375008L342.3125120000001 448L286.25 410.624992L321.750016 357.375008L457.687488 448L321.750016 538.624992zM702.249984 538.624992L566.312512 448L702.249984 357.375008L737.750016 410.624992L681.687488 448L737.750016 485.375008L702.249984 538.624992zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="grinning-face"
unicode="&#xF580;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512zM288 288C288 288 339.36 128 512 128C684.64 128 736 288 736 288L288 288z" />
<glyph glyph-name="thumbs-up"
unicode="&#xF164;"
horiz-adv-x="1024" d="M534 800L524 791L307 576L160 576L160 64L699 64C744.124992 64 783.5 95.875008 793 140L862 460C874.624992 519.124992 828.375008 576 768 576L584 576L590 600C596.5 605 600.624992 607.124992 610 620C625 640.5 640 672.249984 640 715C640 760.624992 598.750016 800 547 800zM557 733C570.5 730.375008 576 724.875008 576 715C576 686.124992 567.250016 668.249984 559 657C550.750016 645.749984 545 643 545 643L534 637L530 624L511 552L501 512L768 512C789.124992 512 803.375008 493.624992 799 473L731 153C727.750016 138 714.250016 128 699 128L352 128L352 531zM224 512L288 512L288 128L224 128z" />
<glyph glyph-name="hospital"
unicode="&#xF0F8;"
horiz-adv-x="1024" d="M480 800L480 736L416 736L416 672L480 672L480 608L544 608L544 672L608 672L608 736L544 736L544 800zM192 736L192 0L480 0L480 96L544 96L544 0L832 0L832 736L672 736L672 672L768 672L768 64L608 64L608 160L416 160L416 64L256 64L256 672L352 672L352 736zM352 544L352 480L416 480L416 544zM480 544L480 480L544 480L544 544zM608 544L608 480L672 480L672 544zM352 416L352 352L416 352L416 416zM480 416L480 352L544 352L544 416zM608 416L608 352L672 352L672 416zM352 288L352 224L416 224L416 288zM480 288L480 224L544 224L544 288zM608 288L608 224L672 224L672 288z" />
<glyph glyph-name="kissing-face"
unicode="&#xF596;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512zM480 351.750016L480 304.062496C508.832 304.062496 527.249984 290.787008 527.249984 283.875008C527.249984 276.99088 508.94432 263.856096 480.312512 263.750016C480.205792 263.75024 480.106784 263.750016 480 263.750016L480 263.6874880000001L480 216.062496L480 216C480.106784 216 480.205792 215.999776 480.312512 216C508.94432 215.893888 527.249984 202.759136 527.249984 195.875008C527.249984 188.963008 508.832 175.687488 480 175.687488L480 128C533.408 128 575.249984 157.827008 575.249984 195.875008C575.249984 212.914304 566.497728 228.074528 552.437504 239.875008C566.497728 251.675488 575.249984 266.83568 575.249984 283.875008C575.249984 321.923008 533.408 351.750016 480 351.750016z" />
<glyph glyph-name="face-blowing-a-kiss"
unicode="&#xF598;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C587.808 -32 658.657504 -11.302016 719.937504 24.249984L673.812512 71.687488C625.300512 46.471488 570.336 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736C706.08 736 864 578.0799999999999 864 384C864 383.168 863.875008 382.362496 863.875008 381.562496C886.883008 377.850496 908.194496 369.303008 926.562496 356.375008C927.170496 365.559008 928 374.656 928 384C928 613.376 741.376 800 512 800zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM640 512C572.992 512 524.5 473.375008 524.5 473.375008L563.5 422.624992C563.5 422.624992 598.174496 448 640.062496 448C681.950496 448 716.562496 422.624992 716.562496 422.624992L755.562496 473.375008C755.498496 473.375008 707.008 512 640 512zM480 351.750016L480 304.062496C508.864 304.062496 527.249984 290.787008 527.249984 283.875008C527.250016 276.99088 508.944576 263.856096 480.312512 263.750016C480.205792 263.75024 480.106784 263.750016 480 263.750016L480 263.6874880000001L480 216.062496L480 216C480.106784 216 480.205792 215.999776 480.312512 216C508.975936 215.893888 527.249984 202.759136 527.249984 195.875008C527.250016 188.963008 508.832 175.687488 480 175.687488L480 128C533.408 128 575.249984 157.827008 575.249984 195.875008C575.249984 212.90816 566.558144 228.076192 552.5 239.875008C566.558144 251.6737920000001 575.249984 266.841824 575.249984 283.875008C575.250016 321.923008 533.408 351.750016 480 351.750016zM728 320C679.04 320 640 278.6950080000001 640 230.375008C640 202.279008 656.164512 181.592992 667.812512 169.624992L784 50.249984L900.5 169.624992C912.148 181.5609920000001 928 198.119008 928 230.375008C928 278.6950080000001 888.96 320 840 320C816.64 320 797.44 312 784 304C770.56 312 751.36 320 728 320zM728 256C735.648 256 743.442016 253.64 751.249984 249L784 229.5L816.750016 249C824.557984 253.64 832.352 256 840 256C852.8 256 864 244.0070080000001 864 230.375008C864 224.3910080000001 863.743488 223.5925120000001 854.687488 214.312512L784.062496 141.937504L713.687488 214.2499840000001C710.071488 217.961984 704 224.999008 704 230.375008C704 244.0070080000001 715.2 256 728 256z" />
<glyph glyph-name="bar-chart"
unicode="&#xF080;"
horiz-adv-x="1024" d="M416 800L416 32L352 32L352 672L160 672L160 32L96 32L96 -32L928 -32L928 32L864 32L864 448L672 448L672 32L608 32L608 800zM480 736L544 736L544 32L480 32z" />
<glyph glyph-name="heart"
unicode="&#xF004;"
horiz-adv-x="1024" d="M304 736C171.624992 736 64 627.124992 64 496C64 450.249984 84.750016 410.624992 104 382C123.250016 353.375008 143 335 143 335L489 -12L512 -35L535 -12L881 335C881 335 960 404.624992 960 496C960 627.124992 852.375008 736 720 736C610.124992 736 539.375008 669.875008 512 642C484.624992 669.875008 413.875008 736 304 736zM304 672C399.624992 672 488 579 488 579L512 552L536 579C536 579 624.375008 672 720 672C817.375008 672 896 592.124992 896 496C896 446.624992 836 380 836 380L512 56L188 380C188 380 172.5 394.875008 157 418C141.5 441.124992 128 471.249984 128 496C128 592.124992 206.624992 672 304 672z" />
<glyph glyph-name="alternate-comment"
unicode="&#xF27A;"
horiz-adv-x="1024" d="M96 736L96 160L256 160L256 -2.5L459.250016 160L928 160L928 736zM160 672L864 672L864 224L436.750016 224L320 130.624992L320 224L160 224z" />
<glyph glyph-name="plus-square"
unicode="&#xF0FE;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM480 544L480 416L352 416L352 352L480 352L480 224L544 224L544 352L672 352L672 416L544 416L544 544z" />
<glyph glyph-name="alternate-list"
unicode="&#xF022;"
horiz-adv-x="1024" d="M329 727L224 622L183 663L137 617L201 553L224 531L247 553L375 681zM480 672L480 608L896 608L896 672zM329 471L224 366L183 407L137 361L201 297L224 275L247 297L375 425zM480 416L480 352L896 352L896 416zM329 215L224 110L183 151L137 105L201 41L224 19L247 41L375 169zM480 160L480 96L896 96L896 160z" />
<glyph glyph-name="smiling-face-with-heart-eyes"
unicode="&#xF584;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM336 544C309.5 544 288 522.5 288 496C288 493 288.250016 490.124992 289 487C290.375008 479.375008 293.5 472.875008 298 467C322 425.624992 384 384 384 384C384 384 480 445 480 496C480 522.5 458.5 544 432 544C405.5 544 384 522.5 384 496C384 522.5 362.5 544 336 544zM592 544C565.5 544 544 522.5 544 496C544 493 544.250016 490.124992 545 487C546.375008 479.375008 549.5 472.875008 554 467C578 425.624992 640 384 640 384C640 384 703.624992 425.249984 727 468C732.124992 477.5 736 486.749984 736 496C736 522.5 714.5 544 688 544C661.5 544 640 522.5 640 496C640 522.5 618.5 544 592 544zM346 288L291 256C335.250016 179.624992 417.5 128 512 128C606.5 128 688.750016 179.624992 733 256L678 288C644.750016 230.624992 583.250016 192 512 192C440.750016 192 379.250016 230.624992 346 288z" />
<glyph glyph-name="alternate-arrow-circle-left"
unicode="&#xF359;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM416 512L288 384L416 256L416 352L736 352L736 416L416 416z" />
<glyph glyph-name="frowning-face-with-open-mouth"
unicode="&#xF57A;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 544C341.5 544 320 508.124992 320 464C320 419.875008 341.5 384 368 384C394.5 384 416 419.875008 416 464C416 508.124992 394.5 544 368 544zM656 544C629.5 544 608 508.124992 608 464C608 419.875008 629.5 384 656 384C682.5 384 704 419.875008 704 464C704 508.124992 682.5 544 656 544zM512 352C438.624992 352 384 292.624992 384 224C384 192.375008 395.375008 160.875008 421 145C446.624992 129.124992 475 128 512 128C549 128 577.375008 129.124992 603 145C628.624992 160.875008 640 192.375008 640 224C640 292.624992 585.375008 352 512 352zM512 288C553.375008 288 576 261.5 576 224C576 202.624992 574.750016 201.875008 570 199C565.250016 196.124992 545.750016 192 512 192C478.250016 192 458.750016 196.124992 454 199C449.250016 201.875008 448 202.624992 448 224C448 261.5 470.624992 288 512 288z" />
<glyph glyph-name="tired-face"
unicode="&#xF5C8;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM321.750016 538.624992L286.25 485.375008L342.3125120000001 448L286.25 410.624992L321.750016 357.375008L457.687488 448L321.750016 538.624992zM702.249984 538.624992L566.312512 448L702.249984 357.375008L737.750016 410.624992L681.687488 448L737.750016 485.375008L702.249984 538.624992zM512 288C428.6720000000001 288 373.303488 218.92 353.687488 181C347.767488 169.576 358.588512 156.750016 370.8125120000001 160.750016C403.356512 171.438016 459.168 192 512 192C564.832 192 620.643488 171.405984 653.187488 160.750016C665.443488 156.718016 676.232512 169.576 670.312512 181C650.696512 218.888 595.328 288 512 288z" />
<glyph glyph-name="peace--hand-"
unicode="&#xF25B;"
horiz-adv-x="1024" d="M480 832C427.375008 832 384 788.624992 384 736L384 687L380 700C364.750016 750.375008 310.375008 779.249984 260 764C209.624992 748.749984 180.750016 694.375008 196 644L279 374C273 371.624992 266.5 368.875008 260 365C233.250016 349 203 316.624992 194 265C187.250016 226.7499840000001 194.624992 193.124992 200 175C200 174.624992 200 174.375008 200 174L225 94C253.875008 0.124992 340.875008 -64 439 -64L608 -64C731.375008 -64 832 36.624992 832 160L832 477C832 478.375008 832 479.624992 832 481C832 481.624992 832 482.375008 832 483C832 483.375008 832 483.624992 832 484C831.750016 485 831.375008 486 831 487C828.124992 522.249984 806.750016 555.124992 772 569C747.875008 578.624992 722.5 577.249984 700 568C689.624992 587.624992 672.875008 604.249984 651 613C625.875008 623 599.124992 621.375008 576 611L576 736C576 788.624992 532.624992 832 480 832zM480 768C498.124992 768 512 754.124992 512 736L512 525L479 441C474.250016 429.124992 472 416.249984 472 404L341 387L258 663C252.750016 680.249984 261.750016 697.749984 279 703C296.250016 708.249984 313.750016 698.249984 319 681L385 462L421 473L448 473L448 736C448 754.124992 461.875008 768 480 768zM614 556C618.124992 556.124992 622.875008 555.624992 627 554C643.750016 547.249984 651.750016 528.749984 645 512L611 427L611 426L598 393C597.375008 391.249984 595.875008 390.5 595 389C578.750016 401.624992 558.250016 409.749984 537 410C537.375008 412.375008 537 414.624992 538 417L586 536C591 548.624992 601.624992 555.749984 614 556zM735 512C739.250016 512 743.750016 511.624992 748 510C760.624992 505 767.750016 493.624992 768 481C768 480.624992 768 480.375008 768 480C768 476.124992 767.5 471.875008 766 468L730 379C723.250016 362.2499840000001 704.750016 354.2499840000001 688 361C671.250016 367.7499840000001 664.250016 386.249984 671 403L705 488C705.5 489.249984 705.5 490.749984 706 492C709.375008 500.375008 715.5 506.749984 723 510C726.750016 511.624992 730.750016 512 735 512zM528 347C544.875008 351 561 340.875008 565 324C567.5 313.624992 566.124992 308.375008 563 303C559.875008 297.624992 553.250016 291 539 286L384 246C374.5 243.5 366.750016 236.7499840000001 362.875008 227.875008C359 218.875008 359.375008 208.624992 364 200L393 147C401.5 131.5 421 125.875008 436.5 134.5C452 143 457.624992 162.5 449 178L440 194L558 225C559 225.2499840000001 560 225.624992 561 226C585.750016 234.624992 605.624992 249.375008 618 270C628.124992 286.875008 632 306.5 630 326C639.375008 315.875008 651.375008 307.5 665 302C701.624992 287.375008 741.875008 296.624992 768 323L768 160C768 71.249984 696.750016 0 608 0L439 0C368.624992 0 306.750016 45.749984 286 113L262 193C259.375008 201.624992 254.124992 231.875008 258 254C264 288.375008 279 301.624992 293 310C307 318.375008 317 319 317 319C317.624992 319 318.375008 319 319 319L525 346C526 346.2499840000001 527 346.624992 528 347z" />
<glyph glyph-name="calendar-minus"
unicode="&#xF272;"
horiz-adv-x="1024" d="M288 736L288 704L160 704L160 0L864 0L864 704L736 704L736 736L672 736L672 704L352 704L352 736zM224 640L288 640L288 608L352 608L352 640L672 640L672 608L736 608L736 640L800 640L800 576L224 576zM224 512L800 512L800 64L224 64zM352 320L352 256L672 256L672 320z" />
<glyph glyph-name="lightbulb"
unicode="&#xF0EB;"
horiz-adv-x="1024" d="M512 768C353.375008 768 224 638.624992 224 480C224 418.624992 251 355 288 298C315.5 255.624992 348.375008 216.624992 384 187L384 96C384 61 413 32 448 32L480 0L544 0L576 32C611 32 640 61 640 96L640 187C675.624992 216.624992 708.5 255.624992 736 298C773 355 800 418.624992 800 480C800 638.624992 670.624992 768 512 768zM512 704C636.124992 704 736 604.124992 736 480C736 438.124992 715.375008 382.875008 683 333C654.124992 288.5 616.124992 248.875008 581 224L443 224C407.875008 248.875008 369.875008 288.5 341 333C308.624992 382.875008 288 438.124992 288 480C288 604.124992 387.875008 704 512 704zM456 160L568 160C570.5 158.2499840000001 573.124992 157 576 156L576 96L448 96L448 156C450.875008 157 453.5 158.2499840000001 456 160z" />
<glyph glyph-name="check-square"
unicode="&#xF14A;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM681 535L448 302L343 407L297 361L425 233L448 211L471 233L727 489z" />
<glyph glyph-name="user"
unicode="&#xF007;"
horiz-adv-x="1024" d="M512 736C388.624992 736 288 635.375008 288 512C288 434.875008 327.375008 366.375008 387 326C272.875008 277 192 163.7499840000001 192 32L256 32C256 173.7499840000001 370.250016 288 512 288C653.750016 288 768 173.7499840000001 768 32L832 32C832 163.7499840000001 751.124992 277 637 326C696.624992 366.375008 736 434.875008 736 512C736 635.375008 635.375008 736 512 736zM512 672C600.750016 672 672 600.749984 672 512C672 423.249984 600.750016 352 512 352C423.250016 352 352 423.249984 352 512C352 600.749984 423.250016 672 512 672z" />
<glyph glyph-name="spock--hand-"
unicode="&#xF259;"
horiz-adv-x="1024" d="M397 832C390.750016 831.875008 384.375008 831.5 378 830C335.124992 820.249984 305 782.124992 303 740C291.250016 741.875008 279.124992 741.749984 267 739C215.624992 727.375008 182.375008 675.375008 194 624L256 355L256 328L228 356C190.750016 393.249984 129.250016 393.249984 92 356C54.750016 318.7499840000001 54.750016 257.2499840000001 92 220L309 3C346.624992 -34.5 398.624992 -64 460 -64L608 -64C731.375008 -64 832 36.624992 832 160L832 384L862 535C872.124992 586.624992 837.624992 637.875008 786 648C775.124992 650.124992 764.375008 650.5 754 649L761 685C771.124992 736.624992 736.624992 787.875008 685 798C633.375008 808.124992 583.124992 773.624992 573 722L541 570L492 757C491.875008 757.249984 492.124992 757.749984 492 758L491 758C480.250016 802.124992 440.750016 832.749984 397 832zM392 767C409.624992 771 426 760.624992 430 743L430 742L510 435L516 411L573 411L579 437L635 709L635 710C638.5 727.749984 655.250016 738.5 673 735C690.750016 731.5 701.5 714.749984 698 697L640 405L703 393L736 559L737 559C740.5 576.749984 756.250016 588.5 774 585C791.750016 581.5 802.5 564.749984 799 547L769 390L768 387L768 160C768 71.249984 696.750016 0 608 0L460 0C419.250016 0 384.250016 19.875008 355 49L137 265C124.250016 277.7499840000001 124.250016 298.2499840000001 137 311C149.750016 323.7499840000001 170.250016 323.7499840000001 183 311L265 228L320 173L320 359L319 362L257 638C253 655.624992 263.375008 673 281 677C298.624992 681 315 669.624992 319 652L376 398L439 412L382 667C381.875008 667.375008 382.124992 667.624992 382 668L368 729C364 746.624992 374.375008 763 392 767z" />
<glyph glyph-name="identification-card"
unicode="&#xF2C2;"
horiz-adv-x="1024" d="M160 704C107.375008 704 64 660.624992 64 608L64 160C64 107.375008 107.375008 64 160 64L864 64C916.624992 64 960 107.375008 960 160L960 608C960 660.624992 916.624992 704 864 704zM160 640L864 640C882.124992 640 896 626.124992 896 608L896 160C896 141.875008 882.124992 128 864 128L160 128C141.875008 128 128 141.875008 128 160L128 608C128 626.124992 141.875008 640 160 640zM352 576C281.624992 576 224 518.375008 224 448C224 412.375008 239.250016 380.2499840000001 263 357C220.375008 328.124992 192 279 192 224L256 224C256 277.375008 298.624992 320 352 320C405.375008 320 448 277.375008 448 224L512 224C512 279 483.624992 328.124992 441 357C464.750016 380.2499840000001 480 412.375008 480 448C480 518.375008 422.375008 576 352 576zM576 544L576 480L832 480L832 544zM352 512C387.750016 512 416 483.749984 416 448C416 412.249984 387.750016 384 352 384C316.250016 384 288 412.249984 288 448C288 483.749984 316.250016 512 352 512zM576 416L576 352L832 352L832 416zM576 288L576 224L736 224L736 288z" />
<glyph glyph-name="calendar-check"
unicode="&#xF274;"
horiz-adv-x="1024" d="M288 736L288 704L160 704L160 0L864 0L864 704L736 704L736 736L672 736L672 704L352 704L352 736zM224 640L288 640L288 608L352 608L352 640L672 640L672 608L736 608L736 640L800 640L800 576L224 576zM224 512L800 512L800 64L224 64zM649 439L480 270L407 343L361 297L457 201L480 179L503 201L695 393z" />
<glyph glyph-name="moon"
unicode="&#xF186;"
horiz-adv-x="1024" d="M160 736L160 672L96 672L96 608L160 608L160 544L224 544L224 608L288 608L288 672L224 672L224 736zM649 642L596 640C425 633.875008 288 493.5 288 321C288 144.624992 431.624992 1 608 1C780.5 1 920.875008 138 927 309L929 361L882 339C857 327.124992 829.250016 321 800 321C693.624992 321 608 406.624992 608 513C608 542.249984 615.124992 569 627 594zM556 566C552 548.249984 544 532 544 513C544 372 659 257 800 257C819.375008 257 835.875008 264.875008 854 269C829.624992 153 731.5 65 608 65C466.250016 65 352 179.2499840000001 352 321C352 444.249984 440.250016 541.375008 556 566z" />
<glyph glyph-name="calendar"
unicode="&#xF133;"
horiz-adv-x="1024" d="M288 768L288 736L160 736L160 32L864 32L864 736L736 736L736 768L672 768L672 736L352 736L352 768zM224 672L288 672L288 640L352 640L352 672L672 672L672 640L736 640L736 672L800 672L800 608L224 608zM224 544L800 544L800 96L224 96zM416 480L416 416L480 416L480 480zM544 480L544 416L608 416L608 480zM672 480L672 416L736 416L736 480zM288 352L288 288L352 288L352 352zM416 352L416 288L480 288L480 352zM544 352L544 288L608 288L608 352zM672 352L672 288L736 288L736 352zM288 224L288 160L352 160L352 224zM416 224L416 160L480 160L480 224zM544 224L544 160L608 160L608 224z" />
<glyph glyph-name="file"
unicode="&#xF15B;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 589L823 599L631 791L621 800zM256 736L576 736L576 544L768 544L768 32L256 32zM640 690L722 608L640 608z" />
<glyph glyph-name="clock"
unicode="&#xF017;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM480 640L480 352L704 352L704 416L544 416L544 640z" />
<glyph glyph-name="sun"
unicode="&#xF185;"
horiz-adv-x="1024" d="M480 800L480 640L544 640L544 800zM240 701L195 656L308 542L354 588zM784 701L670 588L716 542L829 656zM512 608C388.624992 608 288 507.375008 288 384C288 260.624992 388.624992 160 512 160C635.375008 160 736 260.624992 736 384C736 507.375008 635.375008 608 512 608zM512 544C600.750016 544 672 472.749984 672 384C672 295.2499840000001 600.750016 224 512 224C423.250016 224 352 295.2499840000001 352 384C352 472.749984 423.250016 544 512 544zM96 416L96 352L256 352L256 416zM768 416L768 352L928 352L928 416zM308 226L195 112L240 67L354 180zM716 226L670 180L784 67L829 112zM480 128L480 -32L544 -32L544 128z" />
<glyph glyph-name="hand-pointing-up"
unicode="&#xF0A6;"
horiz-adv-x="1024" d="M416 832C363.375008 832 320 788.624992 320 736L320 392L296 398C291 404.5 288.875008 408.624992 276 418C255.5 433 223.750016 448 181 448C135.375008 448 96 406.749984 96 355L96 342L105 332L320 115L320 -64L832 -64L832 475C832 520.124992 800.124992 559.5 756 569L512 622L512 736C512 788.624992 468.624992 832 416 832zM416 768C434.124992 768 448 754.124992 448 736L448 570L473 564L743 507C758 503.749984 768 490.249984 768 475L768 160L365 160L163 365C165.624992 378.5 171.124992 384 181 384C209.875008 384 227.750016 375.2499840000001 239 367C250.250016 358.7499840000001 253 353 253 353L259 342L272 338L344 319L384 309L384 736C384 754.124992 397.875008 768 416 768zM384 96L768 96L768 0L384 0z" />
<glyph glyph-name="user-circle"
unicode="&#xF2BD;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM512 640C424 640 352 568 352 480C352 431.5 374.624992 388.375008 409 359C337 321.624992 288 246.375008 288 160L352 160C352 248.7499840000001 423.250016 320 512 320C600.750016 320 672 248.7499840000001 672 160L736 160C736 246.375008 687 321.624992 615 359C649.375008 388.375008 672 431.5 672 480C672 568 600 640 512 640zM512 576C565.375008 576 608 533.375008 608 480C608 426.624992 565.375008 384 512 384C458.624992 384 416 426.624992 416 480C416 533.375008 458.624992 576 512 576z" />
<glyph glyph-name="envelope-open"
unicode="&#xF2B6;"
horiz-adv-x="1024" d="M512 800L495 789L111 539L96 529L96 -32L928 -32L928 529L913 539L529 789zM512 724L838 512L512 301L186 512zM160 453L495 236L512 225L529 236L864 453L864 32L160 32z" />
<glyph glyph-name="edit"
unicode="&#xF044;"
horiz-adv-x="1024" d="M800 767C775.5 767 751.5 757.5 733 739L416 423L409 416L407 406L385 294L375 247L422 257L534 279L544 281L551 288L867 605C903.875008 641.875008 903.875008 702.124992 867 739C848.5 757.5 824.5 767 800 767zM800 705C807.5 705 814.875008 701.124992 822 694C836.250016 679.749984 836.250016 664.249984 822 650L512 340L457 329L468 384L778 694C785.124992 701.124992 792.5 705 800 705zM128 640L128 0L768 0L768 422L704 358L704 64L192 64L192 576L486 576L550 640z" />
<glyph glyph-name="circle"
unicode="&#xF111;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704z" />
<glyph glyph-name="word-file"
unicode="&#xF1C2;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 800zM256 736L768 736L768 32L256 32zM576 512L576 304C576 297.124992 566.875008 288 560 288C558.624992 288 560.624992 286.5 558 290C555.375008 293.5 551.375008 302.375008 549 312C544.250016 331.124992 544 352 544 352L544 416L480 416L480 272C480 265.124992 470.875008 256 464 256C457.124992 256 448 265.124992 448 272L448 480L320 480L320 416L384 416L384 272C384 227.7499840000001 419.750016 192 464 192C495.5 192 518 212.624992 531 239C540.624992 233 547.624992 224 560 224C604.250016 224 640 259.7499840000001 640 304L640 448L704 448L704 512z" />
<glyph glyph-name="alternate-file"
unicode="&#xF15C;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 589L823 599L631 791L621 800zM256 736L576 736L576 544L768 544L768 32L256 32zM640 690L722 608L640 608zM352 480L352 416L672 416L672 480zM352 352L352 288L672 288L672 352zM352 224L352 160L672 160L672 224z" />
<glyph glyph-name="flushed-face"
unicode="&#xF579;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 640C270.944 640 192 561.056 192 464C192 366.944 270.944 288 368 288C427.54 288 480.13632 317.812992 512 363.1874880000001C543.86368 317.812992 596.46 288 656 288C753.056 288 832 366.944 832 464C832 561.056 753.056 640 656 640C596.46 640 543.86368 610.1870144 512 564.812512C480.13632 610.1870144 427.54 640 368 640zM368 576C429.76 576 480 525.76 480 464C480 402.24 429.76 352 368 352C306.24 352 256 402.24 256 464C256 525.76 306.24 576 368 576zM656 576C717.76 576 768 525.76 768 464C768 402.24 717.76 352 656 352C594.24 352 544 402.24 544 464C544 525.76 594.24 576 656 576zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512zM384 224L384 160L640 160L640 224L384 224z" />
<glyph glyph-name="life-ring"
unicode="&#xF1CD;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C522.750016 704 533.5 703 544 702L544 605C533.5 606.5 522.875008 608 512 608C501.124992 608 490.5 606.5 480 605L480 702C490.5 703 501.250016 704 512 704zM416 690L416 586C369.5 563.749984 332.124992 526.5 310 480L207 480C238.124992 580 316.250016 658.749984 416 690zM608 690C707.624992 658.749984 786.750016 579.624992 818 480L714 480C691.875008 526.5 654.5 563.875008 608 586zM512 544C600.750016 544 672 472.749984 672 384C672 295.2499840000001 600.750016 224 512 224C423.250016 224 352 295.2499840000001 352 384C352 472.749984 423.250016 544 512 544zM194 416L290 416C288.5 405.624992 288 394.749984 288 384C288 373.124992 289.5 362.5 291 352L194 352C193 362.5 192 373.2499840000001 192 384C192 394.749984 193 405.5 194 416zM733 416L830 416C831 405.5 832 394.749984 832 384C832 373.2499840000001 831 362.5 830 352L733 352C734.5 362.5 736 373.124992 736 384C736 394.875008 734.5 405.5 733 416zM206 288L310 288C332.124992 241.5 369.5 204.124992 416 182L416 78C316.375008 109.249984 237.250016 188.375008 206 288zM714 288L818 288C786.750016 188.375008 707.624992 109.249984 608 78L608 182C654.5 204.124992 691.875008 241.5 714 288zM480 163C490.5 161.5 501.124992 160 512 160C522.875008 160 533.5 161.5 544 163L544 66C533.5 65 522.750016 64 512 64C501.250016 64 490.5 65 480 66z" />
<glyph glyph-name="caret-square-right"
unicode="&#xF152;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM455 599L409 553L578 384L409 215L455 169L647 361L669 384L647 407z" />
<glyph glyph-name="beaming-face-with-smiling-eyes"
unicode="&#xF5B8;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM352 512C284.992 512 236.5 473.375008 236.5 473.375008L275.5 422.624992C275.5 422.624992 310.1744992 448 352.062496 448C393.950496 448 428.562496 422.624992 428.562496 422.624992L467.562496 473.375008C467.498496 473.375008 419.008 512 352 512zM672 512C604.992 512 556.5 473.375008 556.5 473.375008L595.5 422.624992C595.5 422.624992 630.174496 448 672.062496 448C713.950496 448 748.562496 422.624992 748.562496 422.624992L787.562496 473.375008C787.498496 473.375008 739.008 512 672 512zM345.937504 288L290.8750016 256C296.4350016 246.4400000000001 302.56 237.290624 309.25 228.562496C315.94 219.8343680000001 323.217504 211.542496 330.937504 203.750016C346.377504 188.164992 363.790016 174.532512 382.750016 163.312512C392.230016 157.702496 402.04 152.675616 412.249984 148.312512C422.46 143.949376 433.035008 140.2624960000001 443.875008 137.2499840000001C454.715008 134.237504 465.88 131.93312 477.249984 130.375008C488.62 128.816864 500.2 128 512 128C523.8 128 535.38 128.816864 546.750016 130.375008C603.6 138.165632 654.462496 164.7874880000001 693.062496 203.750016C700.782496 211.542496 708.06 219.8343680000001 714.750016 228.562496C721.44 237.290624 727.564992 246.4400000000001 733.124992 256L678.062496 288C648.942496 237.88 598.270624 201.944384 538.249984 193.750016C529.675616 192.57936 520.92 192 512 192C503.08 192 494.324384 192.57936 485.750016 193.750016C477.175616 194.9206400000001 468.782496 196.6750080000001 460.624992 198.937504C444.310016 203.462496 428.907488 210.0800000000001 414.687488 218.5C400.467488 226.92 387.430016 237.127488 375.875008 248.812512C370.097504 254.655008 364.691872 260.8968640000001 359.687488 267.437504C354.683136 273.978112 350.097504 280.8400000000001 345.937504 288z" />
<glyph glyph-name="square"
unicode="&#xF0C8;"
horiz-adv-x="1024" d="M192 704L192 64L832 64L832 704zM256 640L768 640L768 128L256 128z" />
<glyph glyph-name="question-circle"
unicode="&#xF059;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM512 576C441.624992 576 384 518.375008 384 448L448 448C448 483.749984 476.250016 512 512 512C547.750016 512 576 483.749984 576 448C576 423.5 560.250016 401.749984 537 394L524 390C497.875008 381.375008 480 356.375008 480 329L480 288L544 288L544 329L557 333C606.250016 349.375008 640 396.124992 640 448C640 518.375008 582.375008 576 512 576zM480 256L480 192L544 192L544 256z" />
<glyph glyph-name="eye"
unicode="&#xF06E;"
horiz-adv-x="1024" d="M512 640C245.250016 640 40 405 40 405L21 384L40 363C40 363 227.124992 149.624992 476 130C487.875008 128.5 499.750016 128 512 128C524.250016 128 536.124992 128.5 548 130C796.875008 149.624992 984 363 984 363L1003 384L984 405C984 405 778.750016 640 512 640zM512 576C582.5 576 647.5 556.749984 704 531C724.375008 497.249984 736 458.375008 736 416C736 300.375008 649.250016 205.375008 537 193C536.375008 192.875008 535.624992 193.124992 535 193C527.375008 192.624992 519.750016 192 512 192C503.5 192 495.250016 192.5 487 193C374.750016 205.375008 288 300.375008 288 416C288 457.749984 299.250016 496.624992 319 530L318 530C375 556.249984 440.750016 576 512 576zM512 512C459 512 416 469 416 416C416 363 459 320 512 320C565 320 608 363 608 416C608 469 565 512 512 512zM232 482C227 460.5 224 438.875008 224 416C224 359.875008 240 307.375008 268 263C187.375008 309.624992 131.375008 365.2499840000001 113 384C128.375008 399.749984 171.250016 441.5 232 482zM792 482C852.750016 441.5 895.624992 399.749984 911 384C892.624992 365.2499840000001 836.624992 309.624992 756 263C784 307.375008 800 359.875008 800 416C800 438.875008 797 460.749984 792 482z" />
<glyph glyph-name="pdf-file"
unicode="&#xF1C1;"
horiz-adv-x="1024" d="M192 800L192 -32L832 -32L832 800zM256 736L768 736L768 32L256 32zM493 565C483.250016 565.124992 471.875008 561.624992 464 555C455.875008 548.124992 452.250016 539.624992 450 531C445.624992 513.749984 446.875008 495.875008 451 476C455.875008 452.749984 469.750016 424.749984 481 398C475.250016 373.624992 473.750016 352 465 327C457.5 305.5 447.875008 293.124992 439 274C418.875008 266.375008 394.875008 261.875008 379 252C361.875008 241.375008 346.875008 229.624992 338 213C329.124992 196.375008 330.124992 172.875008 342 156C347.875008 147.124992 355.624992 140.124992 366 136C376.375008 131.875008 387.624992 131.7499840000001 397 135C415.875008 141.5 429.250016 156 442 173C453.875008 188.7499840000001 462.250016 215.5 473 237C489.124992 242.375008 500.750016 249.124992 518 253C536 257 548.124992 255.124992 565 257C572.250016 248.7499840000001 578.375008 235.5 586 229C601.250016 215.7499840000001 618 205.2499840000001 638 204C658 202.7499840000001 678 215.2499840000001 689 234L690 234L690 235C694.875008 243.5 698.250016 252.7499840000001 698 263C697.750016 273.2499840000001 692.624992 284 686 291C672.875008 305 656.124992 308.624992 638 311C624 312.875008 604.5 307.875008 588 307C573.5 326.124992 559.124992 340.5 546 365C538.875008 378.2499840000001 537 389.5 531 403C535.624992 424.749984 544.750016 449 546 468C547.5 491 546.624992 510.875008 540 529C536.624992 538.124992 531.250016 547.124992 523 554C515 560.624992 504.624992 564.749984 494 565C493.624992 565 493.375008 565 493 565zM514 328C519.750016 317.875008 526.875008 311.5 533 302C524 300.375008 517.124992 302 508 300C506.5 299.624992 505.5 298.375008 504 298C505.875008 303 508.250016 306 510 311C512 316.875008 512.124992 322.124992 514 328zM632 263C642.750016 261.624992 646.624992 259.624992 648 259C647.750016 258.5 648.375008 258.624992 648 258C644 251.375008 643.624992 251.875008 641 252C638.875008 252.124992 630.750016 256.5 623 262C625.250016 261.875008 630 263.2499840000001 632 263zM408 213C406.250016 210.375008 404.750016 204.2499840000001 403 202C393.250016 189 384.250016 183 382 182C381.624992 182.5 382.624992 182 382 183L381 183C377.750016 187.624992 378.624992 185.7499840000001 381 190C383.375008 194.2499840000001 390.875008 202.875008 404 211C405 211.624992 407 212.375008 408 213z" />
<glyph glyph-name="frowning-face"
unicode="&#xF119;"
horiz-adv-x="1024" d="M512 768C300.250016 768 128 595.749984 128 384C128 172.2499840000001 300.250016 0 512 0C723.750016 0 896 172.2499840000001 896 384C896 595.749984 723.750016 768 512 768zM512 704C689.124992 704 832 561.124992 832 384C832 206.875008 689.124992 64 512 64C334.875008 64 192 206.875008 192 384C192 561.124992 334.875008 704 512 704zM368 512C341.5 512 320 490.5 320 464C320 437.5 341.5 416 368 416C394.5 416 416 437.5 416 464C416 490.5 394.5 512 368 512zM656 512C629.5 512 608 490.5 608 464C608 437.5 629.5 416 656 416C682.5 416 704 437.5 704 464C704 490.5 682.5 512 656 512zM512 320C426.624992 320 351.375008 277.2499840000001 305 213L357 176C391.875008 224.5 447.750016 256 512 256C576.250016 256 632.124992 224.5 667 176L719 213C672.624992 277.2499840000001 597.375008 320 512 320z" />
<glyph glyph-name="window-minimize"
unicode="&#xF2D1;"
horiz-adv-x="1024" d="M160 736L160 32L864 32L864 736zM224 672L800 672L800 96L224 96zM288 256L288 192L736 192L736 256z" />
<glyph glyph-name="registered-trademark"
unicode="&#xF25D;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM384 576L384 192L448 192L448 320L557 320L608 192L672 192L618 328C668 345.375008 704 392 704 448C704 518.749984 646.750016 576 576 576zM448 512L576 512C614.124992 512 640 486.124992 640 448C640 409.875008 614.124992 384 576 384L448 384z" />
<glyph glyph-name="comment-dots"
unicode="&#xF4AD;"
horiz-adv-x="1024" d="M96 736L96 160L256 160L256 -2.5L459.250016 160L928 160L928 736zM160 672L864 672L864 224L436.750016 224L320 130.624992L320 224L160 224zM320 512C284.624992 512 256 483.375008 256 448C256 412.624992 284.624992 384 320 384C355.375008 384 384 412.624992 384 448C384 483.375008 355.375008 512 320 512zM512 512C476.624992 512 448 483.375008 448 448C448 412.624992 476.624992 384 512 384C547.375008 384 576 412.624992 576 448C576 483.375008 547.375008 512 512 512zM704 512C668.624992 512 640 483.375008 640 448C640 412.624992 668.624992 384 704 384C739.375008 384 768 412.624992 768 448C768 483.375008 739.375008 512 704 512z" />
<glyph glyph-name="winking-face-with-tongue"
unicode="&#xF58B;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM576 480L576 416L736 416L736 480L576 480zM376.5 340.562496L327.437504 299.437504C355.494176 266.009728 398.443392 242.3571520000001 448 231.2499840000001L448 192C448 156.8 476.8 128 512 128C547.2 128 576 156.8 576 192L576 231.2499840000001C625.554688 242.359072 668.465984 265.98704 696.5 299.437504L647.437504 340.562496C620.301504 308.1144960000001 568.416 288 512 288C455.584 288 403.668 308.146496 376.5 340.562496z" />
<glyph glyph-name="futbol"
unicode="&#xF1E3;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C531.250016 736 549.624992 734 568 731L512 691L456 731C474.250016 733.875008 492.875008 736 512 736zM378 709L493 625L512 611L531 625L646 709C697.124992 687.749984 742.250016 655.249984 778 614L734 477L727 455L746 442L862 357C857.750016 300.624992 840.5 247.7499840000001 813 202L644 202L637 180L592 41C566.375008 35 539.5 32 512 32C483.375008 32 455.624992 35.624992 429 42L385 179L378 201L211 201C183 247 166.375008 300.124992 162 357L277 441L296 454L289 476L244 612C280 654.249984 325.875008 687.5 378 709zM512 573L493 559L347 453L329 439L336 417L392 246L399 224L625 224L632 246L688 417L695 439L677 453L531 559zM824 547C841.5 513.5 853 476.749984 859 438L802 479zM199 545L221 479L165 438C170.875008 476 182 512 199 545zM512 493L620 415L579 288L445 288L404 415zM691 138L763 138C735.750016 109.875008 704.375008 86.624992 669 69zM261 137L331 137L353 70C318.750016 87.375008 287.624992 110 261 137z" />
<glyph glyph-name="clone"
unicode="&#xF24D;"
horiz-adv-x="1024" d="M160 736L160 704L160 224L160 192L192 192L288 192L288 256L224 256L224 672L640 672L640 608L704 608L704 704L704 736L672 736L192 736L160 736zM320 576L320 544L320 64L320 32L352 32L832 32L864 32L864 64L864 544L864 576L832 576L352 576L320 576zM384 512L800 512L800 96L384 96L384 512z" />
<glyph glyph-name="share-square"
unicode="&#xF14D;"
horiz-adv-x="1024" d="M749.249984 754L704 708.75L837.375008 576L528 576C430.976 576 352 497.056 352 400C352 302.944 430.976 224 528 224L544 224L544 288L528 288C466.24 288 416 338.24 416 400C416 461.76 466.24 512 528 512L837.5 512L704.249984 378.750016L749.5 333.5L960 544L749.249984 754zM160 736L160 32L864 32L864 352L800 288L800 96L224 96L224 672L571.312512 672L635.312512 736L160 736z" />
<glyph glyph-name="copyright"
unicode="&#xF1F9;"
horiz-adv-x="1024" d="M512 800C282.624992 800 96 613.375008 96 384C96 154.624992 282.624992 -32 512 -32C741.375008 -32 928 154.624992 928 384C928 613.375008 741.375008 800 512 800zM512 736C706.750016 736 864 578.749984 864 384C864 189.2499840000001 706.750016 32 512 32C317.250016 32 160 189.2499840000001 160 384C160 578.749984 317.250016 736 512 736zM509 576C402.624992 576 317 490.375008 317 384C317 277.624992 402.624992 192 509 192C585.750016 192 651.375008 238 682 303L624 330C603.375008 286.124992 560.250016 256 509 256C436.124992 256 381 311.124992 381 384C381 456.875008 436.124992 512 509 512C560.250016 512 603.375008 481.875008 624 438L682 465C651.375008 530 585.750016 576 509 576z" />
<glyph glyph-name="bell"
unicode="&#xF0F3;"
horiz-adv-x="1024" d="M512 800C476.624992 800 448 771.375008 448 736C448 733.249984 448.624992 730.624992 449 728C338.375008 699.749984 256 599.249984 256 480L256 192C256 173.875008 242.124992 160 224 160L192 160L192 96L422 96C418.375008 85.875008 416 75.249984 416 64C416 11.375008 459.375008 -32 512 -32C564.624992 -32 608 11.375008 608 64C608 75.249984 605.624992 85.875008 602 96L832 96L832 160L800 160C781.875008 160 768 173.875008 768 192L768 471C768 591.249984 687.624992 698.5 575 728C575.375008 730.624992 576 733.249984 576 736C576 771.375008 547.375008 800 512 800zM498 672C502.624992 672.375008 507.250016 672 512 672C514 672 516 672 518 672C622.5 668.875008 704 577.249984 704 471L704 192C704 180.7499840000001 706.375008 170.124992 710 160L314 160C317.624992 170.124992 320 180.7499840000001 320 192L320 480C320 581.624992 398.250016 664.749984 498 672zM512 96C530 96 544 82 544 64C544 46 530 32 512 32C494 32 480 46 480 64C480 82 494 96 512 96z" />
<glyph glyph-name="paper--hand-"
unicode="&#xF256;"
horiz-adv-x="1024" d="M512 832C467.750016 832 431 801 420 760C408.750016 764.624992 396.875008 768 384 768C331.375008 768 288 724.624992 288 672L288 328L260 356C222.750016 393.249984 161.250016 393.249984 124 356C86.750016 318.7499840000001 86.750016 257.2499840000001 124 220L341 3C378.624992 -34.5 430.624992 -64 492 -64L640 -64C763.375008 -64 864 36.624992 864 160L864 544C864 596.624992 820.624992 640 768 640C756.750016 640 746.124992 637.624992 736 634L736 672C736 724.624992 692.624992 768 640 768C627.124992 768 615.250016 764.624992 604 760C593 801 556.250016 832 512 832zM512 768C530.124992 768 544 754.124992 544 736L544 416L608 416L608 672C608 690.124992 621.875008 704 640 704C658.124992 704 672 690.124992 672 672L672 416L736 416L736 544C736 562.124992 749.875008 576 768 576C786.124992 576 800 562.124992 800 544L800 160C800 71.249984 728.750016 0 640 0L492 0C451.250016 0 416.250016 19.875008 387 49L169 265C156.250016 277.7499840000001 156.250016 298.2499840000001 169 311C181.750016 323.7499840000001 202.250016 323.7499840000001 215 311L297 228L352 173L352 672C352 690.124992 365.875008 704 384 704C402.124992 704 416 690.124992 416 672L416 416L480 416L480 736C480 754.124992 493.875008 768 512 768z" />
<glyph glyph-name="scissors--hand-"
unicode="&#xF257;"
horiz-adv-x="1024" d="M365 673C338.5 673.5 316.5 668 303 664C302.624992 664 302.375008 664 302 664L222 639C128.124992 610.124992 64 523.124992 64 425L64 256C64 132.624992 164.624992 32 288 32L605 32C644 31.124992 681.750016 54 697 92C706.624992 116.124992 705.250016 141.5 696 164C715.624992 174.375008 732.250016 191.124992 741 213C751 238.124992 749.375008 264.875008 739 288L864 288C916.624992 288 960 331.375008 960 384C960 436.624992 916.624992 480 864 480L815 480L828 484C878.375008 499.249984 907.250016 553.624992 892 604C876.750016 654.375008 822.375008 683.249984 772 668L502 585C499.624992 591 496.875008 597.5 493 604C477 630.749984 444.624992 661 393 670C383.375008 671.624992 373.875008 672.875008 365 673zM804 608C816.5 606.624992 827.124992 598 831 585C836.250016 567.749984 826.250016 550.249984 809 545L590 479L601 443L601 416L864 416C882.124992 416 896 402.124992 896 384C896 365.875008 882.124992 352 864 352L653 352L569 385C556.875008 389.875008 544.375008 392.249984 532 392L515 523L791 607C795.375008 608.375008 799.875008 608.5 804 608zM364 607C370.124992 607 376.5 607 382 606C416.375008 600 429.624992 585 438 571C446.375008 557 447 547 447 547C447 546.375008 447 545.624992 447 545L474 339C474.250016 338 474.624992 337 475 336C479 319.124992 468.875008 303 452 299C441.624992 296.5 436.375008 297.875008 431 301C425.624992 304.124992 419 310.7499840000001 414 325L374 480C371.5 489.5 364.750016 497.249984 355.875008 501.124992C346.875008 505 336.624992 504.624992 328 500L275 471C259.5 462.5 253.875008 443 262.5 427.5C271 412 290.5 406.375008 306 415L322 424L353 306C353.250016 305 353.624992 304 354 303C362.624992 278.2499840000001 377.375008 258.375008 398 246C414.875008 235.875008 434.5 232 454 234C443.875008 224.624992 435.5 212.624992 430 199C415.375008 162.375008 424.624992 122.124992 451 96L288 96C199.250016 96 128 167.2499840000001 128 256L128 425C128 495.375008 173.750016 557.249984 241 578L321 602C327.5 604 345.750016 607 364 607zM538 327C540.375008 326.624992 542.624992 327 545 326L664 278C680.750016 271.2499840000001 688.750016 253.7499840000001 682 237C675.250016 220.2499840000001 656.750016 212.2499840000001 640 219L521 266C519.250016 266.624992 518.5 268.124992 517 269C529.624992 285.2499840000001 537.750016 305.7499840000001 538 327zM518 195C522.250016 195 526.750016 194.624992 531 193L574 176L616 159L620 158C636.750016 151.2499840000001 644.750016 132.7499840000001 638 116C633.124992 103.749984 622.250016 96.624992 610 96C609.624992 96 609.375008 96 609 96C604.750016 95.875008 600.250016 96.375008 596 98L507 134C490.250016 140.7499840000001 482.250016 159.2499840000001 489 176C492.375008 184.375008 498.5 189.7499840000001 506 193C509.750016 194.624992 513.750016 195 518 195z" />
<glyph glyph-name="bookmark"
unicode="&#xF02E;"
horiz-adv-x="1024" d="M224 736L224 0L275 38L512 216L749 38L800 0L800 736zM288 672L736 672L736 128L531 282L512 296L493 282L288 128z" />
<glyph glyph-name="grimacing-face"
unicode="&#xF57F;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM368 512A48 48 0 0 1 368 416A48 48 0 0 1 368 512zM656 512A48 48 0 0 1 656 416A48 48 0 0 1 656 512zM384 352C331.072 352 288 308.928 288 256C288 203.072 331.072 160 384 160L640 160C692.928 160 736 203.072 736 256C736 308.928 692.928 352 640 352L384 352zM384 288L416 288L416 224L384 224C366.336 224 352 238.336 352 256C352 273.664 366.336 288 384 288zM480 288L544 288L544 224L480 224L480 288zM608 288L640 288C657.664 288 672 273.664 672 256C672 238.336 657.664 224 640 224L608 224L608 288z" />
<glyph glyph-name="check-circle"
unicode="&#xF058;"
horiz-adv-x="1024" d="M512 800C281.624992 800 96 614.375008 96 384C96 153.624992 281.624992 -32 512 -32C742.375008 -32 928 153.624992 928 384C928 428.749984 922 473.375008 906 515L854 464C860.375008 438.375008 864 412.749984 864 384C864 188.7499840000001 707.250016 32 512 32C316.750016 32 160 188.7499840000001 160 384C160 579.249984 316.750016 736 512 736C608 736 694.250016 697.749984 755 637L800 682C726.375008 755.624992 624 800 512 800zM873 663L512 302L375 439L329 393L489 233L512 211L535 233L919 617z" />
<glyph glyph-name="alternate-calendar"
unicode="&#xF073;"
horiz-adv-x="1024" d="M288 768L288 736L160 736L160 32L864 32L864 736L736 736L736 768L672 768L672 736L352 736L352 768zM224 672L288 672L288 640L352 640L352 672L672 672L672 640L736 640L736 672L800 672L800 608L224 608zM224 544L800 544L800 96L224 96zM416 480L416 416L480 416L480 480zM544 480L544 416L608 416L608 480zM672 480L672 416L736 416L736 480zM288 352L288 288L352 288L352 352zM416 352L416 288L480 288L480 352zM544 352L544 288L608 288L608 352zM672 352L672 288L736 288L736 352zM288 224L288 160L352 160L352 224zM416 224L416 160L480 160L480 224zM544 224L544 160L608 160L608 224z" />
<glyph glyph-name="handshake"
unicode="&#xF2B5;"
horiz-adv-x="1024" d="M618 673C600.250016 672.875008 582.624992 672 564 667C545.375008 662 526.750016 654 507 641C482.5 655.375008 458.750016 667.249984 436 670C407.750016 673.5 381.5 671.624992 351 671C314.875008 670.249984 291.250016 651.749984 253 637C214.750016 622.249984 165.5 608 96 608L64 608L64 300L82 291L141 262L351 28L352 27C376 3.249984 408.375008 -2.624992 437 -1C465.624992 0.624992 492.5 9 514 22C561.250016 50.5 690 140 690 140L693 142L695 144C710.250016 159.124992 717.875008 177.624992 723 197L846 259L938 290L960 297L960 608L928 608C858.624992 608 809.250016 623 771 638C732.750016 653 709.5 671.249984 673 672C653.5 672.375008 635.750016 673.124992 618 673zM619 609C633.375008 609.124992 650 608.375008 671 608C674.375008 607.875008 705.124992 594.749984 748 578C784.250016 563.749984 834.624992 551.124992 896 547L896 343L822 318L820 318L818 317L721 268C715.624992 282.624992 708.250016 296.124992 697 308L695 311L559 478L539 503L514 483L428 413C399.124992 393 375.750016 398.749984 352 410C345.124992 413.249984 345.5 414.249984 340 418L486 539L488 541C531.375008 584 558 598.875008 581 605C592.5 608.124992 604.624992 608.875008 619 609zM396 608C408.250016 608.249984 418.5 608.124992 428 607C436.375008 606 445.875008 598.875008 454 596C450.750016 593 448.375008 591.249984 445 588C444.250016 587.249984 443.750016 586.749984 443 586L268 441L241 419L265 394C265 394 288.124992 369 324 352C359.875008 335 415.250016 325.375008 465 360L467 362L529 413L647 268L648 266L649 265C670.124992 244.124992 669.624992 211.2499840000001 649 190C648.750016 189.7499840000001 649.250016 189.2499840000001 649 189C648.750016 188.875008 644.5 186.375008 644 186L602 243L550 205L591 150C574.375008 138.7499840000001 566.875008 132.875008 549 121L506 179L454 141L495 86C491.375008 83.749984 483.875008 78.749984 481 77C470.875008 70.875008 451.375008 64 434 63C417.624992 62.124992 405.124992 65.875008 399 71L398 72L184 309L180 314L128 340L128 547C189.124992 551 239.875008 563.124992 276 577C318.750016 593.375008 349.250016 606.875008 353 607C369.375008 607.375008 383.750016 607.749984 396 608z" />
<glyph glyph-name="grinning-face-with-sweat"
unicode="&#xF583;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 449.728 912.264 511.734496 885 567.062496C875.4 544.438496 859.675488 525.224512 840.187488 510.312512C855.355488 471.048512 864 428.544 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736C555.232 736 596.508512 727.804 634.812512 713.5C643.132512 732.444 652.731008 751.922 662.875008 771.25C616.027008 789.554 565.248 800 512 800zM752 800C752 800 672 663.2124992 672 620.8124992C672 578.4124992 707.84 544 752 544C796.16 544 832 578.4124992 832 620.8124992C832 663.2124992 752 800 752 800zM288 448L288 384L448 384L448 448L288 448zM576 448L576 384L736 384L736 448L576 448z" />
<glyph glyph-name="identification-badge"
unicode="&#xF2C1;"
horiz-adv-x="1024" d="M480 800C445 800 416 771 416 736L224 736L224 0L800 0L800 736L608 736C608 771 579 800 544 800zM480 736L544 736L544 672L480 672zM288 672L416 672L416 608L608 608L608 672L736 672L736 64L288 64zM512 544C441.624992 544 384 486.375008 384 416C384 380.375008 399.250016 348.2499840000001 423 325C380.375008 296.124992 352 247 352 192L416 192C416 245.375008 458.624992 288 512 288C565.375008 288 608 245.375008 608 192L672 192C672 247 643.624992 296.124992 601 325C624.750016 348.2499840000001 640 380.375008 640 416C640 486.375008 582.375008 544 512 544zM512 480C547.750016 480 576 451.749984 576 416C576 380.2499840000001 547.750016 352 512 352C476.250016 352 448 380.2499840000001 448 416C448 451.749984 476.250016 480 512 480z" />
<glyph glyph-name="rock--hand-"
unicode="&#xF255;"
horiz-adv-x="1024" d="M480 704C446 704 416.124992 686 399 659C385 667 369.124992 672 352 672C299.375008 672 256 628.624992 256 576L256 459L189 373C148.124992 320.124992 150.875008 244.2499840000001 195 194L271 108C313.5 59.624992 374.624992 32 439 32L608 32C731.375008 32 832 132.624992 832 256L832 544C832 596.624992 788.624992 640 736 640C718.875008 640 703 635 689 627C671.875008 654 642 672 608 672C590.875008 672 575 667 561 659C543.875008 686 514 704 480 704zM480 640C498.124992 640 512 626.124992 512 608L512 512L576 512L576 576C576 594.124992 589.875008 608 608 608C626.124992 608 640 594.124992 640 576L640 512L704 512L704 544C704 562.124992 717.875008 576 736 576C754.124992 576 768 562.124992 768 544L768 256C768 167.2499840000001 696.750016 96 608 96L439 96C393 96 349.375008 115.375008 319 150L243 237C218.875008 264.5 217.624992 304 240 333L256 354L256 320L320 320L320 576C320 594.124992 333.875008 608 352 608C370.124992 608 384 594.124992 384 576L384 512L448 512L448 608C448 626.124992 461.875008 640 480 640z" />
<glyph glyph-name="face-with-rolling-eyes"
unicode="&#xF5A5;"
horiz-adv-x="1024" d="M512 800C282.624 800 96 613.376 96 384C96 154.624 282.624 -32 512 -32C741.376 -32 928 154.624 928 384C928 613.376 741.376 800 512 800zM512 736C706.08 736 864 578.0799999999999 864 384C864 189.92 706.08 32 512 32C317.92 32 160 189.92 160 384C160 578.0799999999999 317.92 736 512 736zM352 576C281.408 576 224 518.592 224 448C224 377.408 281.408 320 352 320C422.592 320 480 377.408 480 448C480 518.592 422.592 576 352 576zM672 576C601.408 576 544 518.592 544 448C544 377.408 601.408 320 672 320C742.592 320 800 377.408 800 448C800 518.592 742.592 576 672 576zM320.624992 503.437504A48 48 0 0 1 368 448A48 48 0 0 1 410.437504 473.750016C413.934432 465.848384 416 457.183616 416 448C416 412.704 387.296 384 352 384C316.704 384 288 412.704 288 448C288 471.836896 301.2527584 492.424512 320.624992 503.437504zM703.375008 503.437504C722.747232 492.424512 736 471.836896 736 448C736 412.704 707.296 384 672 384C636.704 384 608 412.704 608 448C608 457.183616 610.065568 465.848384 613.562496 473.750016A48 48 0 0 1 656 448A48 48 0 0 1 703.375008 503.437504zM384 256L384 192L640 192L640 256L384 256z" />
<glyph glyph-name="alternate-trash"
unicode="&#xF2ED;"
horiz-adv-x="1024" d="M480 768C463.250016 768 446.124992 762.124992 434 750C421.875008 737.875008 416 720.749984 416 704L416 672L224 672L224 608L256 608L256 96C256 43.375008 299.375008 0 352 0L736 0C788.624992 0 832 43.375008 832 96L832 608L864 608L864 672L672 672L672 704C672 720.749984 666.124992 737.875008 654 750C641.875008 762.124992 624.750016 768 608 768zM480 704L608 704L608 672L480 672zM320 608L768 608L768 96C768 78.249984 753.750016 64 736 64L352 64C334.250016 64 320 78.249984 320 96zM384 512L384 160L448 160L448 512zM512 512L512 160L576 160L576 512zM640 512L640 160L704 160L704 512z" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 111 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 902 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

@ -1,60 +0,0 @@
const appModal = $('#modalDownloadQueue');
const appModalContent = $('#modal_content');
let modalPolling = false;
function proc_notification(icon, title, text) {
Swal.fire({
title: title,
icon: icon,
text: text
})
}
function fill_download_queue() {
$.ajax({
url: '/api/v1/get/queue'
}).done((res) => {
appModalContent.html(res);
})
}
$('.settings_btn').on('click', () => {
$('#modalSettings').modal('toggle');
})
$('.queue_btn').on('click', () => {
console.log('Get Queue!');
if (modalPolling) {
clearInterval(modalPolling);
}
fill_download_queue();
modalPolling = setInterval(fill_download_queue, 12000);
appModal.modal('toggle');
})
$('#download_btn').on('click', () => {
let artist = $('#search_bar').val();
// Prevent
$('#search_bar').val('');
let icon = 'error';
let title = 'What the flip?!';
let text = 'You need to add an artist bro..';
if (artist) {
$("#loader-wrapper").fadeIn(300);
$.ajax({
url: `/api/v1/get/artist/${artist}`,
}).done(function (res) {
text = res.message;
if (res.status === 200) {
icon = 'success';
title = 'Shazam!';
}
$("#loader-wrapper").fadeOut(700);
proc_notification(icon, title, text);
});
} else {
proc_notification(icon, title, text);
}
})

Binary file not shown.

@ -1,21 +0,0 @@
// Bordered & Pulled
// -------------------------
.#{$la-css-prefix}-border {
border: solid 0.08em #eee;
border-radius: .1em;
padding: .2em .25em .15em;
}
.#{$la-css-prefix}-pull-left { float: left; }
.#{$la-css-prefix}-pull-right { float: right; }
.#{$la-css-prefix} {
&.#{$la-css-prefix}-pull-left { margin-right: .3em; }
&.#{$la-css-prefix}-pull-right { margin-left: .3em; }
}
.#{$la-css-prefix} {
&.pull-left { margin-right: .3em; }
&.pull-right { margin-left: .3em; }
}

@ -1,11 +0,0 @@
.lar,
.las,
.lab {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
}

@ -1,4 +0,0 @@
.#{$la-css-prefix}-fw {
width: 1.25em;
text-align: center;
}

File diff suppressed because it is too large Load Diff

@ -1,22 +0,0 @@
.#{$la-css-prefix}-lg {
font-size: 1.33333em;
line-height: 0.75em;
vertical-align: -.0667em;
}
.#{$la-css-prefix}-xs { font-size: 0.75em; }
.#{$la-css-prefix}-2x { font-size: 1em; }
.#{$la-css-prefix}-2x { font-size: 2em; }
.#{$la-css-prefix}-3x { font-size: 3em; }
.#{$la-css-prefix}-4x { font-size: 4em; }
.#{$la-css-prefix}-5x { font-size: 5em; }
.#{$la-css-prefix}-6x { font-size: 6em; }
.#{$la-css-prefix}-7x { font-size: 7em; }
.#{$la-css-prefix}-8x { font-size: 8em; }
.#{$la-css-prefix}-9x { font-size: 9em; }
.#{$la-css-prefix}-10x { font-size: 10em; }
.#{$la-css-prefix}-fw {
text-align: center;
width: 1.25em;
}

@ -1,19 +0,0 @@
.#{$la-css-prefix}-ul {
padding-left: 0;
margin-left: $la-li-width;
list-style-type: none;
> li {
position: relative;
}
}
.#{$la-css-prefix}-li {
position: absolute;
left: -2em;
text-align: center;
width: $la-li-width;
line-height: inherit;
&.#{$la-css-prefix}-lg {
left: -$la-li-width + (4em / 14);
}
}

@ -1,32 +0,0 @@
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
@mixin sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
@mixin sr-only-focusable {
&:active,
&:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
}

@ -1,53 +0,0 @@
@font-face {
font-family: $la-font-name-lab;
font-style: normal;
font-weight: normal;
font-display: auto;
src: url('#{$la-font-path}/la-brands-400.eot');
src: url("#{$la-font-path}/la-brands-400.eot?#iefix") format("embedded-opentype"),
url("#{$la-font-path}/la-brands-400.woff2") format("woff2"),
url("#{$la-font-path}/la-brands-400.woff") format("woff"),
url("#{$la-font-path}/la-brands-400.ttf") format("truetype"),
url("#{$la-font-path}/la-brands-400.svg#lineawesome") format("svg");
}
.#{$la-css-prefix-lab} {
font-family: $la-font-name-lab;
font-weight: 400;
}
@font-face {
font-family: $la-font-name-lar;
font-style: normal;
font-weight: 400;
font-display: auto;
src: url('#{$la-font-path}/la-regular-400.eot');
src: url("#{$la-font-path}/la-regular-400.eot?#iefix") format("embedded-opentype"),
url("#{$la-font-path}/la-regular-400.woff2") format("woff2"),
url("#{$la-font-path}/la-regular-400.woff") format("woff"),
url("#{$la-font-path}/la-regular-400.ttf") format("truetype"),
url("#{$la-font-path}/la-regular-400.svg#lineawesome") format("svg");
}
.#{$la-css-prefix-lar} {
font-family: $la-font-name-lar;
font-weight: 400;
}
@font-face {
font-family: $la-font-name-las;
font-style: normal;
font-weight: 900;
font-display: auto;
src: url('#{$la-font-path}/la-solid-900.eot');
src: url("#{$la-font-path}/la-solid-900.eot?#iefix") format("embedded-opentype"),
url("#{$la-font-path}/la-solid-900.woff2") format("woff2"),
url("#{$la-font-path}/la-solid-900.woff") format("woff"),
url("#{$la-font-path}/la-solid-900.ttf") format("truetype"),
url("#{$la-font-path}/la-solid-900.svg#lineawesome") format("svg");
}
.#{$la-css-prefix-las} {
font-family: $la-font-name-las;
font-weight: 900;
}

@ -1,101 +0,0 @@
.la-pull-left {
float: left;
}
.la-pull-right {
float: right;
}
.la.la-pull-left,
.las.la-pull-left,
.lar.la-pull-left,
.lal.la-pull-left,
.lab.la-pull-left {
margin-right: .3em;
}
.la.la-pull-right,
.las.la-pull-right,
.lar.la-pull-right,
.lal.la-pull-right,
.lab.la-pull-right {
margin-left: .3em;
}
.la-spin {
-webkit-animation: la-spin 2s infinite linear;
animation: la-spin 2s infinite linear;
}
.la-pulse {
-webkit-animation: la-spin 1s infinite steps(8);
animation: la-spin 1s infinite steps(8);
}
@-webkit-keyframes la-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes la-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.la-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.la-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
.la-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
.la-flip-horizontal {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
-webkit-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.la-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(1, -1);
transform: scale(1, -1);
}
.la-flip-both, .la-flip-horizontal.la-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(-1, -1);
transform: scale(-1, -1);
}
:root .la-rotate-90,
:root .la-rotate-180,
:root .la-rotate-270,
:root .la-flip-horizontal,
:root .la-flip-vertical,
:root .la-flip-both {
-webkit-filter: none;
filter: none;
}

@ -1,2 +0,0 @@
.sr-only { @include sr-only(); }
.sr-only-focusable { @include sr-only-focusable(); }

@ -1,28 +0,0 @@
.#{$la-css-prefix}-stack {
display: inline-block;
height: 2em;
line-height: 2em;
position: relative;
vertical-align: middle;
width: 2.5em;
}
.#{$la-css-prefix}-stack-1x,
.#{$la-css-prefix}-stack-2x {
left: 0;
position: absolute;
text-align: center;
width: 100%;
}
.#{$la-css-prefix}-stack-1x {
line-height: inherit;
}
.#{$la-css-prefix}-stack-2x {
font-size: 2em;
}
.#{$la-css-prefix}-inverse {
color: $la-inverse;
}

File diff suppressed because it is too large Load Diff

@ -1,12 +0,0 @@
@import "mixins";
@import "core";
@import "variables";
@import "path";
@import "larger";
@import "fixed-width";
@import "list";
@import "bordered_pulled";
@import "rotated-flipped";
@import "stacked";
@import "icons";
@import "screen-reader";

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 26.199219 12.832031 C 22.875 12.832031 20.910156 15.984375 20.785156 15.984375 C 20.660156 15.984375 18.707031 12.832031 15.355469 12.832031 C 12.007813 12.832031 11.050781 15.984375 10.949219 15.984375 C 10.847656 15.984375 10.078125 13.398438 7.183594 13.398438 C 4.839844 13.398438 4.511719 14.476563 4.3125 14.476563 C 4.3125 14.476563 4.488281 13.320313 4.839844 11.261719 L 10.621094 11.261719 L 10.621094 9 L 2.800781 9 L 1.480469 16.574219 L 4.011719 16.574219 C 4.011719 16.574219 4.589844 15.507813 6.125 15.507813 C 7.660156 15.507813 8.59375 16.6875 8.59375 18.019531 C 8.59375 19.347656 7.5625 20.804688 6.125 20.804688 C 4.691406 20.804688 3.859375 19.574219 3.859375 18.722656 C 2.675781 18.722656 1.039063 18.722656 1.039063 18.722656 C 1.039063 19.476563 1.644531 23 6.125 23 C 9.777344 23 10.949219 20.277344 10.949219 19.953125 C 10.949219 20.226563 12.359375 23 15.359375 23 C 18.835938 23 20.585938 19.953125 20.785156 19.953125 C 20.988281 19.953125 23.054688 23 26.203125 23 C 29.351563 23 30.964844 20.226563 30.964844 17.890625 C 30.960938 15.554688 29.527344 12.832031 26.199219 12.832031 Z M 19.105469 17.9375 L 19.101563 17.9375 C 19.082031 17.964844 17.15625 20.300781 15.371094 20.300781 C 13.578125 20.300781 13.246094 18.515625 13.246094 17.9375 C 13.246094 17.359375 13.578125 15.574219 15.371094 15.574219 C 17.15625 15.574219 19.082031 17.910156 19.101563 17.9375 Z M 28.347656 17.9375 C 28.347656 18.515625 28.015625 20.304688 26.222656 20.304688 C 24.441406 20.304688 22.515625 17.96875 22.492188 17.941406 L 22.492188 17.9375 C 22.515625 17.910156 24.441406 15.574219 26.222656 15.574219 C 28.015625 15.574219 28.347656 17.363281 28.347656 17.9375 Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save