Add origin-clean flag tracking for canvas

This commit is contained in:
David Zbarsky 2015-11-06 00:20:18 -08:00
parent c6ae32abdd
commit b8e9064fe6
15 changed files with 135 additions and 86 deletions

View file

@ -18,12 +18,14 @@ pub struct CanvasPattern {
surface_size: Size2D<i32>, surface_size: Size2D<i32>,
repeat_x: bool, repeat_x: bool,
repeat_y: bool, repeat_y: bool,
origin_clean: bool,
} }
impl CanvasPattern { impl CanvasPattern {
fn new_inherited(surface_data: Vec<u8>, fn new_inherited(surface_data: Vec<u8>,
surface_size: Size2D<i32>, surface_size: Size2D<i32>,
repeat: RepetitionStyle) repeat: RepetitionStyle,
origin_clean: bool)
-> CanvasPattern { -> CanvasPattern {
let (x, y) = match repeat { let (x, y) = match repeat {
RepetitionStyle::Repeat => (true, true), RepetitionStyle::Repeat => (true, true),
@ -38,17 +40,23 @@ impl CanvasPattern {
surface_size: surface_size, surface_size: surface_size,
repeat_x: x, repeat_x: x,
repeat_y: y, repeat_y: y,
origin_clean: origin_clean,
} }
} }
pub fn new(global: GlobalRef, pub fn new(global: GlobalRef,
surface_data: Vec<u8>, surface_data: Vec<u8>,
surface_size: Size2D<i32>, surface_size: Size2D<i32>,
repeat: RepetitionStyle) repeat: RepetitionStyle,
origin_clean: bool)
-> Root<CanvasPattern> { -> Root<CanvasPattern> {
reflect_dom_object(box CanvasPattern::new_inherited(surface_data, surface_size, repeat), reflect_dom_object(box CanvasPattern::new_inherited(surface_data, surface_size,
repeat, origin_clean),
global, global,
CanvasPatternBinding::Wrap) CanvasPatternBinding::Wrap)
} }
pub fn origin_is_clean(&self) -> bool {
self.origin_clean
}
} }
impl<'a> ToFillOrStrokeStyle for &'a CanvasPattern { impl<'a> ToFillOrStrokeStyle for &'a CanvasPattern {

View file

@ -39,6 +39,7 @@ use net_traits::image::base::PixelFormat;
use net_traits::image_cache_task::ImageResponse; use net_traits::image_cache_task::ImageResponse;
use num::{Float, ToPrimitive}; use num::{Float, ToPrimitive};
use script_traits::ScriptMsg as ConstellationMsg; use script_traits::ScriptMsg as ConstellationMsg;
use std::cell::Cell;
use std::str::FromStr; use std::str::FromStr;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use std::{cmp, fmt}; use std::{cmp, fmt};
@ -66,6 +67,7 @@ pub struct CanvasRenderingContext2D {
canvas: JS<HTMLCanvasElement>, canvas: JS<HTMLCanvasElement>,
state: DOMRefCell<CanvasContextState>, state: DOMRefCell<CanvasContextState>,
saved_states: DOMRefCell<Vec<CanvasContextState>>, saved_states: DOMRefCell<Vec<CanvasContextState>>,
origin_clean: Cell<bool>,
} }
#[must_root] #[must_root]
@ -131,6 +133,7 @@ impl CanvasRenderingContext2D {
canvas: JS::from_ref(canvas), canvas: JS::from_ref(canvas),
state: DOMRefCell::new(CanvasContextState::new()), state: DOMRefCell::new(CanvasContextState::new()),
saved_states: DOMRefCell::new(Vec::new()), saved_states: DOMRefCell::new(Vec::new()),
origin_clean: Cell::new(true),
} }
} }
@ -222,6 +225,29 @@ impl CanvasRenderingContext2D {
(source_rect, dest_rect) (source_rect, dest_rect)
} }
// https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean
fn is_origin_clean(&self,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D)
-> bool {
match image {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(canvas) => {
canvas.origin_is_clean()
}
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) =>
image.r().origin_is_clean(),
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) =>
match image.get_url() {
None => true,
Some(url) => {
// TODO(zbarsky): we should check the origin of the image against
// the entry settings object, but for now check it against the canvas' doc.
let node: &Node = &*self.canvas.upcast();
url.origin() == node.owner_doc().url().origin()
}
}
}
}
// //
// drawImage coordinates explained // drawImage coordinates explained
// //
@ -254,19 +280,20 @@ impl CanvasRenderingContext2D {
dw: Option<f64>, dw: Option<f64>,
dh: Option<f64>) dh: Option<f64>)
-> Fallible<()> { -> Fallible<()> {
match image { let result = match image {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(canvas) => HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(ref canvas) => {
self.draw_html_canvas_element(canvas.r(), self.draw_html_canvas_element(canvas.r(),
sx, sy, sw, sh, sx, sy, sw, sh,
dx, dy, dw, dh), dx, dy, dw, dh)
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { }
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(ref image) => {
let context = image.r(); let context = image.r();
let canvas = context.Canvas(); let canvas = context.Canvas();
self.draw_html_canvas_element(canvas.r(), self.draw_html_canvas_element(canvas.r(),
sx, sy, sw, sh, sx, sy, sw, sh,
dx, dy, dw, dh) dx, dy, dw, dh)
} }
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => { HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(ref image) => {
let image_element = image.r(); let image_element = image.r();
// https://html.spec.whatwg.org/multipage/#img-error // https://html.spec.whatwg.org/multipage/#img-error
// If the image argument is an HTMLImageElement object that is in the broken state, // If the image argument is an HTMLImageElement object that is in the broken state,
@ -290,8 +317,15 @@ impl CanvasRenderingContext2D {
sx, sy, sw, sh, sx, sy, sw, sh,
dx, dy, dw, dh) dx, dy, dw, dh)
} }
};
if result.is_ok() {
if !self.is_origin_clean(image) {
self.set_origin_unclean()
} }
} }
result
}
fn draw_html_canvas_element(&self, fn draw_html_canvas_element(&self,
canvas: &HTMLCanvasElement, canvas: &HTMLCanvasElement,
@ -475,6 +509,14 @@ impl CanvasRenderingContext2D {
pub fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> { pub fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> {
self.ipc_renderer.clone() self.ipc_renderer.clone()
} }
pub fn origin_is_clean(&self) -> bool {
self.origin_clean.get()
}
fn set_origin_unclean(&self) {
self.origin_clean.set(false)
}
} }
pub trait LayoutCanvasRenderingContext2DHelpers { pub trait LayoutCanvasRenderingContext2DHelpers {
@ -887,16 +929,13 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) {
match value { match value {
StringOrCanvasGradientOrCanvasPattern::eString(string) => { StringOrCanvasGradientOrCanvasPattern::eString(string) => {
match self.parse_color(&string) { if let Ok(rgba) = self.parse_color(&string) {
Ok(rgba) => {
self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba); self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba);
self.ipc_renderer self.ipc_renderer
.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetStrokeStyle( .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetStrokeStyle(
FillOrStrokeStyle::Color(rgba)))) FillOrStrokeStyle::Color(rgba))))
.unwrap(); .unwrap();
} }
_ => {}
}
}, },
StringOrCanvasGradientOrCanvasPattern::eCanvasGradient(gradient) => { StringOrCanvasGradientOrCanvasPattern::eCanvasGradient(gradient) => {
self.state.borrow_mut().stroke_style = self.state.borrow_mut().stroke_style =
@ -905,7 +944,14 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
Canvas2dMsg::SetStrokeStyle(gradient.to_fill_or_stroke_style())); Canvas2dMsg::SetStrokeStyle(gradient.to_fill_or_stroke_style()));
self.ipc_renderer.send(msg).unwrap(); self.ipc_renderer.send(msg).unwrap();
}, },
_ => {} StringOrCanvasGradientOrCanvasPattern::eCanvasPattern(pattern) => {
let msg = CanvasMsg::Canvas2d(
Canvas2dMsg::SetStrokeStyle(pattern.to_fill_or_stroke_style()));
self.ipc_renderer.send(msg).unwrap();
if !pattern.origin_is_clean() {
self.set_origin_unclean();
}
}
} }
} }
@ -943,8 +989,12 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
self.ipc_renderer.send(msg).unwrap(); self.ipc_renderer.send(msg).unwrap();
} }
StringOrCanvasGradientOrCanvasPattern::eCanvasPattern(pattern) => { StringOrCanvasGradientOrCanvasPattern::eCanvasPattern(pattern) => {
self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetFillStyle( let msg = CanvasMsg::Canvas2d(
pattern.to_fill_or_stroke_style()))).unwrap(); Canvas2dMsg::SetFillStyle(pattern.to_fill_or_stroke_style()));
self.ipc_renderer.send(msg).unwrap();
if !pattern.origin_is_clean() {
self.set_origin_unclean();
}
} }
} }
} }
@ -975,6 +1025,11 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
sw: Finite<f64>, sw: Finite<f64>,
sh: Finite<f64>) sh: Finite<f64>)
-> Fallible<Root<ImageData>> { -> Fallible<Root<ImageData>> {
if !self.origin_is_clean() {
return Err(Error::Security)
}
let mut sx = *sx; let mut sx = *sx;
let mut sy = *sy; let mut sy = *sy;
let mut sw = *sw; let mut sw = *sw;
@ -1095,38 +1150,34 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
repetition: DOMString) repetition: DOMString)
-> Fallible<Root<CanvasPattern>> { -> Fallible<Root<CanvasPattern>> {
let (image_data, image_size) = match image { let (image_data, image_size) = match image {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => { HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(ref image) => {
// https://html.spec.whatwg.org/multipage/#img-error // https://html.spec.whatwg.org/multipage/#img-error
// If the image argument is an HTMLImageElement object that is in the broken state, // If the image argument is an HTMLImageElement object that is in the broken state,
// then throw an InvalidStateError exception // then throw an InvalidStateError exception
match self.fetch_image_data(&image.r()) { try!(self.fetch_image_data(&image.r()).ok_or(Error::InvalidState))
Some((data, size)) => (data, size),
None => return Err(Error::InvalidState),
}
}, },
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(canvas) => { HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(ref canvas) => {
let _ = canvas.get_or_init_2d_context(); let _ = canvas.get_or_init_2d_context();
match canvas.fetch_all_data() { try!(canvas.fetch_all_data().ok_or(Error::InvalidState))
Some((data, size)) => (data, size),
None => return Err(Error::InvalidState),
}
}, },
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(context) => { HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(ref context) => {
let canvas = context.Canvas(); let canvas = context.Canvas();
let _ = canvas.get_or_init_2d_context(); let _ = canvas.get_or_init_2d_context();
match canvas.fetch_all_data() { try!(canvas.fetch_all_data().ok_or(Error::InvalidState))
Some((data, size)) => (data, size),
None => return Err(Error::InvalidState),
}
} }
}; };
if let Ok(rep) = RepetitionStyle::from_str(&repetition) { if let Ok(rep) = RepetitionStyle::from_str(&repetition) {
return Ok(CanvasPattern::new(self.global.root().r(), image_data, image_size, rep)); Ok(CanvasPattern::new(self.global.root().r(),
image_data,
image_size,
rep,
self.is_origin_clean(image)))
} else {
Err(Error::Syntax)
} }
return Err(Error::Syntax);
} }
// https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth

View file

@ -90,6 +90,13 @@ impl HTMLCanvasElement {
pub fn get_size(&self) -> Size2D<i32> { pub fn get_size(&self) -> Size2D<i32> {
Size2D::new(self.Width() as i32, self.Height() as i32) Size2D::new(self.Width() as i32, self.Height() as i32)
} }
pub fn origin_is_clean(&self) -> bool {
match *self.context.borrow() {
Some(CanvasContext::Context2d(ref context)) => context.origin_is_clean(),
_ => true,
}
}
} }
pub struct HTMLCanvasData { pub struct HTMLCanvasData {
@ -251,8 +258,12 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement {
_mime_type: Option<DOMString>, _mime_type: Option<DOMString>,
_arguments: Vec<HandleValue>) -> Fallible<DOMString> { _arguments: Vec<HandleValue>) -> Fallible<DOMString> {
// Step 1: Check the origin-clean flag (should be set in fillText/strokeText if let Some(CanvasContext::Context2d(ref context)) = *self.context.borrow() {
// and currently unimplemented)
// Step 1.
if !context.origin_is_clean() {
return Err(Error::Security);
}
// Step 2. // Step 2.
if self.Width() == 0 || self.Height() == 0 { if self.Width() == 0 || self.Height() == 0 {
@ -260,7 +271,6 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement {
} }
// Step 3. // Step 3.
if let Some(CanvasContext::Context2d(ref context)) = *self.context.borrow() {
let window = window_from_node(self); let window = window_from_node(self);
let image_data = try!(context.GetImageData(Finite::wrap(0f64), Finite::wrap(0f64), let image_data = try!(context.GetImageData(Finite::wrap(0f64), Finite::wrap(0f64),
Finite::wrap(self.Width() as f64), Finite::wrap(self.Width() as f64),

View file

@ -0,0 +1,5 @@
[security.dataURI.html]
type: testharness
[data: URIs do not count as different-origin, and do not taint the canvas]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.drawImage.canvas.sub.html]
type: testharness
[drawImage of unclean canvas makes the canvas origin-unclean]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.drawImage.image.sub.html]
type: testharness
[drawImage of different-origin image makes the canvas origin-unclean]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.pattern.canvas.fillStyle.sub.html]
type: testharness
[Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.pattern.canvas.strokeStyle.sub.html]
type: testharness
[Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.pattern.cross.sub.html]
type: testharness
[Using an unclean pattern makes the target canvas origin-unclean, not the pattern canvas]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.pattern.image.fillStyle.sub.html]
type: testharness
[Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.pattern.image.strokeStyle.sub.html]
type: testharness
[Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean]
expected: FAIL

View file

@ -1,5 +0,0 @@
[security.reset.sub.html]
type: testharness
[Resetting the canvas state does not reset the origin-clean flag]
expected: FAIL

View file

@ -0,0 +1,5 @@
[toDataURL.jpeg.primarycolours.html]
type: testharness
[toDataURL with JPEG handles simple colours correctly]
expected: FAIL

View file

@ -0,0 +1,5 @@
[toDataURL.png.complexcolours.html]
type: testharness
[toDataURL with PNG handles non-primary and non-solid colours correctly]
expected: FAIL

View file

@ -0,0 +1,5 @@
[toDataURL.png.primarycolours.html]
type: testharness
[toDataURL with PNG handles simple colours correctly]
expected: FAIL