Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion components/canvas/canvas_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey

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 {
let mut build_ref = PathBuilderRef {
builder: &mut builder,
Expand Down Expand Up @@ -595,6 +599,7 @@ impl<'a> CanvasData<'a> {
compositor_api: CrossProcessCompositorApi,
font_context: Arc<FontContext>,
) -> CanvasData<'a> {
let size = size.max(MIN_WR_IMAGE_SIZE);
let backend = create_backend();
let draw_target = backend.create_drawtarget(size);
let image_key = compositor_api.generate_image_key().unwrap();
Expand Down Expand Up @@ -1402,7 +1407,9 @@ impl<'a> CanvasData<'a> {
}

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
.backend
.create_drawtarget(Size2D::new(size.width, size.height));
Expand Down
87 changes: 68 additions & 19 deletions components/script/canvas_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ pub(crate) struct CanvasState {
canvas_id: CanvasId,
#[no_trace]
image_key: ImageKey,
#[no_trace]
size: Cell<Size2D<u64>>,
state: DomRefCell<CanvasContextState>,
origin_clean: Cell<bool>,
#[ignore_malloc_size_of = "Arc"]
Expand All @@ -176,6 +178,7 @@ impl CanvasState {
profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap();
let script_to_constellation_chan = global.script_to_constellation_chan();
debug!("Asking constellation to create new canvas thread.");
let size = adjust_canvas_size(size);
script_to_constellation_chan
.send(ScriptToConstellationMessage::CreateCanvasPaintThread(
size, sender,
Expand All @@ -194,6 +197,7 @@ impl CanvasState {
CanvasState {
ipc_renderer,
canvas_id,
size: Cell::new(size),
state: DomRefCell::new(CanvasContextState::new()),
origin_clean: Cell::new(true),
image_cache: global.image_cache(),
Expand Down Expand Up @@ -221,14 +225,26 @@ impl CanvasState {
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) {
if !self.is_paintable() {
return;
}

self.ipc_renderer
.send(CanvasMsg::Canvas2d(msg, self.get_canvas_id()))
.unwrap()
}

/// Updates WR image and blocks on completion
pub(crate) fn update_rendering(&self) {
if !self.is_paintable() {
return;
}

let (sender, receiver) = ipc::channel().unwrap();
self.ipc_renderer
.send(CanvasMsg::Canvas2d(
Expand All @@ -239,16 +255,27 @@ impl CanvasState {
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>) {
self.reset_to_initial_state();

self.size.replace(adjust_canvas_size(size));

self.ipc_renderer
.send(CanvasMsg::Recreate(Some(size), self.get_canvas_id()))
.send(CanvasMsg::Recreate(
Some(self.size.get()),
self.get_canvas_id(),
))
.unwrap();
}

pub(crate) fn reset(&self) {
self.reset_to_initial_state();

if !self.is_paintable() {
return;
}

self.ipc_renderer
.send(CanvasMsg::Recreate(None, self.get_canvas_id()))
.unwrap();
Expand Down Expand Up @@ -347,7 +374,6 @@ impl CanvasState {

pub(crate) fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> {
assert!(self.origin_is_clean());

assert!(Rect::from_size(canvas_size).contains_rect(&rect));

let (sender, receiver) = ipc::channel().unwrap();
Expand Down Expand Up @@ -398,18 +424,22 @@ impl CanvasState {
dw: Option<f64>,
dh: Option<f64>,
) -> ErrorResult {
if !self.is_paintable() {
return Ok(());
}

let result = match image {
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
if !canvas.is_valid() {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
return Err(Error::InvalidState);
}

self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
},
CanvasImageSource::OffscreenCanvas(ref canvas) => {
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
if !canvas.is_valid() {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
return Err(Error::InvalidState);
}

Expand Down Expand Up @@ -528,11 +558,6 @@ impl CanvasState {
dw: Option<f64>,
dh: Option<f64>,
) -> ErrorResult {
// 1. Check the usability of the image argument
if !canvas.is_valid() {
return Err(Error::InvalidState);
}

let canvas_size = canvas.get_size();
let dw = dw.unwrap_or(canvas_size.width as f64);
let dh = dh.unwrap_or(canvas_size.height as f64);
Expand Down Expand Up @@ -1403,13 +1428,13 @@ impl CanvasState {
},
};

ImageData::new(
global,
size.width,
size.height,
Some(self.get_rect(canvas_size, read_rect)),
can_gc,
)
let data = if self.is_paintable() {
Some(self.get_rect(canvas_size, read_rect))
} else {
None
};

ImageData::new(global, size.width, size.height, data, can_gc)
}

// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
Expand Down Expand Up @@ -1445,6 +1470,10 @@ impl CanvasState {
dirty_width: i32,
dirty_height: i32,
) {
if !self.is_paintable() {
return;
}

// FIXME(nox): There are many arithmetic operations here that can
// overflow or underflow, this should probably be audited.

Expand Down Expand Up @@ -2013,3 +2042,23 @@ where
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()
}
}
36 changes: 10 additions & 26 deletions components/script/dom/canvasrenderingcontext2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
use dom_struct::dom_struct;
use euclid::default::{Point2D, Rect, Size2D};
use euclid::default::Size2D;
use profile_traits::ipc;
use script_bindings::inheritance::Castable;
use script_layout_interface::HTMLCanvasDataSource;
Expand Down Expand Up @@ -74,23 +74,12 @@ impl CanvasRenderingContext2D {
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
fn reset_to_initial_state(&self) {
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>) {
self.canvas_state.set_bitmap_dimensions(size);
}
Expand All @@ -106,20 +95,17 @@ impl CanvasRenderingContext2D {
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
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> {
fn canvas_data_source(self) -> HTMLCanvasDataSource {
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
}
}
}

Expand All @@ -139,13 +125,11 @@ impl CanvasContext for CanvasRenderingContext2D {
}

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> {
let size = self.size();

if size.is_empty() {
if !self.canvas_state.is_paintable() {
return None;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl OffscreenCanvasRenderingContext2D {
}

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) {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.