mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Merge 2d5818f616
into e0d96163c7
This commit is contained in:
commit
9d02df540b
6 changed files with 251 additions and 22 deletions
|
@ -973,6 +973,11 @@ impl IOCompositor {
|
||||||
warn!("Sending response to get screen size failed ({error:?}).");
|
warn!("Sending response to get screen size failed ({error:?}).");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
CompositorMsg::Viewport(webview_id, viewport_description) => {
|
||||||
|
if let Some(webview) = self.webview_renderers.get_mut(webview_id) {
|
||||||
|
webview.set_viewport_description(viewport_description);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ mod from_constellation {
|
||||||
Self::GetScreenSize(..) => target!("GetScreenSize"),
|
Self::GetScreenSize(..) => target!("GetScreenSize"),
|
||||||
Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"),
|
Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"),
|
||||||
Self::CollectMemoryReport(..) => target!("CollectMemoryReport"),
|
Self::CollectMemoryReport(..) => target!("CollectMemoryReport"),
|
||||||
|
Self::Viewport(..) => target!("Viewport"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ use std::collections::{HashMap, VecDeque};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use base::id::{PipelineId, WebViewId};
|
use base::id::{PipelineId, WebViewId};
|
||||||
|
use compositing_traits::viewport_description::{
|
||||||
|
DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, ViewportDescription,
|
||||||
|
};
|
||||||
use compositing_traits::{SendableFrameTree, WebViewTrait};
|
use compositing_traits::{SendableFrameTree, WebViewTrait};
|
||||||
use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType};
|
use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType};
|
||||||
use embedder_traits::{
|
use embedder_traits::{
|
||||||
|
@ -28,10 +31,6 @@ use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation};
|
||||||
use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer};
|
use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer};
|
||||||
use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
|
use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
|
||||||
|
|
||||||
// Default viewport constraints
|
|
||||||
const MAX_ZOOM: f32 = 8.0;
|
|
||||||
const MIN_ZOOM: f32 = 0.1;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct ScrollEvent {
|
struct ScrollEvent {
|
||||||
/// Scroll by this offset, or to Start or End
|
/// Scroll by this offset, or to Start or End
|
||||||
|
@ -44,8 +43,10 @@ struct ScrollEvent {
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum ScrollZoomEvent {
|
enum ScrollZoomEvent {
|
||||||
/// An pinch zoom event that magnifies the view by the given factor.
|
/// A pinch zoom event that magnifies the view by the given factor.
|
||||||
PinchZoom(f32),
|
PinchZoom(f32),
|
||||||
|
/// A zoom event that magnifies the view by the factor parsed from meta tag.
|
||||||
|
ViewportZoom(f32),
|
||||||
/// A scroll event that scrolls the scroll node at the given location by the
|
/// A scroll event that scrolls the scroll node at the given location by the
|
||||||
/// given amount.
|
/// given amount.
|
||||||
Scroll(ScrollEvent),
|
Scroll(ScrollEvent),
|
||||||
|
@ -58,7 +59,7 @@ pub(crate) struct ScrollResult {
|
||||||
pub offset: LayoutVector2D,
|
pub offset: LayoutVector2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) enum PinchZoomResult {
|
pub(crate) enum PinchZoomResult {
|
||||||
DidPinchZoom,
|
DidPinchZoom,
|
||||||
DidNotPinchZoom,
|
DidNotPinchZoom,
|
||||||
|
@ -89,9 +90,6 @@ pub(crate) struct WebViewRenderer {
|
||||||
pub page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
|
pub page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
|
||||||
/// "Mobile-style" zoom that does not reflow the page.
|
/// "Mobile-style" zoom that does not reflow the page.
|
||||||
viewport_zoom: PinchZoomFactor,
|
viewport_zoom: PinchZoomFactor,
|
||||||
/// Viewport zoom constraints provided by @viewport.
|
|
||||||
min_viewport_zoom: Option<PinchZoomFactor>,
|
|
||||||
max_viewport_zoom: Option<PinchZoomFactor>,
|
|
||||||
/// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled
|
/// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled
|
||||||
/// by the embedding layer.
|
/// by the embedding layer.
|
||||||
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
|
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
|
||||||
|
@ -102,6 +100,8 @@ pub(crate) struct WebViewRenderer {
|
||||||
pending_point_input_events: RefCell<VecDeque<InputEvent>>,
|
pending_point_input_events: RefCell<VecDeque<InputEvent>>,
|
||||||
/// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages.
|
/// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages.
|
||||||
pub webrender_frame_ready: Cell<bool>,
|
pub webrender_frame_ready: Cell<bool>,
|
||||||
|
/// Viewport Description
|
||||||
|
viewport_description: Option<ViewportDescription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for WebViewRenderer {
|
impl Drop for WebViewRenderer {
|
||||||
|
@ -131,13 +131,12 @@ impl WebViewRenderer {
|
||||||
global,
|
global,
|
||||||
pending_scroll_zoom_events: Default::default(),
|
pending_scroll_zoom_events: Default::default(),
|
||||||
page_zoom: Scale::new(1.0),
|
page_zoom: Scale::new(1.0),
|
||||||
viewport_zoom: PinchZoomFactor::new(1.0),
|
viewport_zoom: PinchZoomFactor::new(DEFAULT_ZOOM),
|
||||||
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
|
|
||||||
max_viewport_zoom: None,
|
|
||||||
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
|
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
|
||||||
animating: false,
|
animating: false,
|
||||||
pending_point_input_events: Default::default(),
|
pending_point_input_events: Default::default(),
|
||||||
webrender_frame_ready: Cell::default(),
|
webrender_frame_ready: Cell::default(),
|
||||||
|
viewport_description: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -809,12 +808,15 @@ impl WebViewRenderer {
|
||||||
|
|
||||||
// Batch up all scroll events into one, or else we'll do way too much painting.
|
// Batch up all scroll events into one, or else we'll do way too much painting.
|
||||||
let mut combined_scroll_event: Option<ScrollEvent> = None;
|
let mut combined_scroll_event: Option<ScrollEvent> = None;
|
||||||
let mut combined_magnification = 1.0;
|
let mut combined_magnification = self.pinch_zoom_level().get();
|
||||||
for scroll_event in self.pending_scroll_zoom_events.drain(..) {
|
for scroll_event in self.pending_scroll_zoom_events.drain(..) {
|
||||||
match scroll_event {
|
match scroll_event {
|
||||||
ScrollZoomEvent::PinchZoom(magnification) => {
|
ScrollZoomEvent::PinchZoom(magnification) => {
|
||||||
combined_magnification *= magnification
|
combined_magnification *= magnification
|
||||||
},
|
},
|
||||||
|
ScrollZoomEvent::ViewportZoom(magnification) => {
|
||||||
|
combined_magnification = magnification
|
||||||
|
},
|
||||||
ScrollZoomEvent::Scroll(scroll_event_info) => {
|
ScrollZoomEvent::Scroll(scroll_event_info) => {
|
||||||
let combined_event = match combined_scroll_event.as_mut() {
|
let combined_event = match combined_scroll_event.as_mut() {
|
||||||
None => {
|
None => {
|
||||||
|
@ -865,9 +867,7 @@ impl WebViewRenderer {
|
||||||
self.send_scroll_positions_to_layout_for_pipeline(scroll_result.pipeline_id);
|
self.send_scroll_positions_to_layout_for_pipeline(scroll_result.pipeline_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pinch_zoom_result = match self
|
let pinch_zoom_result = match self.set_pinch_zoom_level(combined_magnification) {
|
||||||
.set_pinch_zoom_level(self.pinch_zoom_level().get() * combined_magnification)
|
|
||||||
{
|
|
||||||
true => PinchZoomResult::DidPinchZoom,
|
true => PinchZoomResult::DidPinchZoom,
|
||||||
false => PinchZoomResult::DidNotPinchZoom,
|
false => PinchZoomResult::DidNotPinchZoom,
|
||||||
};
|
};
|
||||||
|
@ -941,11 +941,8 @@ impl WebViewRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_pinch_zoom_level(&mut self, mut zoom: f32) -> bool {
|
fn set_pinch_zoom_level(&mut self, mut zoom: f32) -> bool {
|
||||||
if let Some(min) = self.min_viewport_zoom {
|
if let Some(viewport) = self.viewport_description.as_ref() {
|
||||||
zoom = f32::max(min.get(), zoom);
|
zoom = viewport.clamp_zoom(zoom);
|
||||||
}
|
|
||||||
if let Some(max) = self.max_viewport_zoom {
|
|
||||||
zoom = f32::min(max.get(), zoom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_zoom = std::mem::replace(&mut self.viewport_zoom, PinchZoomFactor::new(zoom));
|
let old_zoom = std::mem::replace(&mut self.viewport_zoom, PinchZoomFactor::new(zoom));
|
||||||
|
@ -975,7 +972,12 @@ impl WebViewRenderer {
|
||||||
|
|
||||||
// TODO: Scroll to keep the center in view?
|
// TODO: Scroll to keep the center in view?
|
||||||
self.pending_scroll_zoom_events
|
self.pending_scroll_zoom_events
|
||||||
.push(ScrollZoomEvent::PinchZoom(magnification));
|
.push(ScrollZoomEvent::PinchZoom(
|
||||||
|
self.viewport_description
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.clamp_zoom(magnification),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_window_size_message(&self) {
|
fn send_window_size_message(&self) {
|
||||||
|
@ -1042,6 +1044,17 @@ impl WebViewRenderer {
|
||||||
let screen_geometry = self.webview.screen_geometry().unwrap_or_default();
|
let screen_geometry = self.webview.screen_geometry().unwrap_or_default();
|
||||||
(screen_geometry.available_size.to_f32() / self.hidpi_scale_factor).to_i32()
|
(screen_geometry.available_size.to_f32() / self.hidpi_scale_factor).to_i32()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_viewport_description(&mut self, viewport_description: ViewportDescription) {
|
||||||
|
self.pending_scroll_zoom_events
|
||||||
|
.push(ScrollZoomEvent::ViewportZoom(
|
||||||
|
self.viewport_description
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.clamp_zoom(viewport_description.initial_scale.get()),
|
||||||
|
));
|
||||||
|
self.viewport_description = Some(viewport_description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use compositing_traits::CompositorMsg;
|
||||||
|
use compositing_traits::viewport_description::ViewportDescription;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
|
@ -61,6 +65,9 @@ impl HTMLMetaElement {
|
||||||
if name == "referrer" {
|
if name == "referrer" {
|
||||||
self.apply_referrer();
|
self.apply_referrer();
|
||||||
}
|
}
|
||||||
|
if name == "viewport" {
|
||||||
|
self.parse_and_send_viewport_if_necessary();
|
||||||
|
}
|
||||||
// https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv
|
// https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv
|
||||||
} else if !self.HttpEquiv().is_empty() {
|
} else if !self.HttpEquiv().is_empty() {
|
||||||
// TODO: Implement additional http-equiv candidates
|
// TODO: Implement additional http-equiv candidates
|
||||||
|
@ -115,6 +122,29 @@ impl HTMLMetaElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://drafts.csswg.org/css-viewport/#parsing-algorithm>
|
||||||
|
fn parse_and_send_viewport_if_necessary(&self) {
|
||||||
|
// Skip processing if this isn't the top level frame
|
||||||
|
if !self.owner_window().is_top_level() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let element = self.upcast::<Element>();
|
||||||
|
let Some(content) = element.get_attribute(&ns!(), &local_name!("content")) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(viewport) = ViewportDescription::from_str(&content.value()) {
|
||||||
|
self.owner_window()
|
||||||
|
.compositor_api()
|
||||||
|
.sender()
|
||||||
|
.send(CompositorMsg::Viewport(
|
||||||
|
self.owner_window().webview_id(),
|
||||||
|
viewport,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
|
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
|
||||||
fn apply_csp_list(&self) {
|
fn apply_csp_list(&self) {
|
||||||
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
|
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
|
||||||
|
|
|
@ -23,6 +23,7 @@ use webrender_api::DocumentId;
|
||||||
|
|
||||||
pub mod display_list;
|
pub mod display_list;
|
||||||
pub mod rendering_context;
|
pub mod rendering_context;
|
||||||
|
pub mod viewport_description;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -42,6 +43,8 @@ use webrender_api::{
|
||||||
ImageKey, NativeFontHandle, PipelineId as WebRenderPipelineId,
|
ImageKey, NativeFontHandle, PipelineId as WebRenderPipelineId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::viewport_description::ViewportDescription;
|
||||||
|
|
||||||
/// Sends messages to the compositor.
|
/// Sends messages to the compositor.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CompositorProxy {
|
pub struct CompositorProxy {
|
||||||
|
@ -176,6 +179,8 @@ pub enum CompositorMsg {
|
||||||
/// Measure the current memory usage associated with the compositor.
|
/// Measure the current memory usage associated with the compositor.
|
||||||
/// The report must be sent on the provided channel once it's complete.
|
/// The report must be sent on the provided channel once it's complete.
|
||||||
CollectMemoryReport(ReportsChan),
|
CollectMemoryReport(ReportsChan),
|
||||||
|
/// A top-level frame has parsed a viewport metatag and is sending the new constraints.
|
||||||
|
Viewport(WebViewId, ViewportDescription),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for CompositorMsg {
|
impl Debug for CompositorMsg {
|
||||||
|
|
175
components/shared/compositing/viewport_description.rs
Normal file
175
components/shared/compositing/viewport_description.rs
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! This module contains helpers for Viewport
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use euclid::default::Scale;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Default viewport constraints
|
||||||
|
///
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#initial-scale>
|
||||||
|
pub const MIN_ZOOM: f32 = 0.1;
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#initial-scale>
|
||||||
|
pub const MAX_ZOOM: f32 = 10.0;
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#initial-scale>
|
||||||
|
pub const DEFAULT_ZOOM: f32 = 1.0;
|
||||||
|
|
||||||
|
/// <https://drafts.csswg.org/css-viewport/#parsing-algorithm>
|
||||||
|
const SEPARATORS: [char; 2] = [',', ';']; // Comma (0x2c) and Semicolon (0x3b)
|
||||||
|
|
||||||
|
/// A set of viewport descriptors:
|
||||||
|
///
|
||||||
|
/// <https://www.w3.org/TR/css-viewport-1/#viewport-meta>
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||||
|
pub struct ViewportDescription {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#width
|
||||||
|
// the (minimum width) size of the viewport
|
||||||
|
// TODO: width Needs to be implemented
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#width
|
||||||
|
// the (minimum height) size of the viewport
|
||||||
|
// TODO: height Needs to be implemented
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#initial-scale>
|
||||||
|
/// the zoom level when the page is first loaded
|
||||||
|
pub initial_scale: Scale<f32>,
|
||||||
|
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#minimum_scale>
|
||||||
|
/// how much zoom out is allowed on the page.
|
||||||
|
pub minimum_scale: Scale<f32>,
|
||||||
|
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#maximum_scale>
|
||||||
|
/// how much zoom in is allowed on the page
|
||||||
|
pub maximum_scale: Scale<f32>,
|
||||||
|
|
||||||
|
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#user_scalable>
|
||||||
|
/// whether zoom in and zoom out actions are allowed on the page
|
||||||
|
pub user_scalable: UserScalable,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The errors that the viewport parsing can generate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ViewportDescriptionParseError {
|
||||||
|
/// When viewport attribute string is empty
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of User Zoom values:
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||||
|
pub enum UserScalable {
|
||||||
|
/// Zoom is not allowed
|
||||||
|
No = 0,
|
||||||
|
/// Zoom is allowed
|
||||||
|
Yes = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a viewport user scalable value.
|
||||||
|
impl TryFrom<&str> for UserScalable {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"yes" => Ok(UserScalable::Yes),
|
||||||
|
"no" => Ok(UserScalable::No),
|
||||||
|
_ => match value.parse::<f32>() {
|
||||||
|
Ok(1.0) => Ok(UserScalable::Yes),
|
||||||
|
Ok(0.0) => Ok(UserScalable::No),
|
||||||
|
_ => Err("can't convert character to UserScalable"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ViewportDescription {
|
||||||
|
fn default() -> Self {
|
||||||
|
ViewportDescription {
|
||||||
|
initial_scale: Scale::new(DEFAULT_ZOOM),
|
||||||
|
minimum_scale: Scale::new(MIN_ZOOM),
|
||||||
|
maximum_scale: Scale::new(MAX_ZOOM),
|
||||||
|
user_scalable: UserScalable::Yes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewportDescription {
|
||||||
|
/// Iterates over the key-value pairs generated from meta tag and returns a ViewportDescription
|
||||||
|
fn process_viewport_key_value_pairs(pairs: HashMap<String, String>) -> ViewportDescription {
|
||||||
|
let mut description = ViewportDescription::default();
|
||||||
|
for (key, value) in &pairs {
|
||||||
|
match key.as_str() {
|
||||||
|
"initial-scale" => {
|
||||||
|
if let Some(zoom) = Self::parse_viewport_value_as_zoom(value) {
|
||||||
|
description.initial_scale = zoom;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-scale" => {
|
||||||
|
if let Some(zoom) = Self::parse_viewport_value_as_zoom(value) {
|
||||||
|
description.minimum_scale = zoom;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maximum-scale" => {
|
||||||
|
if let Some(zoom) = Self::parse_viewport_value_as_zoom(value) {
|
||||||
|
description.maximum_scale = zoom;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user-scalable" => {
|
||||||
|
if let Ok(user_zoom_allowed) = value.as_str().try_into() {
|
||||||
|
description.user_scalable = user_zoom_allowed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a viewport zoom value.
|
||||||
|
fn parse_viewport_value_as_zoom(value: &str) -> Option<Scale<f32>> {
|
||||||
|
value
|
||||||
|
.to_lowercase()
|
||||||
|
.as_str()
|
||||||
|
.parse::<f32>()
|
||||||
|
.ok()
|
||||||
|
.filter(|&n| (0.0..=10.0).contains(&n))
|
||||||
|
.map(Scale::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constrains a zoom value within the allowed scale range
|
||||||
|
pub fn clamp_zoom(&self, zoom: f32) -> f32 {
|
||||||
|
zoom.clamp(self.minimum_scale.get(), self.maximum_scale.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://drafts.csswg.org/css-viewport/#parsing-algorithm>
|
||||||
|
///
|
||||||
|
/// This implementation differs from the specified algorithm, but is equivalent because
|
||||||
|
/// 1. It uses higher-level string operations to process string instead of character-by-character iteration.
|
||||||
|
/// 2. Uses trim() operation to handle whitespace instead of explicitly handling throughout the parsing process.
|
||||||
|
impl FromStr for ViewportDescription {
|
||||||
|
type Err = ViewportDescriptionParseError;
|
||||||
|
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||||
|
if string.is_empty() {
|
||||||
|
return Err(ViewportDescriptionParseError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse key-value pairs from the content string
|
||||||
|
// 1. Split the content string using SEPARATORS
|
||||||
|
// 2. Split into key-value pair using "=" and trim whitespaces
|
||||||
|
// 3. Insert into HashMap
|
||||||
|
let parsed_values = string
|
||||||
|
.split(SEPARATORS)
|
||||||
|
.filter_map(|pair| {
|
||||||
|
let mut parts = pair.split('=').map(str::trim);
|
||||||
|
if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
|
||||||
|
Some((key.to_string(), value.to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<HashMap<String, String>>();
|
||||||
|
|
||||||
|
Ok(Self::process_viewport_key_value_pairs(parsed_values))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue