<commit_before>use super::PaletteIndex;
use super::PaletteSet;
use super::TilePattern;
use super::SCREEN_BUFFER_SIZE;
use super::SCREEN_WIDTH;
use super::SCREEN_HEIGHT;
use super::ppu_reg::PPUReg;
use super::ppu_memory::PPUMemory;
use memory::MemSegment;
use std::cmp;

const NAMETABLE_WIDTH: usize = 32;
const TILES_PER_LINE: usize = 34;

#[derive(Debug, Copy, Clone, PartialEq)]
struct TileAttribute {
    bits: u8,
}

impl TileAttribute {
    fn new(bits: u8) -> TileAttribute {
        TileAttribute { bits: bits }
    }

    fn get_palette(&self, x: u16, y: u16) -> u8 {
        let mut at = self.bits;
        if y & 0x10 != 0 {
            at >>= 4
        }
        if x & 0x10 != 0 {
            at >>= 2
        }
        at & 0x03
    }
}

impl Default for TileAttribute {
    fn default() -> TileAttribute {
        TileAttribute { bits: 0 }
    }
}

pub struct BackgroundRenderer {
    tile: Box<[[TilePattern; TILES_PER_LINE]]>,
    attr: Box<[[TileAttribute; TILES_PER_LINE]]>,

    background_buffer: Box<[PaletteIndex]>,
}

fn div_rem(num: usize, den: usize) -> (usize, usize) {
    (num / den, num % den)
}

impl BackgroundRenderer {
    pub fn render(&mut self, start_px: usize, stop_px: usize, reg: &PPUReg, mem: &mut PPUMemory) {
        for pixel in start_px..stop_px {
            let (y, x) = div_rem(pixel, SCREEN_WIDTH);
            self.visible_scanline_background(x as u16, y as u16, reg, mem);
        }
        self.draw(start_px, stop_px);
    }

    fn visible_scanline_background(&mut self, x: u16, y: u16, reg: &PPUReg, mem: &mut PPUMemory) {
        if x > 256 {
            return;
        }
        let sl = y as usize;
        let px = x as usize / 8;

        if x % 8 == 0 {
            let nametable_addr = self.get_nametable_addr(reg, x, y);
            let tile_idx = mem.read(nametable_addr);

            let tile_table = reg.ppuctrl.background_table();
            self.tile[sl][px] = mem.read_tile_pattern(tile_idx, y & 0x07, tile_table);

            let attribute_addr = self.get_attribute_addr(reg, x, y);
            self.attr[sl][px] = TileAttribute::new(mem.read(attribute_addr));
        }
    }

    fn get_nametable_addr(&self, reg: &PPUReg, px_x: u16, px_y: u16) -> u16 {
        let x = px_x / 8;
        let y = px_y / 8;
        let result = reg.ppuctrl.nametable_addr() + y * NAMETABLE_WIDTH as u16 + x;
        result
    }

    fn get_attribute_addr(&self, reg: &PPUReg, x: u16, y: u16) -> u16 {
        let x = x / 32;
        let y = y / 32;
        let attr_table = reg.ppuctrl.nametable_addr() + 0x03C0;
        attr_table + (y * 8) + x
    }

    fn draw(&mut self, start: usize, stop: usize) {
        let mut current_scanline = start / SCREEN_WIDTH;
        let mut last_scanline_boundary = current_scanline * SCREEN_WIDTH;
        let next_scanline = current_scanline + 1;
        let mut next_scanline_boundary = next_scanline * SCREEN_WIDTH;

        let mut current = start;
        while current < stop {
            let segment_start = current - last_scanline_boundary;
            let segment_end = cmp::min(next_scanline_boundary, stop) - last_scanline_boundary;

            self.draw_segment(current_scanline,
                              last_scanline_boundary,
                              next_scanline_boundary,
                              segment_start,
                              segment_end);
            current_scanline += 1;
            last_scanline_boundary = next_scanline_boundary;
            current = next_scanline_boundary;
            next_scanline_boundary += SCREEN_WIDTH;
        }
    }

    fn draw_segment(&mut self,
                    scanline: usize,
                    line_start: usize,
                    line_stop: usize,
                    start: usize,
                    stop: usize) {
        let pattern_line = &self.tile[scanline];
        let attr_line = &self.attr[scanline];
        let pixel_line = &mut self.background_buffer[line_start..line_stop];

        for pixel in start..stop {
            let tile_idx = pixel / 8;
            let pattern = pattern_line[tile_idx];
            let fine_x = pixel as u32 & 0x07;
            let color_id = pattern.get_color_in_pattern(fine_x);

            let attr = attr_line[tile_idx];
            let palette_id = attr.get_palette(pixel as u16, scanline as u16);

            pixel_line[pixel] = PaletteIndex {
                set: PaletteSet::Background,
                palette_id: palette_id,
                color_id: color_id,
            }
        }
    }

    pub fn buffer(&self) -> &[PaletteIndex] {
        &self.background_buffer
    }

    #[allow(dead_code)]
    pub fn dump_background_pixels(&self) {
        for y in 0..SCREEN_HEIGHT {
            for x in 0..SCREEN_WIDTH {
                let idx = y * SCREEN_WIDTH + x;
                let pix = self.background_buffer[idx];
                if pix.is_transparent() {
                    print!(" ");
                } else {
                    print!("{}", pix.color_id);
                }
            }
            println!("");
        }
        println!("");
    }

    #[allow(dead_code)]
    pub fn dump_background_palettes(&self) {
        for y in 0..SCREEN_HEIGHT {
            for x in 0..SCREEN_WIDTH {
                let idx = y * SCREEN_WIDTH + x;
                let pix = self.background_buffer[idx];
                if pix.is_transparent() {
                    print!(" ");
                } else {
                    print!("{}", pix.palette_id);
                }
            }
            println!("");
        }
        println!("");
    }
}

impl Default for BackgroundRenderer {
    fn default() -> BackgroundRenderer {
        // Work around the 32-element array limitation
        let (tiles, attrs) = unsafe {
            use std::ptr;
            use std::mem;

            let mut tiles: [[TilePattern; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized();
            let mut attrs: [[TileAttribute; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized();

            for element in tiles.iter_mut() {
                let tile_line: [TilePattern; TILES_PER_LINE] = [Default::default(); TILES_PER_LINE];
                ptr::write(element, tile_line);
            }
            for element in attrs.iter_mut() {
                let attr_line: [TileAttribute; TILES_PER_LINE] =
                    [Default::default(); TILES_PER_LINE];
                ptr::write(element, attr_line);
            }

            (tiles, attrs)
        };

        BackgroundRenderer {
            tile: Box::new(tiles),
            attr: Box::new(attrs),

            background_buffer: vec![Default::default(); SCREEN_BUFFER_SIZE].into_boxed_slice(),
        }
    }
}
<commit_msg>Refactor background evaluation a bit.<commit_after>use super::PaletteIndex;
use super::PaletteSet;
use super::TilePattern;
use super::SCREEN_BUFFER_SIZE;
use super::SCREEN_WIDTH;
use super::SCREEN_HEIGHT;
use super::ppu_reg::PPUReg;
use super::ppu_memory::PPUMemory;
use memory::MemSegment;
use std::cmp;

const NAMETABLE_WIDTH: usize = 32;
const TILES_PER_LINE: usize = 34;

#[derive(Debug, Copy, Clone, PartialEq)]
struct TileAttribute {
    bits: u8,
}

impl TileAttribute {
    fn new(bits: u8) -> TileAttribute {
        TileAttribute { bits: bits }
    }

    fn get_palette(&self, x: u16, y: u16) -> u8 {
        let mut at = self.bits;
        if y & 0x10 != 0 {
            at >>= 4
        }
        if x & 0x10 != 0 {
            at >>= 2
        }
        at & 0x03
    }
}

impl Default for TileAttribute {
    fn default() -> TileAttribute {
        TileAttribute { bits: 0 }
    }
}

pub struct BackgroundRenderer {
    tile: Box<[[TilePattern; TILES_PER_LINE]]>,
    attr: Box<[[TileAttribute; TILES_PER_LINE]]>,

    background_buffer: Box<[PaletteIndex]>,
}

fn get_nametable_addr(reg: &PPUReg, tile_x: u16, tile_y: u16) -> u16 {
    reg.ppuctrl.nametable_addr() + tile_y * NAMETABLE_WIDTH as u16 + tile_x
}

fn get_attribute_addr(reg: &PPUReg, tile_x: u16, tile_y: u16) -> u16 {
    let x = tile_x / 4;
    let y = tile_y / 4;
    let attr_table = reg.ppuctrl.nametable_addr() + 0x03C0;
    attr_table + (y * 8) + x
}

impl BackgroundRenderer {
    pub fn render(&mut self, start_px: usize, stop_px: usize, reg: &PPUReg, mem: &mut PPUMemory) {
        self.evaluate(reg, mem, start_px, stop_px);
        self.draw(start_px, stop_px);
    }

    fn evaluate(&mut self, reg: &PPUReg, mem: &mut PPUMemory, start: usize, stop: usize) {
        let mut current_scanline = start / SCREEN_WIDTH;
        let mut last_scanline_boundary = current_scanline * SCREEN_WIDTH;
        let mut next_scanline_boundary = last_scanline_boundary + SCREEN_WIDTH;

        let mut current = start;
        while current < stop {
            let segment_start = current - last_scanline_boundary;
            let segment_end = cmp::min(next_scanline_boundary, stop) - last_scanline_boundary;

            self.evaluate_segment(reg, mem, current_scanline, segment_start, segment_end);
            current_scanline += 1;
            last_scanline_boundary = next_scanline_boundary;
            current = next_scanline_boundary;
            next_scanline_boundary += SCREEN_WIDTH;
        }
    }

    fn evaluate_segment(&mut self,
                        reg: &PPUReg,
                        mem: &mut PPUMemory,
                        scanline: usize,
                        start: usize,
                        stop: usize) {
        let tile_line = &mut self.tile[scanline];
        let attr_line = &mut self.attr[scanline];

        let tile_start = start / 8;
        let tile_stop = (stop + 8 - 1) / 8;

        for x in tile_start..tile_stop {
            let tile_x = x as u16;
            let tile_y = scanline as u16 / 8;

            let nametable_addr = get_nametable_addr(reg, tile_x, tile_y);
            let tile_table = reg.ppuctrl.background_table();
            let tile_idx = mem.read(nametable_addr);
            tile_line[x] = mem.read_tile_pattern(tile_idx, scanline as u16 & 0x07, tile_table);

            let attr_addr = get_attribute_addr(reg, tile_x, tile_y);
            attr_line[x] = TileAttribute::new(mem.read(attr_addr));
        }
    }

    fn draw(&mut self, start: usize, stop: usize) {
        let mut current_scanline = start / SCREEN_WIDTH;
        let mut last_scanline_boundary = current_scanline * SCREEN_WIDTH;
        let next_scanline = current_scanline + 1;
        let mut next_scanline_boundary = next_scanline * SCREEN_WIDTH;

        let mut current = start;
        while current < stop {
            let segment_start = current - last_scanline_boundary;
            let segment_end = cmp::min(next_scanline_boundary, stop) - last_scanline_boundary;

            self.draw_segment(current_scanline,
                              last_scanline_boundary,
                              next_scanline_boundary,
                              segment_start,
                              segment_end);
            current_scanline += 1;
            last_scanline_boundary = next_scanline_boundary;
            current = next_scanline_boundary;
            next_scanline_boundary += SCREEN_WIDTH;
        }
    }

    fn draw_segment(&mut self,
                    scanline: usize,
                    line_start: usize,
                    line_stop: usize,
                    start: usize,
                    stop: usize) {
        let pattern_line = &self.tile[scanline];
        let attr_line = &self.attr[scanline];
        let pixel_line = &mut self.background_buffer[line_start..line_stop];

        for pixel in start..stop {
            let tile_idx = pixel / 8;
            let pattern = pattern_line[tile_idx];
            let fine_x = pixel as u32 & 0x07;
            let color_id = pattern.get_color_in_pattern(fine_x);

            let attr = attr_line[tile_idx];
            let palette_id = attr.get_palette(pixel as u16, scanline as u16);

            pixel_line[pixel] = PaletteIndex {
                set: PaletteSet::Background,
                palette_id: palette_id,
                color_id: color_id,
            }
        }
    }

    pub fn buffer(&self) -> &[PaletteIndex] {
        &self.background_buffer
    }

    #[allow(dead_code)]
    pub fn dump_background_pixels(&self) {
        for y in 0..SCREEN_HEIGHT {
            for x in 0..SCREEN_WIDTH {
                let idx = y * SCREEN_WIDTH + x;
                let pix = self.background_buffer[idx];
                if pix.is_transparent() {
                    print!(" ");
                } else {
                    print!("{}", pix.color_id);
                }
            }
            println!("");
        }
        println!("");
    }

    #[allow(dead_code)]
    pub fn dump_background_palettes(&self) {
        for y in 0..SCREEN_HEIGHT {
            for x in 0..SCREEN_WIDTH {
                let idx = y * SCREEN_WIDTH + x;
                let pix = self.background_buffer[idx];
                if pix.is_transparent() {
                    print!(" ");
                } else {
                    print!("{}", pix.palette_id);
                }
            }
            println!("");
        }
        println!("");
    }
}

impl Default for BackgroundRenderer {
    fn default() -> BackgroundRenderer {
        // Work around the 32-element array limitation
        let (tiles, attrs) = unsafe {
            use std::ptr;
            use std::mem;

            let mut tiles: [[TilePattern; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized();
            let mut attrs: [[TileAttribute; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized();

            for element in tiles.iter_mut() {
                let tile_line: [TilePattern; TILES_PER_LINE] = [Default::default(); TILES_PER_LINE];
                ptr::write(element, tile_line);
            }
            for element in attrs.iter_mut() {
                let attr_line: [TileAttribute; TILES_PER_LINE] =
                    [Default::default(); TILES_PER_LINE];
                ptr::write(element, attr_line);
            }

            (tiles, attrs)
        };

        BackgroundRenderer {
            tile: Box::new(tiles),
            attr: Box::new(attrs),

            background_buffer: vec![Default::default(); SCREEN_BUFFER_SIZE].into_boxed_slice(),
        }
    }
}
<|endoftext|>


<commit_before>//! Helpers providing multi-dimensional views of 1d data.

use num::Zero;

/// A 3d array that owns its data.
pub struct Array3d<T> {
    /// The owned data.
    pub data: Vec<T>,
    /// Lengths of the dimensions, from innermost (i.e. fastest-varying) to outermost.
    pub lengths: [usize; 3]
}

/// A view into a 3d array.
pub struct View3d<'a, T: 'a> {
    /// The underlying data.
	pub data: &'a mut [T],
    /// Lengths of the dimensions, from innermost (i.e. fastest-varying) to outermost.
    pub lengths: [usize; 3]
}

impl<T: Zero + Clone> Array3d<T> {
    /// Allocates a new Array3d with the given dimensions.
	pub fn new(lengths: [usize; 3]) -> Array3d<T> {
		let data = vec![Zero::zero(); data_length(lengths)];
		Array3d { data: data, lengths: lengths }
	}

    /// Provides a 3d view of the data.
    pub fn view_mut(&mut self) -> View3d<T> {
        View3d::from_raw(&mut self.data, self.lengths)
    }
}

impl<'a, T> View3d<'a, T> {

	/// Constructs index from existing data and the lengths of the desired dimensions.
    pub fn from_raw(data: &'a mut [T], lengths: [usize; 3]) -> View3d<'a, T> {
        View3d { data: data, lengths: lengths }
    }

    /// Immutable access to the raw data.
    pub fn data(&self) -> &[T] {
        self.data
    }

    /// Mutable access to the raw data.
    pub fn data_mut(&mut self) -> &mut [T] {
        &mut self.data
    }

    /// An immutable reference from a 3d index.
    pub fn at(&self, indices: [usize; 3]) -> &T {
        &self.data[self.offset(indices)]
    }

    /// A mutable reference from a 3d index.
    pub fn at_mut(&mut self, indices: [usize; 3]) -> &mut T {
        &mut self.data[self.offset(indices)]
    }

    /// All entries with the given outer dimensions. As the first dimension
    /// is fastest varying, this is a contiguous slice.
    pub fn inner_slice(&self, x1: usize, x2: usize) -> &[T] {
        let offset = self.offset([0, x1, x2]);
        & self.data[offset..offset + self.lengths[0]]
    }

    /// All entries with the given outer dimensions. As the first dimension
    /// is fastest varying, this is a contiguous slice.
    pub fn inner_slice_mut(&mut self, x1: usize, x2: usize) -> &mut [T] {
        let offset = self.offset([0, x1, x2]);
        &mut self.data[offset..offset + self.lengths[0]]
    }

    fn offset(&self, indices: [usize; 3]) -> usize {
        indices[2] * self.lengths[1] * self.lengths[0] + indices[1] * self.lengths[0] + indices[0]
    }
}

/// Length of array needed for the given dimensions.
fn data_length(lengths: [usize; 3]) -> usize {
    lengths[0] * lengths[1] * lengths[2]
}
<commit_msg>Run rustfmt on multiarray.rs<commit_after>//! Helpers providing multi-dimensional views of 1d data.

use num::Zero;

/// A 3d array that owns its data.
pub struct Array3d<T> {
    /// The owned data.
    pub data: Vec<T>,
    /// Lengths of the dimensions, from innermost (i.e. fastest-varying) to outermost.
    pub lengths: [usize; 3],
}

/// A view into a 3d array.
pub struct View3d<'a, T: 'a> {
    /// The underlying data.
    pub data: &'a mut [T],
    /// Lengths of the dimensions, from innermost (i.e. fastest-varying) to outermost.
    pub lengths: [usize; 3],
}

impl<T: Zero + Clone> Array3d<T> {
    /// Allocates a new Array3d with the given dimensions.
    pub fn new(lengths: [usize; 3]) -> Array3d<T> {
        let data = vec![Zero::zero(); data_length(lengths)];
        Array3d {
            data: data,
            lengths: lengths,
        }
    }

    /// Provides a 3d view of the data.
    pub fn view_mut(&mut self) -> View3d<T> {
        View3d::from_raw(&mut self.data, self.lengths)
    }
}

impl<'a, T> View3d<'a, T> {
    /// Constructs index from existing data and the lengths of the desired dimensions.
    pub fn from_raw(data: &'a mut [T], lengths: [usize; 3]) -> View3d<'a, T> {
        View3d {
            data: data,
            lengths: lengths,
        }
    }

    /// Immutable access to the raw data.
    pub fn data(&self) -> &[T] {
        self.data
    }

    /// Mutable access to the raw data.
    pub fn data_mut(&mut self) -> &mut [T] {
        &mut self.data
    }

    /// An immutable reference from a 3d index.
    pub fn at(&self, indices: [usize; 3]) -> &T {
        &self.data[self.offset(indices)]
    }

    /// A mutable reference from a 3d index.
    pub fn at_mut(&mut self, indices: [usize; 3]) -> &mut T {
        &mut self.data[self.offset(indices)]
    }

    /// All entries with the given outer dimensions. As the first dimension
    /// is fastest varying, this is a contiguous slice.
    pub fn inner_slice(&self, x1: usize, x2: usize) -> &[T] {
        let offset = self.offset([0, x1, x2]);
        &self.data[offset..offset + self.lengths[0]]
    }

    /// All entries with the given outer dimensions. As the first dimension
    /// is fastest varying, this is a contiguous slice.
    pub fn inner_slice_mut(&mut self, x1: usize, x2: usize) -> &mut [T] {
        let offset = self.offset([0, x1, x2]);
        &mut self.data[offset..offset + self.lengths[0]]
    }

    fn offset(&self, indices: [usize; 3]) -> usize {
        indices[2] * self.lengths[1] * self.lengths[0] + indices[1] * self.lengths[0] + indices[0]
    }
}

/// Length of array needed for the given dimensions.
fn data_length(lengths: [usize; 3]) -> usize {
    lengths[0] * lengths[1] * lengths[2]
}
<|endoftext|>


<commit_before>use mio::Token;

#[derive(Debug)]
pub enum InitError {
    UnknownError,
}

pub struct Server {
    token: Token,
}

impl Server {
    fn new(port: u16) -> Result<Server, InitError> {
        unimplemented!();
    }
}
<commit_msg>Remove server acording to the outcome of #1<commit_after><|endoftext|>


<commit_before>use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
use std::fmt::{Formatter, Result, Display};
use bit_reverse::ParallelReverse;

/// Represents a 64-bit bitboard.
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct BitBoard(pub u64);

impl BitBoard {
    pub fn flip(&self) -> Self {
        BitBoard(self.0.swap_bits())
    }
}

// ---------------------------------------------------------------------------
// Operations
impl BitAnd for BitBoard {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self {
        BitBoard(self.0 & rhs.0)
    }
}

impl BitAndAssign for BitBoard {
    fn bitand_assign(&mut self, rhs: Self) {
        *self = BitBoard(self.0 & rhs.0)
    }
}

impl BitOr for BitBoard {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self {
        BitBoard(self.0 | rhs.0)
    }
}

impl BitOrAssign for BitBoard {
    fn bitor_assign(&mut self, rhs: Self) {
        *self = BitBoard(self.0 | rhs.0)
    }
}

impl BitXor for BitBoard {
    type Output = Self;

    fn bitxor(self, rhs: Self) -> Self {
        BitBoard(self.0 ^ rhs.0)
    }
}

impl BitXorAssign for BitBoard {
    fn bitxor_assign(&mut self, rhs: Self) {
        *self = BitBoard(self.0 ^ rhs.0)
    }
}

impl Not for BitBoard {
    type Output = Self;

    fn not(self) -> Self {
        BitBoard(!self.0)
    }
}

// ---------------------------------------------------------------------------
// Display
impl Display for BitBoard {
    #[allow(unused_must_use)]
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut str = String::new();
        str.push_str(self.0.to_string().as_str());
        str.push_str("\n+-+-+-+-+-+-+-+-+-+\n");
        for rank in 0..8 {
            str.push_str("| ");
            for file in 0..8 {
                let char = if self.0 & (1 << ((7 - rank) * 8 + file)) > 0 {
                    "X"
                } else {
                    "-"
                };
                str.push_str(char);
            }
            str.push_str("|\n");
        }
        str.push_str("+-+-+-+-+-+-+-+-+-+");
        write!(f, "{}", str)
    }
}
<commit_msg>Add count method to bitboard<commit_after>use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
use std::fmt::{Formatter, Result, Display};
use bit_reverse::ParallelReverse;

/// Represents a 64-bit bitboard.
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct BitBoard(pub u64);

impl BitBoard {
    /// Flips the bitboard such that it's from the perspective of the other player.
    pub fn flip(&self) -> Self {
        BitBoard(self.0.swap_bits())
    }

    /// Counts the number of set bits on the bitboard.
    pub fn count(&self) -> u32 {
        self.0.count_ones()
    }
}

// ---------------------------------------------------------------------------
// Operations
impl BitAnd for BitBoard {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self {
        BitBoard(self.0 & rhs.0)
    }
}

impl BitAndAssign for BitBoard {
    fn bitand_assign(&mut self, rhs: Self) {
        *self = BitBoard(self.0 & rhs.0)
    }
}

impl BitOr for BitBoard {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self {
        BitBoard(self.0 | rhs.0)
    }
}

impl BitOrAssign for BitBoard {
    fn bitor_assign(&mut self, rhs: Self) {
        *self = BitBoard(self.0 | rhs.0)
    }
}

impl BitXor for BitBoard {
    type Output = Self;

    fn bitxor(self, rhs: Self) -> Self {
        BitBoard(self.0 ^ rhs.0)
    }
}

impl BitXorAssign for BitBoard {
    fn bitxor_assign(&mut self, rhs: Self) {
        *self = BitBoard(self.0 ^ rhs.0)
    }
}

impl Not for BitBoard {
    type Output = Self;

    fn not(self) -> Self {
        BitBoard(!self.0)
    }
}

// ---------------------------------------------------------------------------
// Display
impl Display for BitBoard {
    #[allow(unused_must_use)]
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut str = String::new();
        str.push_str(self.0.to_string().as_str());
        str.push_str("\n+-+-+-+-+-+-+-+-+-+\n");
        for rank in 0..8 {
            str.push_str("| ");
            for file in 0..8 {
                let char = if self.0 & (1 << ((7 - rank) * 8 + file)) > 0 {
                    "X"
                } else {
                    "-"
                };
                str.push_str(char);
            }
            str.push_str("|\n");
        }
        str.push_str("+-+-+-+-+-+-+-+-+-+");
        write!(f, "{}", str)
    }
}
<|endoftext|>


<commit_before>/* 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 http://mozilla.org/MPL/2.0/. */

use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use net_traits::storage_thread::{StorageThreadMsg, StorageType};
use resource_thread;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::PathBuf;
use std::thread;

const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024;

pub trait StorageThreadFactory {
    fn new(config_dir: Option<PathBuf>) -> Self;
}

impl StorageThreadFactory for IpcSender<StorageThreadMsg> {
    /// Create a storage thread
    fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> {
        let (chan, port) = ipc::channel().unwrap();
        thread::Builder::new().name("StorageManager".to_owned()).spawn(move || {
            StorageManager::new(port, config_dir).start();
        }).expect("Thread spawning failed");
        chan
    }
}

struct StorageManager {
    port: IpcReceiver<StorageThreadMsg>,
    session_data: HashMap<String, (usize, BTreeMap<String, String>)>,
    local_data: HashMap<String, (usize, BTreeMap<String, String>)>,
    config_dir: Option<PathBuf>,
}

impl StorageManager {
    fn new(port: IpcReceiver<StorageThreadMsg>,
           config_dir: Option<PathBuf>)
           -> StorageManager {
        let mut local_data = HashMap::new();
        if let Some(ref config_dir) = config_dir {
            resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json");
        }
        StorageManager {
            port: port,
            session_data: HashMap::new(),
            local_data: local_data,
            config_dir: config_dir,
        }
    }
}

impl StorageManager {
    fn start(&mut self) {
        loop {
            match self.port.recv().unwrap() {
                StorageThreadMsg::Length(sender, url, storage_type) => {
                    self.length(sender, url, storage_type)
                }
                StorageThreadMsg::Key(sender, url, storage_type, index) => {
                    self.key(sender, url, storage_type, index)
                }
                StorageThreadMsg::Keys(sender, url, storage_type) => {
                    self.keys(sender, url, storage_type)
                }
                StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => {
                    self.set_item(sender, url, storage_type, name, value)
                }
                StorageThreadMsg::GetItem(sender, url, storage_type, name) => {
                    self.request_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => {
                    self.remove_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::Clear(sender, url, storage_type) => {
                    self.clear(sender, url, storage_type)
                }
                StorageThreadMsg::Exit(sender) => {
                    if let Some(ref config_dir) = self.config_dir {
                        resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json");
                    }
                    let _ = sender.send(());
                    break
                }
            }
        }
    }

    fn select_data(&self, storage_type: StorageType)
                   -> &HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &self.session_data,
            StorageType::Local => &self.local_data
        }
    }

    fn select_data_mut(&mut self, storage_type: StorageType)
                       -> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &mut self.session_data,
            StorageType::Local => &mut self.local_data
        }
    }

    fn length(&self, sender: IpcSender<usize>, url: ServoUrl, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len())).unwrap();
    }

    fn key(&self,
           sender: IpcSender<Option<String>>,
           url: ServoUrl,
           storage_type: StorageType,
           index: u32) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let key = data.get(&origin)
                      .and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
                      .cloned();
        sender.send(key).unwrap();
    }

    fn keys(&self,
            sender: IpcSender<Vec<String>>,
            url: ServoUrl,
            storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let keys = data.get(&origin)
                       .map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());

        sender.send(keys).unwrap();
    }

    /// Sends Ok(changed, Some(old_value)) in case there was a previous
    /// value with the same key name but with different value name
    /// otherwise sends Err(()) to indicate that the operation would result in
    /// exceeding the quota limit
    fn set_item(&mut self,
                sender: IpcSender<Result<(bool, Option<String>), ()>>,
                url: ServoUrl,
                storage_type: StorageType,
                name: String,
                value: String) {
        let origin = self.origin_as_string(url);

        let (this_storage_size, other_storage_size) = {
            let local_data = self.select_data(StorageType::Local);
            let session_data = self.select_data(StorageType::Session);
            let local_data_size = local_data.get(&origin).map_or(0, |&(total, _)| total);
            let session_data_size = session_data.get(&origin).map_or(0, |&(total, _)| total);
            match storage_type {
                StorageType::Local => (local_data_size, session_data_size),
                StorageType::Session => (session_data_size, local_data_size),
            }
        };

        let data = self.select_data_mut(storage_type);
        if !data.contains_key(&origin) {
            data.insert(origin.clone(), (0, BTreeMap::new()));
        }

        let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| {
            let mut new_total_size = this_storage_size + value.as_bytes().len();
            if let Some(old_value) = entry.get(&name) {
                new_total_size -= old_value.as_bytes().len();
            } else {
                new_total_size += name.as_bytes().len();
            }

            if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT {
                return Err(());
            }

            let message = entry.insert(name.clone(), value.clone()).map_or(
                Ok((true, None)),
                |old| if old == value {
                    Ok((false, None))
                } else {
                    Ok((true, Some(old)))
                });
            *total = new_total_size;
            message
        }).unwrap();
        sender.send(message).unwrap();
    }

    fn request_item(&self,
                sender: IpcSender<Option<String>>,
                url: ServoUrl,
                storage_type: StorageType,
                name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin)
                    .and_then(|&(_, ref entry)| entry.get(&name))
                    .map(String::clone)).unwrap();
    }

    /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None
    fn remove_item(&mut self,
                   sender: IpcSender<Option<String>>,
                   url: ServoUrl,
                   storage_type: StorageType,
                   name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| {
            entry.remove(&name).and_then(|old| {
                *total -= name.as_bytes().len() + old.as_bytes().len();
                Some(old)
            })
        });
        sender.send(old_value).unwrap();
    }

    fn clear(&mut self, sender: IpcSender<bool>, url: ServoUrl, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        sender.send(data.get_mut(&origin)
                    .map_or(false, |&mut (ref mut total, ref mut entry)| {
                        if !entry.is_empty() {
                            entry.clear();
                            *total = 0;
                            true
                        } else {
                            false
                        }})).unwrap();
    }

    fn origin_as_string(&self, url: ServoUrl) -> String {
        url.origin().ascii_serialization()
    }
}
<commit_msg>Save local storage at every change and not just on shutdown<commit_after>/* 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 http://mozilla.org/MPL/2.0/. */

use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use net_traits::storage_thread::{StorageThreadMsg, StorageType};
use resource_thread;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::PathBuf;
use std::thread;

const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024;

pub trait StorageThreadFactory {
    fn new(config_dir: Option<PathBuf>) -> Self;
}

impl StorageThreadFactory for IpcSender<StorageThreadMsg> {
    /// Create a storage thread
    fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> {
        let (chan, port) = ipc::channel().unwrap();
        thread::Builder::new().name("StorageManager".to_owned()).spawn(move || {
            StorageManager::new(port, config_dir).start();
        }).expect("Thread spawning failed");
        chan
    }
}

struct StorageManager {
    port: IpcReceiver<StorageThreadMsg>,
    session_data: HashMap<String, (usize, BTreeMap<String, String>)>,
    local_data: HashMap<String, (usize, BTreeMap<String, String>)>,
    config_dir: Option<PathBuf>,
}

impl StorageManager {
    fn new(port: IpcReceiver<StorageThreadMsg>,
           config_dir: Option<PathBuf>)
           -> StorageManager {
        let mut local_data = HashMap::new();
        if let Some(ref config_dir) = config_dir {
            resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json");
        }
        StorageManager {
            port: port,
            session_data: HashMap::new(),
            local_data: local_data,
            config_dir: config_dir,
        }
    }
}

impl StorageManager {
    fn start(&mut self) {
        loop {
            match self.port.recv().unwrap() {
                StorageThreadMsg::Length(sender, url, storage_type) => {
                    self.length(sender, url, storage_type)
                }
                StorageThreadMsg::Key(sender, url, storage_type, index) => {
                    self.key(sender, url, storage_type, index)
                }
                StorageThreadMsg::Keys(sender, url, storage_type) => {
                    self.keys(sender, url, storage_type)
                }
                StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => {
                    self.set_item(sender, url, storage_type, name, value);
                    self.save_state()
                }
                StorageThreadMsg::GetItem(sender, url, storage_type, name) => {
                    self.request_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => {
                    self.remove_item(sender, url, storage_type, name);
                    self.save_state()
                }
                StorageThreadMsg::Clear(sender, url, storage_type) => {
                    self.clear(sender, url, storage_type);
                    self.save_state()
                }
                StorageThreadMsg::Exit(sender) => {
                    // Nothing to do since we save localstorage set eagerly.
                    let _ = sender.send(());
                    break
                }
            }
        }
    }

    fn save_state(&self) {
        if let Some(ref config_dir) = self.config_dir {
            resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json");
        }
    }

    fn select_data(&self, storage_type: StorageType)
                   -> &HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &self.session_data,
            StorageType::Local => &self.local_data
        }
    }

    fn select_data_mut(&mut self, storage_type: StorageType)
                       -> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &mut self.session_data,
            StorageType::Local => &mut self.local_data
        }
    }

    fn length(&self, sender: IpcSender<usize>, url: ServoUrl, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len())).unwrap();
    }

    fn key(&self,
           sender: IpcSender<Option<String>>,
           url: ServoUrl,
           storage_type: StorageType,
           index: u32) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let key = data.get(&origin)
                      .and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
                      .cloned();
        sender.send(key).unwrap();
    }

    fn keys(&self,
            sender: IpcSender<Vec<String>>,
            url: ServoUrl,
            storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let keys = data.get(&origin)
                       .map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());

        sender.send(keys).unwrap();
    }

    /// Sends Ok(changed, Some(old_value)) in case there was a previous
    /// value with the same key name but with different value name
    /// otherwise sends Err(()) to indicate that the operation would result in
    /// exceeding the quota limit
    fn set_item(&mut self,
                sender: IpcSender<Result<(bool, Option<String>), ()>>,
                url: ServoUrl,
                storage_type: StorageType,
                name: String,
                value: String) {
        let origin = self.origin_as_string(url);

        let (this_storage_size, other_storage_size) = {
            let local_data = self.select_data(StorageType::Local);
            let session_data = self.select_data(StorageType::Session);
            let local_data_size = local_data.get(&origin).map_or(0, |&(total, _)| total);
            let session_data_size = session_data.get(&origin).map_or(0, |&(total, _)| total);
            match storage_type {
                StorageType::Local => (local_data_size, session_data_size),
                StorageType::Session => (session_data_size, local_data_size),
            }
        };

        let data = self.select_data_mut(storage_type);
        if !data.contains_key(&origin) {
            data.insert(origin.clone(), (0, BTreeMap::new()));
        }

        let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| {
            let mut new_total_size = this_storage_size + value.as_bytes().len();
            if let Some(old_value) = entry.get(&name) {
                new_total_size -= old_value.as_bytes().len();
            } else {
                new_total_size += name.as_bytes().len();
            }

            if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT {
                return Err(());
            }

            let message = entry.insert(name.clone(), value.clone()).map_or(
                Ok((true, None)),
                |old| if old == value {
                    Ok((false, None))
                } else {
                    Ok((true, Some(old)))
                });
            *total = new_total_size;
            message
        }).unwrap();
        sender.send(message).unwrap();
    }

    fn request_item(&self,
                sender: IpcSender<Option<String>>,
                url: ServoUrl,
                storage_type: StorageType,
                name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin)
                    .and_then(|&(_, ref entry)| entry.get(&name))
                    .map(String::clone)).unwrap();
    }

    /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None
    fn remove_item(&mut self,
                   sender: IpcSender<Option<String>>,
                   url: ServoUrl,
                   storage_type: StorageType,
                   name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| {
            entry.remove(&name).and_then(|old| {
                *total -= name.as_bytes().len() + old.as_bytes().len();
                Some(old)
            })
        });
        sender.send(old_value).unwrap();
    }

    fn clear(&mut self, sender: IpcSender<bool>, url: ServoUrl, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        sender.send(data.get_mut(&origin)
                    .map_or(false, |&mut (ref mut total, ref mut entry)| {
                        if !entry.is_empty() {
                            entry.clear();
                            *total = 0;
                            true
                        } else {
                            false
                        }})).unwrap();
    }

    fn origin_as_string(&self, url: ServoUrl) -> String {
        url.origin().ascii_serialization()
    }
}
<|endoftext|>


<commit_before>/* 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 http://mozilla.org/MPL/2.0/. */

use hyper::header::Headers;
use hyper::method::Method;
use msg::constellation_msg::{PipelineId, ReferrerPolicy};
use std::cell::{Cell, RefCell};
use std::default::Default;
use std::mem::swap;
use url::{Origin as UrlOrigin, Url};

/// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum Initiator {
    None,
    Download,
    ImageSet,
    Manifest,
    XSLT
}

/// A request [type](https://fetch.spec.whatwg.org/#concept-request-type)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum Type {
    None, Audio, Font, Image,
    Script, Style, Track, Video
}

/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum Destination {
    None, Document, Embed, Font, Image, Manifest,
    Media, Object, Report, Script, ServiceWorker,
    SharedWorker, Style, Worker, XSLT
}

/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
#[derive(Clone, PartialEq, Debug, HeapSizeOf)]
pub enum Origin {
    Client,
    Origin(UrlOrigin)
}

/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer)
#[derive(Clone, PartialEq, HeapSizeOf)]
pub enum Referrer {
    NoReferrer,
    /// Default referrer if nothing is specified
    Client,
    ReferrerUrl(Url)
}

/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum RequestMode {
    Navigate,
    SameOrigin,
    NoCORS,
    CORSMode
}

/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum CredentialsMode {
    Omit,
    CredentialsSameOrigin,
    Include
}

/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum CacheMode {
    Default,
    NoStore,
    Reload,
    NoCache,
    ForceCache,
    OnlyIfCached
}

/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum RedirectMode {
    Follow,
    Error,
    Manual
}

/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum ResponseTainting {
    Basic,
    CORSTainting,
    Opaque
}

/// [Window](https://fetch.spec.whatwg.org/#concept-request-window)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum Window {
    NoWindow,
    Client,
    // TODO: Environmental settings object
}

/// [CORS settings attribute](https://html.spec.whatwg.org/multipage/#attr-crossorigin-anonymous)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum CORSSettings {
    Anonymous,
    UseCredentials
}

#[derive(Serialize, Deserialize, Clone)]
pub struct RequestInit {
    #[serde(deserialize_with = "::hyper_serde::deserialize",
            serialize_with = "::hyper_serde::serialize")]
    pub method: Method,
    pub url: Url,
    #[serde(deserialize_with = "::hyper_serde::deserialize",
            serialize_with = "::hyper_serde::serialize")]
    pub headers: Headers,
    pub unsafe_request: bool,
    pub body: Option<Vec<u8>>,
    // TODO: client object
    pub type_: Type,
    pub destination: Destination,
    pub synchronous: bool,
    pub mode: RequestMode,
    pub use_cors_preflight: bool,
    pub credentials_mode: CredentialsMode,
    pub use_url_credentials: bool,
    // this should actually be set by fetch, but fetch
    // doesn't have info about the client right now
    pub origin: Url,
    // XXXManishearth these should be part of the client object
    pub referrer_url: Option<Url>,
    pub referrer_policy: Option<ReferrerPolicy>,
    pub pipeline_id: Option<PipelineId>,
}

impl Default for RequestInit {
    fn default() -> RequestInit {
        RequestInit {
            method: Method::Get,
            url: Url::parse("about:blank").unwrap(),
            headers: Headers::new(),
            unsafe_request: false,
            body: None,
            type_: Type::None,
            destination: Destination::None,
            synchronous: false,
            mode: RequestMode::NoCORS,
            use_cors_preflight: false,
            credentials_mode: CredentialsMode::Omit,
            use_url_credentials: false,
            origin: Url::parse("about:blank").unwrap(),
            referrer_url: None,
            referrer_policy: None,
            pipeline_id: None,
        }
    }
}

/// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
#[derive(Clone, HeapSizeOf)]
pub struct Request {
    #[ignore_heap_size_of = "Defined in hyper"]
    pub method: RefCell<Method>,
    pub local_urls_only: bool,
    pub sandboxed_storage_area_urls: bool,
    #[ignore_heap_size_of = "Defined in hyper"]
    pub headers: RefCell<Headers>,
    pub unsafe_request: bool,
    pub body: RefCell<Option<Vec<u8>>>,
    // TODO: client object
    pub is_service_worker_global_scope: bool,
    pub window: Cell<Window>,
    // TODO: target browsing context
    pub keep_alive: Cell<bool>,
    pub skip_service_worker: Cell<bool>,
    pub initiator: Initiator,
    pub type_: Type,
    pub destination: Destination,
    // TODO: priority object
    pub origin: RefCell<Origin>,
    pub omit_origin_header: Cell<bool>,
    /// https://fetch.spec.whatwg.org/#concept-request-referrer
    pub referrer: RefCell<Referrer>,
    pub referrer_policy: Cell<Option<ReferrerPolicy>>,
    pub pipeline_id: Cell<Option<PipelineId>>,
    pub synchronous: bool,
    pub mode: RequestMode,
    pub use_cors_preflight: bool,
    pub credentials_mode: CredentialsMode,
    pub use_url_credentials: bool,
    pub cache_mode: Cell<CacheMode>,
    pub redirect_mode: Cell<RedirectMode>,
    pub integrity_metadata: RefCell<String>,
    // Use the last method on url_list to act as spec current url field, and
    // first method to act as spec url field
    pub url_list: RefCell<Vec<Url>>,
    pub redirect_count: Cell<u32>,
    pub response_tainting: Cell<ResponseTainting>,
    pub done: Cell<bool>,
}

impl Request {
    pub fn new(url: Url,
               origin: Option<Origin>,
               is_service_worker_global_scope: bool,
               pipeline_id: Option<PipelineId>) -> Request {
        Request {
            method: RefCell::new(Method::Get),
            local_urls_only: false,
            sandboxed_storage_area_urls: false,
            headers: RefCell::new(Headers::new()),
            unsafe_request: false,
            body: RefCell::new(None),
            is_service_worker_global_scope: is_service_worker_global_scope,
            window: Cell::new(Window::Client),
            keep_alive: Cell::new(false),
            skip_service_worker: Cell::new(false),
            initiator: Initiator::None,
            type_: Type::None,
            destination: Destination::None,
            origin: RefCell::new(origin.unwrap_or(Origin::Client)),
            omit_origin_header: Cell::new(false),
            referrer: RefCell::new(Referrer::Client),
            referrer_policy: Cell::new(None),
            pipeline_id: Cell::new(pipeline_id),
            synchronous: false,
            mode: RequestMode::NoCORS,
            use_cors_preflight: false,
            credentials_mode: CredentialsMode::Omit,
            use_url_credentials: false,
            cache_mode: Cell::new(CacheMode::Default),
            redirect_mode: Cell::new(RedirectMode::Follow),
            integrity_metadata: RefCell::new(String::new()),
            url_list: RefCell::new(vec![url]),
            redirect_count: Cell::new(0),
            response_tainting: Cell::new(ResponseTainting::Basic),
            done: Cell::new(false)
        }
    }

    pub fn from_init(init: RequestInit) -> Request {
        let mut req = Request::new(init.url,
                                   Some(Origin::Origin(init.origin.origin())),
                                   false, init.pipeline_id);
        *req.method.borrow_mut() = init.method;
        *req.headers.borrow_mut() = init.headers;
        req.unsafe_request = init.unsafe_request;
        *req.body.borrow_mut() = init.body;
        req.type_ = init.type_;
        req.destination = init.destination;
        req.synchronous = init.synchronous;
        req.mode = init.mode;
        req.use_cors_preflight = init.use_cors_preflight;
        req.credentials_mode = init.credentials_mode;
        req.use_url_credentials = init.use_url_credentials;
        *req.referrer.borrow_mut() = if let Some(url) = init.referrer_url {
            Referrer::ReferrerUrl(url)
        } else {
            Referrer::NoReferrer
        };
        req.referrer_policy.set(init.referrer_policy);
        req.pipeline_id.set(init.pipeline_id);
        req
    }

    pub fn url(&self) -> Url {
        self.url_list.borrow().first().unwrap().clone()
    }

    pub fn current_url(&self) -> Url {
        self.url_list.borrow().last().unwrap().clone()
    }

    pub fn current_url_string(&self) -> String {
        self.url_list.borrow().last().unwrap().to_string()
    }

    pub fn is_navigation_request(&self) -> bool {
        self.destination == Destination::Document
    }

    pub fn is_subresource_request(&self) -> bool {
        match self.destination {
            Destination::Font | Destination::Image | Destination::Manifest
                | Destination::Media | Destination::Script
                | Destination::Style | Destination::XSLT
                | Destination::None => true,
            _ => false
        }
    }
}

impl Referrer {
    pub fn to_url(&self) -> Option<&Url> {
        match *self {
            Referrer::NoReferrer | Referrer::Client => None,
            Referrer::ReferrerUrl(ref url) => Some(url)
        }
    }
    pub fn from_url(url: Option<Url>) -> Self {
        if let Some(url) = url {
            Referrer::ReferrerUrl(url)
        } else {
            Referrer::NoReferrer
        }
    }
    pub fn take(&mut self) -> Option<Url> {
        let mut new = Referrer::Client;
        swap(self, &mut new);
        match new {
            Referrer::NoReferrer | Referrer::Client => None,
            Referrer::ReferrerUrl(url) => Some(url)
        }
    }
}
<commit_msg>Remove unused trait method.<commit_after>/* 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 http://mozilla.org/MPL/2.0/. */

use hyper::header::Headers;
use hyper::method::Method;
use msg::constellation_msg::{PipelineId, ReferrerPolicy};
use std::cell::{Cell, RefCell};
use std::default::Default;
use std::mem::swap;
use url::{Origin as UrlOrigin, Url};

/// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum Initiator {
    None,
    Download,
    ImageSet,
    Manifest,
    XSLT
}

/// A request [type](https://fetch.spec.whatwg.org/#concept-request-type)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum Type {
    None, Audio, Font, Image,
    Script, Style, Track, Video
}

/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum Destination {
    None, Document, Embed, Font, Image, Manifest,
    Media, Object, Report, Script, ServiceWorker,
    SharedWorker, Style, Worker, XSLT
}

/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
#[derive(Clone, PartialEq, Debug, HeapSizeOf)]
pub enum Origin {
    Client,
    Origin(UrlOrigin)
}

/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer)
#[derive(Clone, PartialEq, HeapSizeOf)]
pub enum Referrer {
    NoReferrer,
    /// Default referrer if nothing is specified
    Client,
    ReferrerUrl(Url)
}

/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum RequestMode {
    Navigate,
    SameOrigin,
    NoCORS,
    CORSMode
}

/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)]
pub enum CredentialsMode {
    Omit,
    CredentialsSameOrigin,
    Include
}

/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum CacheMode {
    Default,
    NoStore,
    Reload,
    NoCache,
    ForceCache,
    OnlyIfCached
}

/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum RedirectMode {
    Follow,
    Error,
    Manual
}

/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum ResponseTainting {
    Basic,
    CORSTainting,
    Opaque
}

/// [Window](https://fetch.spec.whatwg.org/#concept-request-window)
#[derive(Copy, Clone, PartialEq, HeapSizeOf)]
pub enum Window {
    NoWindow,
    Client,
    // TODO: Environmental settings object
}

/// [CORS settings attribute](https://html.spec.whatwg.org/multipage/#attr-crossorigin-anonymous)
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum CORSSettings {
    Anonymous,
    UseCredentials
}

#[derive(Serialize, Deserialize, Clone)]
pub struct RequestInit {
    #[serde(deserialize_with = "::hyper_serde::deserialize",
            serialize_with = "::hyper_serde::serialize")]
    pub method: Method,
    pub url: Url,
    #[serde(deserialize_with = "::hyper_serde::deserialize",
            serialize_with = "::hyper_serde::serialize")]
    pub headers: Headers,
    pub unsafe_request: bool,
    pub body: Option<Vec<u8>>,
    // TODO: client object
    pub type_: Type,
    pub destination: Destination,
    pub synchronous: bool,
    pub mode: RequestMode,
    pub use_cors_preflight: bool,
    pub credentials_mode: CredentialsMode,
    pub use_url_credentials: bool,
    // this should actually be set by fetch, but fetch
    // doesn't have info about the client right now
    pub origin: Url,
    // XXXManishearth these should be part of the client object
    pub referrer_url: Option<Url>,
    pub referrer_policy: Option<ReferrerPolicy>,
    pub pipeline_id: Option<PipelineId>,
}

impl Default for RequestInit {
    fn default() -> RequestInit {
        RequestInit {
            method: Method::Get,
            url: Url::parse("about:blank").unwrap(),
            headers: Headers::new(),
            unsafe_request: false,
            body: None,
            type_: Type::None,
            destination: Destination::None,
            synchronous: false,
            mode: RequestMode::NoCORS,
            use_cors_preflight: false,
            credentials_mode: CredentialsMode::Omit,
            use_url_credentials: false,
            origin: Url::parse("about:blank").unwrap(),
            referrer_url: None,
            referrer_policy: None,
            pipeline_id: None,
        }
    }
}

/// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
#[derive(Clone, HeapSizeOf)]
pub struct Request {
    #[ignore_heap_size_of = "Defined in hyper"]
    pub method: RefCell<Method>,
    pub local_urls_only: bool,
    pub sandboxed_storage_area_urls: bool,
    #[ignore_heap_size_of = "Defined in hyper"]
    pub headers: RefCell<Headers>,
    pub unsafe_request: bool,
    pub body: RefCell<Option<Vec<u8>>>,
    // TODO: client object
    pub is_service_worker_global_scope: bool,
    pub window: Cell<Window>,
    // TODO: target browsing context
    pub keep_alive: Cell<bool>,
    pub skip_service_worker: Cell<bool>,
    pub initiator: Initiator,
    pub type_: Type,
    pub destination: Destination,
    // TODO: priority object
    pub origin: RefCell<Origin>,
    pub omit_origin_header: Cell<bool>,
    /// https://fetch.spec.whatwg.org/#concept-request-referrer
    pub referrer: RefCell<Referrer>,
    pub referrer_policy: Cell<Option<ReferrerPolicy>>,
    pub pipeline_id: Cell<Option<PipelineId>>,
    pub synchronous: bool,
    pub mode: RequestMode,
    pub use_cors_preflight: bool,
    pub credentials_mode: CredentialsMode,
    pub use_url_credentials: bool,
    pub cache_mode: Cell<CacheMode>,
    pub redirect_mode: Cell<RedirectMode>,
    pub integrity_metadata: RefCell<String>,
    // Use the last method on url_list to act as spec current url field, and
    // first method to act as spec url field
    pub url_list: RefCell<Vec<Url>>,
    pub redirect_count: Cell<u32>,
    pub response_tainting: Cell<ResponseTainting>,
    pub done: Cell<bool>,
}

impl Request {
    pub fn new(url: Url,
               origin: Option<Origin>,
               is_service_worker_global_scope: bool,
               pipeline_id: Option<PipelineId>) -> Request {
        Request {
            method: RefCell::new(Method::Get),
            local_urls_only: false,
            sandboxed_storage_area_urls: false,
            headers: RefCell::new(Headers::new()),
            unsafe_request: false,
            body: RefCell::new(None),
            is_service_worker_global_scope: is_service_worker_global_scope,
            window: Cell::new(Window::Client),
            keep_alive: Cell::new(false),
            skip_service_worker: Cell::new(false),
            initiator: Initiator::None,
            type_: Type::None,
            destination: Destination::None,
            origin: RefCell::new(origin.unwrap_or(Origin::Client)),
            omit_origin_header: Cell::new(false),
            referrer: RefCell::new(Referrer::Client),
            referrer_policy: Cell::new(None),
            pipeline_id: Cell::new(pipeline_id),
            synchronous: false,
            mode: RequestMode::NoCORS,
            use_cors_preflight: false,
            credentials_mode: CredentialsMode::Omit,
            use_url_credentials: false,
            cache_mode: Cell::new(CacheMode::Default),
            redirect_mode: Cell::new(RedirectMode::Follow),
            integrity_metadata: RefCell::new(String::new()),
            url_list: RefCell::new(vec![url]),
            redirect_count: Cell::new(0),
            response_tainting: Cell::new(ResponseTainting::Basic),
            done: Cell::new(false)
        }
    }

    pub fn from_init(init: RequestInit) -> Request {
        let mut req = Request::new(init.url,
                                   Some(Origin::Origin(init.origin.origin())),
                                   false, init.pipeline_id);
        *req.method.borrow_mut() = init.method;
        *req.headers.borrow_mut() = init.headers;
        req.unsafe_request = init.unsafe_request;
        *req.body.borrow_mut() = init.body;
        req.type_ = init.type_;
        req.destination = init.destination;
        req.synchronous = init.synchronous;
        req.mode = init.mode;
        req.use_cors_preflight = init.use_cors_preflight;
        req.credentials_mode = init.credentials_mode;
        req.use_url_credentials = init.use_url_credentials;
        *req.referrer.borrow_mut() = if let Some(url) = init.referrer_url {
            Referrer::ReferrerUrl(url)
        } else {
            Referrer::NoReferrer
        };
        req.referrer_policy.set(init.referrer_policy);
        req.pipeline_id.set(init.pipeline_id);
        req
    }

    pub fn url(&self) -> Url {
        self.url_list.borrow().first().unwrap().clone()
    }

    pub fn current_url(&self) -> Url {
        self.url_list.borrow().last().unwrap().clone()
    }

    pub fn is_navigation_request(&self) -> bool {
        self.destination == Destination::Document
    }

    pub fn is_subresource_request(&self) -> bool {
        match self.destination {
            Destination::Font | Destination::Image | Destination::Manifest
                | Destination::Media | Destination::Script
                | Destination::Style | Destination::XSLT
                | Destination::None => true,
            _ => false
        }
    }
}

impl Referrer {
    pub fn to_url(&self) -> Option<&Url> {
        match *self {
            Referrer::NoReferrer | Referrer::Client => None,
            Referrer::ReferrerUrl(ref url) => Some(url)
        }
    }
    pub fn from_url(url: Option<Url>) -> Self {
        if let Some(url) = url {
            Referrer::ReferrerUrl(url)
        } else {
            Referrer::NoReferrer
        }
    }
    pub fn take(&mut self) -> Option<Url> {
        let mut new = Referrer::Client;
        swap(self, &mut new);
        match new {
            Referrer::NoReferrer | Referrer::Client => None,
            Referrer::ReferrerUrl(url) => Some(url)
        }
    }
}
<|endoftext|>


<commit_before>/* 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 http://mozilla.org/MPL/2.0/. */

