Bug 1602547 - Implement extension endpoint for print, r=webdriver-reviewers,ato

Implement /session/<session id>/moz/print endpoing in GeckoDriver, corresponding
to the proposed spec at https://github.com/w3c/webdriver/pull/1468.

Differential Revision: https://phabricator.services.mozilla.com/D56444

--HG--
extra : moz-landing-system : lando
This commit is contained in:
James Graham 2019-12-19 21:26:05 +00:00
parent 878aa4ec9b
commit ed2571ec01
2 changed files with 162 additions and 1 deletions

View File

@ -52,6 +52,11 @@ pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
"/session/{sessionId}/moz/screenshot/full",
GeckoExtensionRoute::TakeFullScreenshot,
),
(
Method::POST,
"/session/{sessionId}/moz/print",
GeckoExtensionRoute::Print,
),
];
}
@ -64,6 +69,7 @@ pub enum GeckoExtensionRoute {
InstallAddon,
UninstallAddon,
TakeFullScreenshot,
Print,
}
impl WebDriverExtensionRoute for GeckoExtensionRoute {
@ -108,6 +114,7 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute {
GeckoExtensionCommand::UninstallAddon(serde_json::from_value(body_data.clone())?)
}
TakeFullScreenshot => GeckoExtensionCommand::TakeFullScreenshot,
Print => GeckoExtensionCommand::Print(serde_json::from_value(body_data.clone())?),
};
Ok(WebDriverCommand::Extension(command))
@ -123,6 +130,7 @@ pub enum GeckoExtensionCommand {
InstallAddon(AddonInstallParameters),
UninstallAddon(AddonUninstallParameters),
TakeFullScreenshot,
Print(PrintParameters),
}
impl WebDriverExtensionCommand for GeckoExtensionCommand {
@ -136,6 +144,7 @@ impl WebDriverExtensionCommand for GeckoExtensionCommand {
XblAnonymousByAttribute(_, x) => Some(serde_json::to_value(x).unwrap()),
XblAnonymousChildren(_) => None,
TakeFullScreenshot => None,
Print(x) => Some(serde_json::to_value(x).unwrap()),
}
}
}
@ -232,6 +241,113 @@ pub struct LogOptions {
pub level: Option<logging::Level>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PrintParameters {
pub orientation: PrintOrientation,
#[serde(deserialize_with = "deserialize_to_print_scale_f64")]
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)]
#[serde(default)]
pub struct PrintPage {
#[serde(deserialize_with = "deserialize_to_positive_f64")]
pub width: f64,
#[serde(deserialize_with = "deserialize_to_positive_f64")]
pub height: f64,
}
impl Default for PrintPage {
fn default() -> Self {
PrintPage {
width: 21.59,
height: 27.94,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct PrintMargins {
#[serde(deserialize_with = "deserialize_to_positive_f64")]
pub top: f64,
#[serde(deserialize_with = "deserialize_to_positive_f64")]
pub bottom: f64,
#[serde(deserialize_with = "deserialize_to_positive_f64")]
pub left: f64,
#[serde(deserialize_with = "deserialize_to_positive_f64")]
pub right: f64,
}
impl Default for PrintMargins {
fn default() -> Self {
PrintMargins {
top: 1.0,
bottom: 1.0,
left: 1.0,
right: 1.0,
}
}
}
fn deserialize_to_positive_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
let val = f64::deserialize(deserializer)?;
if val < 0.0 {
return Err(de::Error::custom(format!("{} is negative", val)));
};
Ok(val)
}
fn deserialize_to_print_scale_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
let val = f64::deserialize(deserializer)?;
if val < 0.1 || val > 2.0 {
return Err(de::Error::custom(format!(
"{} is outside range 0.1-2",
val
)));
};
Ok(val)
}
#[cfg(test)]
mod tests {
use serde_json::json;
@ -390,4 +506,36 @@ mod tests {
assert!(serde_json::from_value::<P>(json!({"name": "foo"})).is_err());
assert!(serde_json::from_value::<P>(json!({"name": "foo", "value": null})).is_err());
}
#[test]
fn test_json_gecko_print_defaults() {
let params = PrintParameters::default();
assert_de(&params, json!({}));
}
#[test]
fn test_json_gecko_print() {
let params = PrintParameters {
orientation: PrintOrientation::Landscape,
page: PrintPage {
width: 10.0,
..Default::default()
},
margin: PrintMargins {
top: 10.0,
..Default::default()
},
scale: 1.5,
..Default::default()
};
assert_de(
&params,
json!({"orientation": "landscape", "page": {"width": 10}, "margin": {"top": 10}, "scale": 1.5}),
);
}
#[test]
fn test_json_gecko_scale_invalid() {
assert!(serde_json::from_value::<AddonInstallParameters>(json!({"scale": 3})).is_err());
}
}

View File

@ -1,7 +1,8 @@
use crate::android::{AndroidHandler};
use crate::command::{
AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters, CHROME_ELEMENT_KEY,
GeckoExtensionCommand, GeckoExtensionRoute, PrintParameters,
XblLocatorParameters, CHROME_ELEMENT_KEY,
};
use marionette_rs::common::{
Cookie as MarionetteCookie, Date as MarionetteDate, Frame as MarionetteFrame,
@ -878,6 +879,7 @@ impl MarionetteSession {
InstallAddon(_) => WebDriverResponse::Generic(resp.into_value_response(true)?),
UninstallAddon(_) => WebDriverResponse::Void,
TakeFullScreenshot => WebDriverResponse::Generic(resp.into_value_response(true)?),
Print(_) => WebDriverResponse::Generic(resp.into_value_response(true)?),
},
})
}
@ -1156,6 +1158,7 @@ impl MarionetteCommand {
Extension(ref extension) => match extension {
GetContext => (Some("Marionette:GetContext"), None),
InstallAddon(x) => (Some("Addon:Install"), Some(x.to_marionette())),
Print(x) => (Some("WebDriver:Print"), Some(x.to_marionette())),
SetContext(x) => (Some("Marionette:SetContext"), Some(x.to_marionette())),
UninstallAddon(x) => (Some("Addon:Uninstall"), Some(x.to_marionette())),
XblAnonymousByAttribute(e, x) => {
@ -1532,6 +1535,16 @@ impl ToMarionette<Map<String, Value>> for GeckoContextParameters {
}
}
impl ToMarionette<Map<String, Value>> for PrintParameters {
fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
Ok(try_opt!(
serde_json::to_value(self)?.as_object(),
ErrorStatus::UnknownError,
"Expected an object").clone()
)
}
}
impl ToMarionette<Map<String, Value>> for XblLocatorParameters {
fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
let mut value = Map::new();