Source code

Revision control

Copy as Markdown

Other Tools

/* 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 base64::prelude::BASE64_STANDARD;
use base64::Engine;
use hyper::Method;
use serde::de::{self, Deserialize, Deserializer};
use serde_json::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_STANDARD
.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());
}
}