canvas: Apply large size limitations#36445
Conversation
|
cc @xiaochengh, @sagudev < Please review. |
69069ab to
c3d8cdb
Compare
c3d8cdb to
50dd0c8
Compare
|
@xiaochengh < I decided to simplify PR and remove the "lazy allocation of canvas draw target" functionality |
xiaochengh
left a comment
There was a problem hiding this comment.
Thanks for splitting it up and making it easier to review!
| _ => false, | ||
| }; | ||
|
|
||
| (paintable && !size.is_empty()) || is_supported_canvas_size(size) |
There was a problem hiding this comment.
Could you explain why OffScreenCanvas needs to check more than paintable?
There was a problem hiding this comment.
You compared with outdated HTMLCanvasElement.rs (in the latest version they have similar check)
https://github.com/servo/servo/pull/36445/files#diff-7483a0f0ae501ab9da1432c28321c22f39647521072498f773903f360b026ce3R388
There was a problem hiding this comment.
In an early message you mentioned that WebGL needs this check as a fixup, in which case I believe it should be moved to WebGL related types.
If it's general, then could you elaborate the difference between the paintability of a DrawTarget and that of a canvas (be it HTMLCanvas or offscreen)?
There was a problem hiding this comment.
HTMLCanvasElement "paintable" (top level)
- ContextCanvas2D DrawTarget "paintable" (low level)
- WebGL/WebGPU DrawingBuffer "paintable" (low level)
There are some places in code there we could/must check possibility to snapshot image data OR draw from other canvas and current proposal was to up checking of "paintability" to top level instead of waiting until it will be done at context level (but it will be more CPU resourceble because we can do validation twice, but could help on some edge cases).
NOTE:
"Paintable" on low level (per context) will be validated against draw target size limitations + context lost status (for GPU accelerated rendering - not yet supported on 100%)
Hm, do we really need |
50dd0c8 to
af0e53a
Compare
Your change with snapshot concept is a bit large, so need time to check it well, but at first view "paintable" concept is more top-level compare with "snapshot". Instead of doing "paintable" validation inside each context method implementation we will do it inside HTMLCanvasElement. "Paintable" concept will allow to do:
Anyway I will check your PR and return back with more feedback. |
The problem here is that snapshot::cleared will do allocation, so in case of too big canvas we would still get OOM. |
How about to replace it to this "is_paintable" check (included validation of max canvas size) to avoid OOM? |
That, would work, but that's included in sagudev@1f34641 (which I pushed just now, but already linked before). Actually rather than putting spotlight on my PR I just wanted to put it on sagudev@1f34641, which does similar as paintable but for each context in get_image_data. I think this is important for two reasons:
So I am questioning reasoning for this:
Is there any reason why we cannot do this in each context and I am missing it? |
@sagudev < Sorry for long reply... I viewed your "snapshot" PR and "yes" after your unification of "get_image_data" my proposed "paintable" concept There are one possible (but not mandatory) consumer of it is "CanvasState::draw_image_internal" for HTMLCanvasElement/OffscreenCanvas, but it could also be done per context level right now (with software image drawing). So I can propose to cut-off "HTMLCanvasElement::is_paintable" change from PR and preserve only "CanvasState::is_paintable" for DrawTarget (to validate canvas size). |
Sounds good to me. BTW I created test site that allow to set custom canvas size on different context, to see how other browsers respond to edge cases (and to see what they return on |
There are some existed application for testing canvas size: |
But they do not test if they actually work or what does canvas methods return, the ones we are most interested for this PR are |
af0e53a to
c5d98ad
Compare
xiaochengh
left a comment
There was a problem hiding this comment.
LGTM.
Thanks for further splitting the PR and making it nice and clean!
mrobinson
left a comment
There was a problem hiding this comment.
Looks good, but I have a few very small suggestions:
components/script/canvas_state.rs
Outdated
| if size.is_empty() { | ||
| return false; | ||
| } | ||
| if size.width > MAX_CANVAS_SIZE || size.height > MAX_CANVAS_SIZE { | ||
| return false; | ||
| } | ||
| if size.area() > MAX_CANVAS_AREA { | ||
| return false; | ||
| } | ||
| true |
There was a problem hiding this comment.
I would just combine this one:
| if size.is_empty() { | |
| return false; | |
| } | |
| if size.width > MAX_CANVAS_SIZE || size.height > MAX_CANVAS_SIZE { | |
| return false; | |
| } | |
| if size.area() > MAX_CANVAS_AREA { | |
| return false; | |
| } | |
| true | |
| !size.is_empty() && | |
| size <= Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE) && | |
| size.area() < MAX_CANVAS_AREA |
There was a problem hiding this comment.
And size <= Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE) is not supported
(PartialOrd is not implemeted for Size2D), so I replace it to
size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE)).none()
components/script/canvas_state.rs
Outdated
| } | ||
| } | ||
|
|
||
| fn get_paintable_size(&self) -> Size2D<u64> { |
There was a problem hiding this comment.
Getters in Rust usually don't use get_: https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter:
| fn get_paintable_size(&self) -> Size2D<u64> { | |
| fn paintable_size(&self) -> Size2D<u64> { |
There was a problem hiding this comment.
I know about Rust naming policy, but I was confused by existed getter methods names (like get_ipc_renderer, get_canvas_id), so I decided that in Servo are using non Rust naming policy is OK.
There was a problem hiding this comment.
Servo is very old and predates Rust naming policy, so I think general rule of thumb is that new stuff try to follow it, but existing stuff can keep the old names.
components/script/canvas_state.rs
Outdated
|
|
||
| #[derive(Clone, Debug, JSTraceable, MallocSizeOf)] | ||
| /// Helps observe states of canvas draw target. | ||
| struct DrawTarget { |
There was a problem hiding this comment.
The use of DrawTarget here is a little bit confusing, because this isn't the target of the draw itself, but metadata about it. I would call it DrawTargetSize.
| struct DrawTarget { | |
| struct DrawTargetSize { |
There was a problem hiding this comment.
Yes, it has mostly metadata in itself, but the goal is to have mirrored version (with size + state) of canvas draw target but on script thread. Later I have plan to add delayed creation of draw target for Canvas2D context (was cutoff from current version of PR) and it will add new filed "created/allocated" to this struct.
I was inspired by existed WebGPU implementation in Servo - it also have the same thing - metadata version of WebGPU drawing buffer on script thread.
https://github.com/servo/servo/blob/main/components/script/dom/webgpu/gpucanvascontext.rs#L54
Canvas2D output bitmap -> CanvasState::DrawTarget
WebGPU drawing buffer -> GPUCanvasContext:DrawingBuffer
WebGL drawing buffer -> None
Anyway if after you and/or @sagudev decide to rename it to DrawTargetSize - no problem.
There was a problem hiding this comment.
Okay. That plan sounds interesting. For now let's give it a name that matches what it does. It's no problem to rename it later.
There was a problem hiding this comment.
I was inspired by existed WebGPU implementation in Servo - it also have the same thing - metadata version of WebGPU drawing buffer on script thread.
The reason why we named it this way in WebGPU is because it matches the spec and it's also used referenced in spec steps (this way also out code matches spec better).
But here there are no exact steps written out in spec so either name is fine by me.
There was a problem hiding this comment.
Okay. Let's call this DrawTargetSize then. Thanks!
There was a problem hiding this comment.
@mrobinson < To do member renaming also?
draw_target -> draw_target_size?
components/script/canvas_state.rs
Outdated
| const MAX_CANVAS_SIZE: u64 = 65535; | ||
|
|
||
| #[derive(Clone, Debug, JSTraceable, MallocSizeOf)] | ||
| /// Helps observe states of canvas draw target. |
There was a problem hiding this comment.
Please put documentation above the derive line and perhaps expand to:
| /// Helps observe states of canvas draw target. | |
| /// Information about the canvas draw target size and whether the size is valid. |
components/script/canvas_state.rs
Outdated
| } | ||
| } | ||
|
|
||
| pub(crate) fn is_supported_canvas_size(size: Size2D<u64>) -> bool { |
There was a problem hiding this comment.
Does this need to be pub(crate)? I would actually make this a static method on DrawTargetSize above.
impl DrawTargetSize
...
fn is_supported_canvas_size()
...Then you can call it using Self::is_supported_canvas_size(...).
There was a problem hiding this comment.
Moved to DrawTarget struct. However may be better to move it to https://github.com/servo/servo/blob/main/components/shared/canvas/lib.rs ??
There was a problem hiding this comment.
I would keep this in the impl block of DrawTargetSize.
5e429aa to
9a251f3
Compare
components/script/canvas_state.rs
Outdated
| fn paintable_size(&self) -> Size2D<u64> { | ||
| if self.paintable { | ||
| self.size | ||
| } else { | ||
| Size2D::zero() | ||
| } | ||
| } |
There was a problem hiding this comment.
So we will use paintable size for toDataUrl,toBlobUrl and similar (hence in edge cases we will error), but when rendering we will simply return empty htmlcanvassource (transparent black image as handled by WR/Layout), right? Can we document this somewhere in code.
9a251f3 to
f4a8b32
Compare
|
@mrobinson @sagudev < After your suggestions I reviewed PR code and decide to simplify a little bit all this mess "DrawTarget/DrawTargetSize" thing a lot. So i simply replaced it with just "size" field and use it as "paintable" marker for canvas state. @sagudev < Regarding "toDataUrl,toBlobUrl" - good catch! |
| if self.canvas_state.is_paintable() { | ||
| Some( | ||
| self.canvas_state | ||
| .get_rect(self.size(), Rect::from_size(self.size())), | ||
| ) | ||
| } else { | ||
| None | ||
| } |
There was a problem hiding this comment.
Shouldn't we do this as part of get_rect?
There was a problem hiding this comment.
Fixed as CanvasState::get_rect() -> Option<Vec<u8>>
f4a8b32 to
de29e50
Compare
mrobinson
left a comment
There was a problem hiding this comment.
Looks good. Sorry for the long delay in reviewing!
de29e50 to
d26c041
Compare
|
@mrobinson @sagudev < Please review. I rebased it on top of "main" branch with "snapshot" feature. |
To prevent any potential crash/OOM issues with <canvas> element from "rogue" applications let's apply upper size limitations for rendering context's output bitmap (draw target) to Servo (similar approach in Firefox/Chromium - they limits width and height to 32767/65535 pixels). Bug: servo#36155 Signed-off-by: Andrei Volykhin <[email protected]>
d26c041 to
4f7f22f
Compare
|
FYI the mentioned issues haven't been closed as they weren't part of separate lines. GitHub only closes the first issue in such a list. |
Done. |
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
--
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