mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
canvas: Apply large size limitations (#36445)
To prevent any potential crash/OOM issues with "canvas" element from "rogue" applications let's apply large size limitations for context canvas2d's draw target to Servo (similar approach in Firefox/Chromium - they limits width and height to 32767/65535 pixels). Fixes: #36155, #34117, #30164, #24710 -- - [x] ./mach build -d does not report any errors - [x] ./mach test-tidy does not report any errors - [x] There are tests for these changes tests/wpt/tests/html/canvas/element/canvas-host/2d.canvas.host.size.large.html tests/wpt/tests/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.html tests/wpt/tests/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.js Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
parent
bd6928f3dc
commit
6b2a755736
7 changed files with 87 additions and 53 deletions
|
@ -28,6 +28,10 @@ use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey
|
||||||
|
|
||||||
use crate::raqote_backend::Repetition;
|
use crate::raqote_backend::Repetition;
|
||||||
|
|
||||||
|
// Asserts on WR texture cache update for zero sized image with raw data.
|
||||||
|
// https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475
|
||||||
|
const MIN_WR_IMAGE_SIZE: Size2D<u64> = Size2D::new(1, 1);
|
||||||
|
|
||||||
fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Path {
|
fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Path {
|
||||||
let mut build_ref = PathBuilderRef {
|
let mut build_ref = PathBuilderRef {
|
||||||
builder: &mut builder,
|
builder: &mut builder,
|
||||||
|
@ -595,6 +599,7 @@ impl<'a> CanvasData<'a> {
|
||||||
compositor_api: CrossProcessCompositorApi,
|
compositor_api: CrossProcessCompositorApi,
|
||||||
font_context: Arc<FontContext>,
|
font_context: Arc<FontContext>,
|
||||||
) -> CanvasData<'a> {
|
) -> CanvasData<'a> {
|
||||||
|
let size = size.max(MIN_WR_IMAGE_SIZE);
|
||||||
let backend = create_backend();
|
let backend = create_backend();
|
||||||
let draw_target = backend.create_drawtarget(size);
|
let draw_target = backend.create_drawtarget(size);
|
||||||
let image_key = compositor_api.generate_image_key().unwrap();
|
let image_key = compositor_api.generate_image_key().unwrap();
|
||||||
|
@ -1402,7 +1407,9 @@ impl<'a> CanvasData<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recreate(&mut self, size: Option<Size2D<u64>>) {
|
pub fn recreate(&mut self, size: Option<Size2D<u64>>) {
|
||||||
let size = size.unwrap_or_else(|| self.drawtarget.get_size().to_u64());
|
let size = size
|
||||||
|
.unwrap_or_else(|| self.drawtarget.get_size().to_u64())
|
||||||
|
.max(MIN_WR_IMAGE_SIZE);
|
||||||
self.drawtarget = self
|
self.drawtarget = self
|
||||||
.backend
|
.backend
|
||||||
.create_drawtarget(Size2D::new(size.width, size.height));
|
.create_drawtarget(Size2D::new(size.width, size.height));
|
||||||
|
|
|
@ -152,6 +152,8 @@ pub(crate) struct CanvasState {
|
||||||
canvas_id: CanvasId,
|
canvas_id: CanvasId,
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
image_key: ImageKey,
|
image_key: ImageKey,
|
||||||
|
#[no_trace]
|
||||||
|
size: Cell<Size2D<u64>>,
|
||||||
state: DomRefCell<CanvasContextState>,
|
state: DomRefCell<CanvasContextState>,
|
||||||
origin_clean: Cell<bool>,
|
origin_clean: Cell<bool>,
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
#[ignore_malloc_size_of = "Arc"]
|
||||||
|
@ -176,6 +178,7 @@ impl CanvasState {
|
||||||
profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||||
let script_to_constellation_chan = global.script_to_constellation_chan();
|
let script_to_constellation_chan = global.script_to_constellation_chan();
|
||||||
debug!("Asking constellation to create new canvas thread.");
|
debug!("Asking constellation to create new canvas thread.");
|
||||||
|
let size = adjust_canvas_size(size);
|
||||||
script_to_constellation_chan
|
script_to_constellation_chan
|
||||||
.send(ScriptToConstellationMessage::CreateCanvasPaintThread(
|
.send(ScriptToConstellationMessage::CreateCanvasPaintThread(
|
||||||
size, sender,
|
size, sender,
|
||||||
|
@ -194,6 +197,7 @@ impl CanvasState {
|
||||||
CanvasState {
|
CanvasState {
|
||||||
ipc_renderer,
|
ipc_renderer,
|
||||||
canvas_id,
|
canvas_id,
|
||||||
|
size: Cell::new(size),
|
||||||
state: DomRefCell::new(CanvasContextState::new()),
|
state: DomRefCell::new(CanvasContextState::new()),
|
||||||
origin_clean: Cell::new(true),
|
origin_clean: Cell::new(true),
|
||||||
image_cache: global.image_cache(),
|
image_cache: global.image_cache(),
|
||||||
|
@ -221,7 +225,15 @@ impl CanvasState {
|
||||||
self.canvas_id
|
self.canvas_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_paintable(&self) -> bool {
|
||||||
|
!self.size.get().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
|
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
|
||||||
|
if !self.is_paintable() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.ipc_renderer
|
self.ipc_renderer
|
||||||
.send(CanvasMsg::Canvas2d(msg, self.get_canvas_id()))
|
.send(CanvasMsg::Canvas2d(msg, self.get_canvas_id()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -229,6 +241,10 @@ impl CanvasState {
|
||||||
|
|
||||||
/// Updates WR image and blocks on completion
|
/// Updates WR image and blocks on completion
|
||||||
pub(crate) fn update_rendering(&self) {
|
pub(crate) fn update_rendering(&self) {
|
||||||
|
if !self.is_paintable() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let (sender, receiver) = ipc::channel().unwrap();
|
let (sender, receiver) = ipc::channel().unwrap();
|
||||||
self.ipc_renderer
|
self.ipc_renderer
|
||||||
.send(CanvasMsg::Canvas2d(
|
.send(CanvasMsg::Canvas2d(
|
||||||
|
@ -239,16 +255,27 @@ impl CanvasState {
|
||||||
receiver.recv().unwrap();
|
receiver.recv().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
|
/// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
|
||||||
pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
|
pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
|
||||||
self.reset_to_initial_state();
|
self.reset_to_initial_state();
|
||||||
|
|
||||||
|
self.size.replace(adjust_canvas_size(size));
|
||||||
|
|
||||||
self.ipc_renderer
|
self.ipc_renderer
|
||||||
.send(CanvasMsg::Recreate(Some(size), self.get_canvas_id()))
|
.send(CanvasMsg::Recreate(
|
||||||
|
Some(self.size.get()),
|
||||||
|
self.get_canvas_id(),
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset(&self) {
|
pub(crate) fn reset(&self) {
|
||||||
self.reset_to_initial_state();
|
self.reset_to_initial_state();
|
||||||
|
|
||||||
|
if !self.is_paintable() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.ipc_renderer
|
self.ipc_renderer
|
||||||
.send(CanvasMsg::Recreate(None, self.get_canvas_id()))
|
.send(CanvasMsg::Recreate(None, self.get_canvas_id()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -347,7 +374,6 @@ impl CanvasState {
|
||||||
|
|
||||||
pub(crate) fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> {
|
pub(crate) fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> {
|
||||||
assert!(self.origin_is_clean());
|
assert!(self.origin_is_clean());
|
||||||
|
|
||||||
assert!(Rect::from_size(canvas_size).contains_rect(&rect));
|
assert!(Rect::from_size(canvas_size).contains_rect(&rect));
|
||||||
|
|
||||||
let (sender, receiver) = ipc::channel().unwrap();
|
let (sender, receiver) = ipc::channel().unwrap();
|
||||||
|
@ -398,18 +424,22 @@ impl CanvasState {
|
||||||
dw: Option<f64>,
|
dw: Option<f64>,
|
||||||
dh: Option<f64>,
|
dh: Option<f64>,
|
||||||
) -> ErrorResult {
|
) -> ErrorResult {
|
||||||
|
if !self.is_paintable() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let result = match image {
|
let result = match image {
|
||||||
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
|
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
|
||||||
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
||||||
if !canvas.is_valid() {
|
if canvas.get_size().is_empty() {
|
||||||
return Err(Error::InvalidState);
|
return Err(Error::InvalidState);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
|
self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
|
||||||
},
|
},
|
||||||
CanvasImageSource::OffscreenCanvas(ref canvas) => {
|
CanvasImageSource::OffscreenCanvas(ref canvas) => {
|
||||||
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
||||||
if !canvas.is_valid() {
|
if canvas.get_size().is_empty() {
|
||||||
return Err(Error::InvalidState);
|
return Err(Error::InvalidState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,11 +558,6 @@ impl CanvasState {
|
||||||
dw: Option<f64>,
|
dw: Option<f64>,
|
||||||
dh: Option<f64>,
|
dh: Option<f64>,
|
||||||
) -> ErrorResult {
|
) -> ErrorResult {
|
||||||
// 1. Check the usability of the image argument
|
|
||||||
if !canvas.is_valid() {
|
|
||||||
return Err(Error::InvalidState);
|
|
||||||
}
|
|
||||||
|
|
||||||
let canvas_size = canvas.get_size();
|
let canvas_size = canvas.get_size();
|
||||||
let dw = dw.unwrap_or(canvas_size.width as f64);
|
let dw = dw.unwrap_or(canvas_size.width as f64);
|
||||||
let dh = dh.unwrap_or(canvas_size.height as f64);
|
let dh = dh.unwrap_or(canvas_size.height as f64);
|
||||||
|
@ -1403,13 +1428,13 @@ impl CanvasState {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
ImageData::new(
|
let data = if self.is_paintable() {
|
||||||
global,
|
Some(self.get_rect(canvas_size, read_rect))
|
||||||
size.width,
|
} else {
|
||||||
size.height,
|
None
|
||||||
Some(self.get_rect(canvas_size, read_rect)),
|
};
|
||||||
can_gc,
|
|
||||||
)
|
ImageData::new(global, size.width, size.height, data, can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
|
||||||
|
@ -1445,6 +1470,10 @@ impl CanvasState {
|
||||||
dirty_width: i32,
|
dirty_width: i32,
|
||||||
dirty_height: i32,
|
dirty_height: i32,
|
||||||
) {
|
) {
|
||||||
|
if !self.is_paintable() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME(nox): There are many arithmetic operations here that can
|
// FIXME(nox): There are many arithmetic operations here that can
|
||||||
// overflow or underflow, this should probably be audited.
|
// overflow or underflow, this should probably be audited.
|
||||||
|
|
||||||
|
@ -2013,3 +2042,23 @@ where
|
||||||
style.font_family.to_css_string()
|
style.font_family.to_css_string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn adjust_canvas_size(size: Size2D<u64>) -> Size2D<u64> {
|
||||||
|
// Firefox limits width/height to 32767 pixels and Chromium to 65535 pixels,
|
||||||
|
// but slows down dramatically before it reaches that limit.
|
||||||
|
// We limit by area instead, giving us larger maximum dimensions,
|
||||||
|
// in exchange for a smaller maximum canvas size.
|
||||||
|
const MAX_CANVAS_AREA: u64 = 32768 * 8192;
|
||||||
|
// Max width/height to 65535 in CSS pixels.
|
||||||
|
const MAX_CANVAS_SIZE: u64 = 65535;
|
||||||
|
|
||||||
|
if !size.is_empty() &&
|
||||||
|
size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE))
|
||||||
|
.none() &&
|
||||||
|
size.area() < MAX_CANVAS_AREA
|
||||||
|
{
|
||||||
|
size
|
||||||
|
} else {
|
||||||
|
Size2D::zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
|
use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use euclid::default::{Point2D, Rect, Size2D};
|
use euclid::default::Size2D;
|
||||||
use profile_traits::ipc;
|
use profile_traits::ipc;
|
||||||
use script_bindings::inheritance::Castable;
|
use script_bindings::inheritance::Castable;
|
||||||
use script_layout_interface::HTMLCanvasDataSource;
|
use script_layout_interface::HTMLCanvasDataSource;
|
||||||
|
@ -74,23 +74,12 @@ impl CanvasRenderingContext2D {
|
||||||
reflect_dom_object(boxed, global, can_gc)
|
reflect_dom_object(boxed, global, can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
|
|
||||||
pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u32>) {
|
|
||||||
self.reset_to_initial_state();
|
|
||||||
self.canvas_state
|
|
||||||
.get_ipc_renderer()
|
|
||||||
.send(CanvasMsg::Recreate(
|
|
||||||
Some(size.to_u64()),
|
|
||||||
self.canvas_state.get_canvas_id(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state
|
// https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state
|
||||||
fn reset_to_initial_state(&self) {
|
fn reset_to_initial_state(&self) {
|
||||||
self.canvas_state.reset_to_initial_state();
|
self.canvas_state.reset_to_initial_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
|
||||||
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
|
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
|
||||||
self.canvas_state.set_bitmap_dimensions(size);
|
self.canvas_state.set_bitmap_dimensions(size);
|
||||||
}
|
}
|
||||||
|
@ -106,20 +95,17 @@ impl CanvasRenderingContext2D {
|
||||||
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
|
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
|
||||||
self.canvas_state.send_canvas_2d_msg(msg)
|
self.canvas_state.send_canvas_2d_msg(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_rect(&self, rect: Rect<u32>) -> Vec<u8> {
|
|
||||||
let rect = Rect::new(
|
|
||||||
Point2D::new(rect.origin.x as u64, rect.origin.y as u64),
|
|
||||||
Size2D::new(rect.size.width as u64, rect.size.height as u64),
|
|
||||||
);
|
|
||||||
self.canvas_state.get_rect(self.canvas.size(), rect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> {
|
impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> {
|
||||||
fn canvas_data_source(self) -> HTMLCanvasDataSource {
|
fn canvas_data_source(self) -> HTMLCanvasDataSource {
|
||||||
let canvas_state = &self.unsafe_get().canvas_state;
|
let canvas_state = &self.unsafe_get().canvas_state;
|
||||||
HTMLCanvasDataSource::Image(canvas_state.image_key())
|
|
||||||
|
if canvas_state.is_paintable() {
|
||||||
|
HTMLCanvasDataSource::Image(canvas_state.image_key())
|
||||||
|
} else {
|
||||||
|
HTMLCanvasDataSource::Empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,13 +125,11 @@ impl CanvasContext for CanvasRenderingContext2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&self) {
|
fn resize(&self) {
|
||||||
self.set_bitmap_dimensions(self.size().cast())
|
self.set_canvas_bitmap_dimensions(self.size().cast())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_image_data(&self) -> Option<Snapshot> {
|
fn get_image_data(&self) -> Option<Snapshot> {
|
||||||
let size = self.size();
|
if !self.canvas_state.is_paintable() {
|
||||||
|
|
||||||
if size.is_empty() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl OffscreenCanvasRenderingContext2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
|
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
|
||||||
self.context.set_bitmap_dimensions(size.cast());
|
self.context.set_canvas_bitmap_dimensions(size.cast());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
|
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[2d.canvas.host.size.large.html]
|
|
||||||
expected: CRASH
|
|
|
@ -1,2 +0,0 @@
|
||||||
[2d.canvas.host.size.large.html]
|
|
||||||
expected: CRASH
|
|
|
@ -1,2 +0,0 @@
|
||||||
[2d.canvas.host.size.large.worker.html]
|
|
||||||
expected: CRASH
|
|
Loading…
Add table
Add a link
Reference in a new issue