//! Native representation of JS Promise values.
//!
//! This implementation differs from the traditional Rust DOM object, because the reflector
//! is provided by SpiderMonkey and has no knowledge of an associated native representation
//! (ie. dom::Promise). This means that native instances use native reference counting (Rc)
//! to ensure that no memory is leaked, which means that there can be multiple instances of
//! native Promise values that refer to the same JS value yet are distinct native objects
//! (ie. address equality for the native objects is meaningless).

use dom::bindings::conversions::root_from_object;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::reflector::{DomObject, MutDomObject, Reflector};
use dom::bindings::utils::AsCCharPtrPtr;
use dom::globalscope::GlobalScope;
use dom::promisenativehandler::PromiseNativeHandler;
use dom_struct::dom_struct;
use js::conversions::ToJSValConvertible;
use js::jsapi::{CallOriginalPromiseResolve, CallOriginalPromiseReject};
use js::jsapi::{JSAutoCompartment, CallArgs, JS_GetFunctionObject, JS_NewFunction};
use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, GetFunctionNativeReserved};
use js::jsapi::{JS_ClearPendingException, JSObject, AddRawValueRoot, RemoveRawValueRoot, PromiseState};
use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise, GetPromiseState};
use js::jsapi::{SetFunctionNativeReserved, NewFunctionWithReserved, AddPromiseReactions};
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue, ObjectValue, Int32Value};
use std::ptr;
use std::rc::Rc;

#[dom_struct]
pub struct Promise {
    reflector: Reflector,
    /// Since Promise values are natively reference counted without the knowledge of
    /// the SpiderMonkey GC, an explicit root for the reflector is stored while any
    /// native instance exists. This ensures that the reflector will never be GCed
    /// while native code could still interact with its native representation.
    #[ignore_malloc_size_of = "SM handles JS values"]
    permanent_js_root: Heap<JSVal>,
}

/// Private helper to enable adding new methods to Rc<Promise>.
trait PromiseHelper {
    #[allow(unsafe_code)]
    unsafe fn initialize(&self, cx: *mut JSContext);
}

impl PromiseHelper for Rc<Promise> {
    #[allow(unsafe_code)]
    unsafe fn initialize(&self, cx: *mut JSContext) {
        let obj = self.reflector().get_jsobject();
        self.permanent_js_root.set(ObjectValue(*obj));
        assert!(AddRawValueRoot(cx,
                                self.permanent_js_root.get_unsafe(),
                                b"Promise::root\0".as_c_char_ptr()));
    }
}

impl Drop for Promise {
    #[allow(unsafe_code)]
    fn drop(&mut self) {
        let cx = self.global().get_cx();
        unsafe {
            RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe());
        }
    }
}

impl Promise {
    #[allow(unsafe_code)]
    pub fn new(global: &GlobalScope) -> Rc<Promise> {
        let cx = global.get_cx();
        rooted!(in(cx) let mut obj = ptr::null_mut::<JSObject>());
        unsafe {
            Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut());
            Promise::new_with_js_promise(obj.handle(), cx)
        }
    }

    #[allow(unsafe_code, unrooted_must_root)]
    pub fn duplicate(&self) -> Rc<Promise> {
        let cx = self.global().get_cx();
        unsafe {
            Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
        }
    }

    #[allow(unsafe_code, unrooted_must_root)]
    unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
        assert!(IsPromiseObject(obj));
        let promise = Promise {
            reflector: Reflector::new(),
            permanent_js_root: Heap::default(),
        };
        let mut promise = Rc::new(promise);
        Rc::get_mut(&mut promise).unwrap().init_reflector(obj.get());
        promise.initialize(cx);
        promise
    }

    #[allow(unsafe_code)]
    unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, obj: MutableHandleObject) {
        let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), /* nargs = */ 2,
                                             /* flags = */ 0, ptr::null());
        assert!(!do_nothing_func.is_null());
        rooted!(in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
        assert!(!do_nothing_obj.is_null());
        obj.set(NewPromiseObject(cx, do_nothing_obj.handle(), proto));
        assert!(!obj.is_null());
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn new_resolved(
        global: &GlobalScope,
        cx: *mut JSContext,
        value: HandleValue,
    ) -> Fallible<Rc<Promise>> {
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        rooted!(in(cx) let p = CallOriginalPromiseResolve(cx, value));
        assert!(!p.handle().is_null());
        Ok(Promise::new_with_js_promise(p.handle(), cx))
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn new_rejected(
        global: &GlobalScope,
        cx: *mut JSContext,
        value: HandleValue,
    ) -> Fallible<Rc<Promise>> {
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        rooted!(in(cx) let p = CallOriginalPromiseReject(cx, value));
        assert!(!p.handle().is_null());
        Ok(Promise::new_with_js_promise(p.handle(), cx))
    }

    #[allow(unsafe_code)]
    pub fn resolve_native<T>(&self, val: &T) where T: ToJSValConvertible {
        let cx = self.global().get_cx();
        let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get());
        rooted!(in(cx) let mut v = UndefinedValue());
        unsafe {
            val.to_jsval(cx, v.handle_mut());
            self.resolve(cx, v.handle());
        }
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn resolve(&self, cx: *mut JSContext, value: HandleValue) {
        if !ResolvePromise(cx, self.promise_obj(), value) {
            JS_ClearPendingException(cx);
        }
    }

    #[allow(unsafe_code)]
    pub fn reject_native<T>(&self, val: &T) where T: ToJSValConvertible {
        let cx = self.global().get_cx();
        let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get());
        rooted!(in(cx) let mut v = UndefinedValue());
        unsafe {
            val.to_jsval(cx, v.handle_mut());
            self.reject(cx, v.handle());
        }
    }

    #[allow(unsafe_code)]
    pub fn reject_error(&self, error: Error) {
        let cx = self.global().get_cx();
        let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get());
        rooted!(in(cx) let mut v = UndefinedValue());
        unsafe {
            error.to_jsval(cx, &self.global(), v.handle_mut());
            self.reject(cx, v.handle());
        }
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn reject(&self, cx: *mut JSContext, value: HandleValue) {
        if !RejectPromise(cx, self.promise_obj(), value) {
            JS_ClearPendingException(cx);
        }
    }

    #[allow(unsafe_code)]
    pub fn is_fulfilled(&self) -> bool {
        let state = unsafe { GetPromiseState(self.promise_obj()) };
        match state {
            PromiseState::Rejected | PromiseState::Fulfilled => true,
            _ => false
        }
    }

    #[allow(unsafe_code)]
    fn promise_obj(&self) -> HandleObject {
        let obj = self.reflector().get_jsobject();
        unsafe {
            assert!(IsPromiseObject(obj));
        }
        obj
    }

    #[allow(unsafe_code)]
    pub fn append_native_handler(&self, handler: &PromiseNativeHandler) {
        let cx = self.global().get_cx();
        rooted!(in(cx) let resolve_func =
                create_native_handler_function(cx,
                                               handler.reflector().get_jsobject(),
                                               NativeHandlerTask::Resolve));

        rooted!(in(cx) let reject_func =
                create_native_handler_function(cx,
                                               handler.reflector().get_jsobject(),
                                               NativeHandlerTask::Reject));

        unsafe {
            let ok = AddPromiseReactions(cx,
                                         self.promise_obj(),
                                         resolve_func.handle(),
                                         reject_func.handle());
            assert!(ok);
        }
    }
}

#[allow(unsafe_code)]
unsafe extern fn do_nothing_promise_executor(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
    let args = CallArgs::from_vp(vp, argc);
    *args.rval() = UndefinedValue();
    true
}

const SLOT_NATIVEHANDLER: usize = 0;
const SLOT_NATIVEHANDLER_TASK: usize = 1;

#[derive(PartialEq)]
enum NativeHandlerTask {
    Resolve = 0,
    Reject = 1,
}

#[allow(unsafe_code)]
unsafe extern fn native_handler_callback(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
    let args = CallArgs::from_vp(vp, argc);
    rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER));
    assert!(v.get().is_object());

    let handler = root_from_object::<PromiseNativeHandler>(v.to_object())
        .expect("unexpected value for native handler in promise native handler callback");

    rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK));
    match v.to_int32() {
        v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(cx, args.get(0)),
        v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(cx, args.get(0)),
        _ => panic!("unexpected native handler task value"),
    };

    true
}

#[allow(unsafe_code)]
fn create_native_handler_function(cx: *mut JSContext,
                                  holder: HandleObject,
                                  task: NativeHandlerTask) -> *mut JSObject {
    unsafe {
        let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
        assert!(!func.is_null());

        rooted!(in(cx) let obj = JS_GetFunctionObject(func));
        assert!(!obj.is_null());
        SetFunctionNativeReserved(obj.get(),
                                  SLOT_NATIVEHANDLER,
                                  &ObjectValue(*holder));
        SetFunctionNativeReserved(obj.get(),
                                  SLOT_NATIVEHANDLER_TASK,
                                  &Int32Value(task as i32));
        obj.get()
    }
}

<commit_msg>Auto merge of #20305 - servo:OMAE-WA-MOU-SHINDEIRU, r=jdm<commit_after>/* 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 http://mozilla.org/MPL/2.0/. */

//! Native representation of JS Promise values.
//!
//! This implementation differs from the traditional Rust DOM object, because the reflector
//! is provided by SpiderMonkey and has no knowledge of an associated native representation
//! (ie. dom::Promise). This means that native instances use native reference counting (Rc)
//! to ensure that no memory is leaked, which means that there can be multiple instances of
//! native Promise values that refer to the same JS value yet are distinct native objects
//! (ie. address equality for the native objects is meaningless).

use dom::bindings::conversions::root_from_object;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::reflector::{DomObject, MutDomObject, Reflector};
use dom::bindings::utils::AsCCharPtrPtr;
use dom::globalscope::GlobalScope;
use dom::promisenativehandler::PromiseNativeHandler;
use dom_struct::dom_struct;
use js::conversions::ToJSValConvertible;
use js::jsapi::{AddPromiseReactions, AddRawValueRoot, CallArgs, CallOriginalPromiseReject};
use js::jsapi::{CallOriginalPromiseResolve, GetFunctionNativeReserved, GetPromiseState};
use js::jsapi::{HandleObject, HandleValue, Heap, IsPromiseObject, JS_ClearPendingException};
use js::jsapi::{JSAutoCompartment, JSContext, JSObject, JS_GetContext, JS_GetFunctionObject};
use js::jsapi::{JS_GetObjectRuntime, JS_NewFunction, MutableHandleObject};
use js::jsapi::{NewFunctionWithReserved, NewPromiseObject, PromiseState, RejectPromise};
use js::jsapi::{RemoveRawValueRoot, ResolvePromise, SetFunctionNativeReserved};
use js::jsval::{JSVal, UndefinedValue, ObjectValue, Int32Value};
use std::ptr;
use std::rc::Rc;

#[dom_struct]
pub struct Promise {
    reflector: Reflector,
    /// Since Promise values are natively reference counted without the knowledge of
    /// the SpiderMonkey GC, an explicit root for the reflector is stored while any
    /// native instance exists. This ensures that the reflector will never be GCed
    /// while native code could still interact with its native representation.
    #[ignore_malloc_size_of = "SM handles JS values"]
    permanent_js_root: Heap<JSVal>,
}

/// Private helper to enable adding new methods to Rc<Promise>.
trait PromiseHelper {
    #[allow(unsafe_code)]
    unsafe fn initialize(&self, cx: *mut JSContext);
}

impl PromiseHelper for Rc<Promise> {
    #[allow(unsafe_code)]
    unsafe fn initialize(&self, cx: *mut JSContext) {
        let obj = self.reflector().get_jsobject();
        self.permanent_js_root.set(ObjectValue(*obj));
        assert!(AddRawValueRoot(cx,
                                self.permanent_js_root.get_unsafe(),
                                b"Promise::root\0".as_c_char_ptr()));
    }
}

impl Drop for Promise {
    #[allow(unsafe_code)]
    fn drop(&mut self) {
        unsafe {
            let object = self.permanent_js_root.get().to_object();
            assert!(!object.is_null());
            let runtime = JS_GetObjectRuntime(object);
            assert!(!runtime.is_null());
            let cx = JS_GetContext(runtime);
            assert!(!cx.is_null());
            RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe());
        }
    }
}

impl Promise {
    #[allow(unsafe_code)]
    pub fn new(global: &GlobalScope) -> Rc<Promise> {
        let cx = global.get_cx();
        rooted!(in(cx) let mut obj = ptr::null_mut::<JSObject>());
        unsafe {
            Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut());
            Promise::new_with_js_promise(obj.handle(), cx)
        }
    }

    #[allow(unsafe_code, unrooted_must_root)]
    pub fn duplicate(&self) -> Rc<Promise> {
        let cx = self.global().get_cx();
        unsafe {
            Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
        }
    }

    #[allow(unsafe_code, unrooted_must_root)]
    unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
        assert!(IsPromiseObject(obj));
        let promise = Promise {
            reflector: Reflector::new(),
            permanent_js_root: Heap::default(),
        };
        let mut promise = Rc::new(promise);
        Rc::get_mut(&mut promise).unwrap().init_reflector(obj.get());
        promise.initialize(cx);
        promise
    }

    #[allow(unsafe_code)]
    unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, obj: MutableHandleObject) {
        let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), /* nargs = */ 2,
                                             /* flags = */ 0, ptr::null());
        assert!(!do_nothing_func.is_null());
        rooted!(in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
        assert!(!do_nothing_obj.is_null());
        obj.set(NewPromiseObject(cx, do_nothing_obj.handle(), proto));
        assert!(!obj.is_null());
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn new_resolved(
        global: &GlobalScope,
        cx: *mut JSContext,
        value: HandleValue,
    ) -> Fallible<Rc<Promise>> {
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        rooted!(in(cx) let p = CallOriginalPromiseResolve(cx, value));
        assert!(!p.handle().is_null());
        Ok(Promise::new_with_js_promise(p.handle(), cx))
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn new_rejected(
        global: &GlobalScope,
        cx: *mut JSContext,
        value: HandleValue,
    ) -> Fallible<Rc<Promise>> {
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        rooted!(in(cx) let p = CallOriginalPromiseReject(cx, value));
        assert!(!p.handle().is_null());
        Ok(Promise::new_with_js_promise(p.handle(), cx))
    }

    #[allow(unsafe_code)]
    pub fn resolve_native<T>(&self, val: &T) where T: ToJSValConvertible {
        let cx = self.global().get_cx();
        let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get());
        rooted!(in(cx) let mut v = UndefinedValue());
        unsafe {
            val.to_jsval(cx, v.handle_mut());
            self.resolve(cx, v.handle());
        }
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn resolve(&self, cx: *mut JSContext, value: HandleValue) {
        if !ResolvePromise(cx, self.promise_obj(), value) {
            JS_ClearPendingException(cx);
        }
    }

    #[allow(unsafe_code)]
    pub fn reject_native<T>(&self, val: &T) where T: ToJSValConvertible {
        let cx = self.global().get_cx();
        let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get());
        rooted!(in(cx) let mut v = UndefinedValue());
        unsafe {
            val.to_jsval(cx, v.handle_mut());
            self.reject(cx, v.handle());
        }
    }

    #[allow(unsafe_code)]
    pub fn reject_error(&self, error: Error) {
        let cx = self.global().get_cx();
        let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get());
        rooted!(in(cx) let mut v = UndefinedValue());
        unsafe {
            error.to_jsval(cx, &self.global(), v.handle_mut());
            self.reject(cx, v.handle());
        }
    }

    #[allow(unrooted_must_root, unsafe_code)]
    pub unsafe fn reject(&self, cx: *mut JSContext, value: HandleValue) {
        if !RejectPromise(cx, self.promise_obj(), value) {
            JS_ClearPendingException(cx);
        }
    }

    #[allow(unsafe_code)]
    pub fn is_fulfilled(&self) -> bool {
        let state = unsafe { GetPromiseState(self.promise_obj()) };
        match state {
            PromiseState::Rejected | PromiseState::Fulfilled => true,
            _ => false
        }
    }

    #[allow(unsafe_code)]
    fn promise_obj(&self) -> HandleObject {
        let obj = self.reflector().get_jsobject();
        unsafe {
            assert!(IsPromiseObject(obj));
        }
        obj
    }

    #[allow(unsafe_code)]
    pub fn append_native_handler(&self, handler: &PromiseNativeHandler) {
        let cx = self.global().get_cx();
        rooted!(in(cx) let resolve_func =
                create_native_handler_function(cx,
                                               handler.reflector().get_jsobject(),
                                               NativeHandlerTask::Resolve));

        rooted!(in(cx) let reject_func =
                create_native_handler_function(cx,
                                               handler.reflector().get_jsobject(),
                                               NativeHandlerTask::Reject));

        unsafe {
            let ok = AddPromiseReactions(cx,
                                         self.promise_obj(),
                                         resolve_func.handle(),
                                         reject_func.handle());
            assert!(ok);
        }
    }
}

#[allow(unsafe_code)]
unsafe extern fn do_nothing_promise_executor(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
    let args = CallArgs::from_vp(vp, argc);
    *args.rval() = UndefinedValue();
    true
}

const SLOT_NATIVEHANDLER: usize = 0;
const SLOT_NATIVEHANDLER_TASK: usize = 1;

#[derive(PartialEq)]
enum NativeHandlerTask {
    Resolve = 0,
    Reject = 1,
}

#[allow(unsafe_code)]
unsafe extern fn native_handler_callback(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
    let args = CallArgs::from_vp(vp, argc);
    rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER));
    assert!(v.get().is_object());

    let handler = root_from_object::<PromiseNativeHandler>(v.to_object())
        .expect("unexpected value for native handler in promise native handler callback");

    rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK));
    match v.to_int32() {
        v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(cx, args.get(0)),
        v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(cx, args.get(0)),
        _ => panic!("unexpected native handler task value"),
    };

    true
}

#[allow(unsafe_code)]
fn create_native_handler_function(cx: *mut JSContext,
                                  holder: HandleObject,
                                  task: NativeHandlerTask) -> *mut JSObject {
    unsafe {
        let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
        assert!(!func.is_null());

        rooted!(in(cx) let obj = JS_GetFunctionObject(func));
        assert!(!obj.is_null());
        SetFunctionNativeReserved(obj.get(),
                                  SLOT_NATIVEHANDLER,
                                  &ObjectValue(*holder));
        SetFunctionNativeReserved(obj.get(),
                                  SLOT_NATIVEHANDLER_TASK,
                                  &Int32Value(task as i32));
        obj.get()
    }
}

<|endoftext|>