<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Akira Hayakawa <ruby.wktk@gmail.com>
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (ToDO) PREFIXaa

mod filenames;
mod platform;

use crate::filenames::FilenameFactory;
use clap::{crate_version, App, Arg, ArgMatches};
use std::convert::TryFrom;
use std::env;
use std::fs::remove_file;
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::parse_size::parse_size;

static OPT_BYTES: &str = "bytes";
static OPT_LINE_BYTES: &str = "line-bytes";
static OPT_LINES: &str = "lines";
static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix";
static OPT_FILTER: &str = "filter";
static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes";
static OPT_SUFFIX_LENGTH: &str = "suffix-length";
static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0";
static OPT_VERBOSE: &str = "verbose";

static ARG_INPUT: &str = "input";
static ARG_PREFIX: &str = "prefix";

fn usage() -> String {
    format!(
        "{0} [OPTION]... [INPUT [PREFIX]]",
        uucore::execution_phrase()
    )
}
fn get_long_usage() -> String {
    format!(
        "Usage:
  {0}

Output fixed-size pieces of INPUT to PREFIXaa, PREFIX ab, ...; default
size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is
-, read standard input.",
        usage()
    )
}

#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let usage = usage();
    let long_usage = get_long_usage();

    let matches = uu_app()
        .usage(&usage[..])
        .after_help(&long_usage[..])
        .get_matches_from(args);

    let mut settings = Settings {
        prefix: "".to_owned(),
        numeric_suffix: false,
        suffix_length: 0,
        additional_suffix: "".to_owned(),
        input: "".to_owned(),
        filter: None,
        strategy: Strategy::Lines(1000),
        verbose: false,
    };

    settings.suffix_length = matches
        .value_of(OPT_SUFFIX_LENGTH)
        .unwrap()
        .parse()
        .unwrap_or_else(|_| panic!("Invalid number for {}", OPT_SUFFIX_LENGTH));

    settings.numeric_suffix = matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0;
    settings.additional_suffix = matches.value_of(OPT_ADDITIONAL_SUFFIX).unwrap().to_owned();

    settings.verbose = matches.occurrences_of("verbose") > 0;
    settings.strategy = Strategy::from(&matches)?;
    settings.input = matches.value_of(ARG_INPUT).unwrap().to_owned();
    settings.prefix = matches.value_of(ARG_PREFIX).unwrap().to_owned();

    if matches.occurrences_of(OPT_FILTER) > 0 {
        if cfg!(windows) {
            // see https://github.com/rust-lang/rust/issues/29494
            return Err(USimpleError::new(
                -1,
                format!("{} is currently not supported in this platform", OPT_FILTER),
            ));
        } else {
            settings.filter = Some(matches.value_of(OPT_FILTER).unwrap().to_owned());
        }
    }

    split(settings)
}

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .version(crate_version!())
        .about("Create output files containing consecutive or interleaved sections of input")
        // strategy (mutually exclusive)
        .arg(
            Arg::with_name(OPT_BYTES)
                .short("b")
                .long(OPT_BYTES)
                .takes_value(true)
                .help("put SIZE bytes per output file"),
        )
        .arg(
            Arg::with_name(OPT_LINE_BYTES)
                .short("C")
                .long(OPT_LINE_BYTES)
                .takes_value(true)
                .default_value("2")
                .help("put at most SIZE bytes of lines per output file"),
        )
        .arg(
            Arg::with_name(OPT_LINES)
                .short("l")
                .long(OPT_LINES)
                .takes_value(true)
                .default_value("1000")
                .help("put NUMBER lines/records per output file"),
        )
        // rest of the arguments
        .arg(
            Arg::with_name(OPT_ADDITIONAL_SUFFIX)
                .long(OPT_ADDITIONAL_SUFFIX)
                .takes_value(true)
                .default_value("")
                .help("additional suffix to append to output file names"),
        )
        .arg(
            Arg::with_name(OPT_FILTER)
                .long(OPT_FILTER)
                .takes_value(true)
                .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"),
        )
        .arg(
            Arg::with_name(OPT_NUMERIC_SUFFIXES)
                .short("d")
                .long(OPT_NUMERIC_SUFFIXES)
                .takes_value(true)
                .default_value("0")
                .help("use numeric suffixes instead of alphabetic"),
        )
        .arg(
            Arg::with_name(OPT_SUFFIX_LENGTH)
                .short("a")
                .long(OPT_SUFFIX_LENGTH)
                .takes_value(true)
                .default_value(OPT_DEFAULT_SUFFIX_LENGTH)
                .help("use suffixes of length N (default 2)"),
        )
        .arg(
            Arg::with_name(OPT_VERBOSE)
                .long(OPT_VERBOSE)
                .help("print a diagnostic just before each output file is opened"),
        )
        .arg(
            Arg::with_name(ARG_INPUT)
            .takes_value(true)
            .default_value("-")
            .index(1)
        )
        .arg(
            Arg::with_name(ARG_PREFIX)
            .takes_value(true)
            .default_value("x")
            .index(2)
        )
}

/// The strategy for breaking up the input file into chunks.
enum Strategy {
    /// Each chunk has the specified number of lines.
    Lines(usize),

    /// Each chunk has the specified number of bytes.
    Bytes(usize),

    /// Each chunk has as many lines as possible without exceeding the
    /// specified number of bytes.
    LineBytes(usize),
}

impl Strategy {
    /// Parse a strategy from the command-line arguments.
    fn from(matches: &ArgMatches) -> UResult<Self> {
        // Check that the user is not specifying more than one strategy.
        //
        // Note: right now, this exact behavior cannot be handled by
        // `ArgGroup` since `ArgGroup` considers a default value `Arg`
        // as "defined".
        match (
            matches.occurrences_of(OPT_LINES),
            matches.occurrences_of(OPT_BYTES),
            matches.occurrences_of(OPT_LINE_BYTES),
        ) {
            (0, 0, 0) => Ok(Strategy::Lines(1000)),
            (1, 0, 0) => {
                let s = matches.value_of(OPT_LINES).unwrap();
                let n = parse_size(s)
                    .map_err(|e| USimpleError::new(1, format!("invalid number of lines: {}", e)))?;
                Ok(Strategy::Lines(n))
            }
            (0, 1, 0) => {
                let s = matches.value_of(OPT_BYTES).unwrap();
                let n = parse_size(s)
                    .map_err(|e| USimpleError::new(1, format!("invalid number of bytes: {}", e)))?;
                Ok(Strategy::Bytes(n))
            }
            (0, 0, 1) => {
                let s = matches.value_of(OPT_LINE_BYTES).unwrap();
                let n = parse_size(s)
                    .map_err(|e| USimpleError::new(1, format!("invalid number of bytes: {}", e)))?;
                Ok(Strategy::LineBytes(n))
            }
            _ => Err(UUsageError::new(1, "cannot split in more than one way")),
        }
    }
}

struct Settings {
    prefix: String,
    numeric_suffix: bool,
    suffix_length: usize,
    additional_suffix: String,
    input: String,
    /// When supplied, a shell command to output to instead of xaa, xab …
    filter: Option<String>,
    strategy: Strategy,
    verbose: bool,
}

trait Splitter {
    // Consume as much as possible from `reader` so as to saturate `writer`.
    // Equivalent to finishing one of the part files. Returns the number of
    // bytes that have been moved.
    fn consume(
        &mut self,
        reader: &mut BufReader<Box<dyn Read>>,
        writer: &mut BufWriter<Box<dyn Write>>,
    ) -> std::io::Result<u128>;
}

struct LineSplitter {
    lines_per_split: usize,
}

impl LineSplitter {
    fn new(chunk_size: usize) -> LineSplitter {
        LineSplitter {
            lines_per_split: chunk_size,
        }
    }
}

impl Splitter for LineSplitter {
    fn consume(
        &mut self,
        reader: &mut BufReader<Box<dyn Read>>,
        writer: &mut BufWriter<Box<dyn Write>>,
    ) -> std::io::Result<u128> {
        let mut bytes_consumed = 0u128;
        let mut buffer = String::with_capacity(1024);
        for _ in 0..self.lines_per_split {
            let bytes_read = reader.read_line(&mut buffer)?;
            // If we ever read 0 bytes then we know we've hit EOF.
            if bytes_read == 0 {
                return Ok(bytes_consumed);
            }

            writer.write_all(buffer.as_bytes())?;
            // Empty out the String buffer since `read_line` appends instead of
            // replaces.
            buffer.clear();

            bytes_consumed += bytes_read as u128;
        }

        Ok(bytes_consumed)
    }
}

struct ByteSplitter {
    bytes_per_split: u128,
}

impl ByteSplitter {
    fn new(chunk_size: usize) -> ByteSplitter {
        ByteSplitter {
            bytes_per_split: u128::try_from(chunk_size).unwrap(),
        }
    }
}

impl Splitter for ByteSplitter {
    fn consume(
        &mut self,
        reader: &mut BufReader<Box<dyn Read>>,
        writer: &mut BufWriter<Box<dyn Write>>,
    ) -> std::io::Result<u128> {
        // We buffer reads and writes. We proceed until `bytes_consumed` is
        // equal to `self.bytes_per_split` or we reach EOF.
        let mut bytes_consumed = 0u128;
        const BUFFER_SIZE: usize = 1024;
        let mut buffer = [0u8; BUFFER_SIZE];
        while bytes_consumed < self.bytes_per_split {
            // Don't overshoot `self.bytes_per_split`! Note: Using std::cmp::min
            // doesn't really work since we have to get types to match which
            // can't be done in a way that keeps all conversions safe.
            let bytes_desired = if (BUFFER_SIZE as u128) <= self.bytes_per_split - bytes_consumed {
                BUFFER_SIZE
            } else {
                // This is a safe conversion since the difference must be less
                // than BUFFER_SIZE in this branch.
                (self.bytes_per_split - bytes_consumed) as usize
            };
            let bytes_read = reader.read(&mut buffer[0..bytes_desired])?;
            // If we ever read 0 bytes then we know we've hit EOF.
            if bytes_read == 0 {
                return Ok(bytes_consumed);
            }

            writer.write_all(&buffer[0..bytes_read])?;

            bytes_consumed += bytes_read as u128;
        }

        Ok(bytes_consumed)
    }
}

fn split(settings: Settings) -> UResult<()> {
    let mut reader = BufReader::new(if settings.input == "-" {
        Box::new(stdin()) as Box<dyn Read>
    } else {
        let r = File::open(Path::new(&settings.input)).map_err_context(|| {
            format!(
                "cannot open {} for reading: No such file or directory",
                settings.input.quote()
            )
        })?;
        Box::new(r) as Box<dyn Read>
    });

    let mut splitter: Box<dyn Splitter> = match settings.strategy {
        Strategy::Lines(chunk_size) => Box::new(LineSplitter::new(chunk_size)),
        Strategy::Bytes(chunk_size) | Strategy::LineBytes(chunk_size) => {
            Box::new(ByteSplitter::new(chunk_size))
        }
    };

    // This object is responsible for creating the filename for each chunk.
    let filename_factory = FilenameFactory::new(
        settings.prefix,
        settings.additional_suffix,
        settings.suffix_length,
        settings.numeric_suffix,
    );
    let mut fileno = 0;
    loop {
        // Get a new part file set up, and construct `writer` for it.
        let filename = filename_factory
            .make(fileno)
            .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
        let mut writer = platform::instantiate_current_writer(&settings.filter, filename.as_str());

        let bytes_consumed = splitter
            .consume(&mut reader, &mut writer)
            .map_err_context(|| "input/output error".to_string())?;
        writer
            .flush()
            .map_err_context(|| "error flushing to output file".to_string())?;

        // If we didn't write anything we should clean up the empty file, and
        // break from the loop.
        if bytes_consumed == 0 {
            // The output file is only ever created if --filter isn't used.
            // Complicated, I know...
            if settings.filter.is_none() {
                remove_file(filename)
                    .map_err_context(|| "error removing empty file".to_string())?;
            }
            break;
        }

        // TODO It is silly to have the "creating file" message here
        // after the file has been already created. However, because
        // of the way the main loop has been written, an extra file
        // gets created and then deleted in the last iteration of the
        // loop. So we need to make sure we are not in that case when
        // printing this message.
        //
        // This is only here temporarily while we make some
        // improvements to the architecture of the main loop in this
        // function. In the future, it will move to a more appropriate
        // place---at the point where the file is actually created.
        if settings.verbose {
            println!("creating file {}", filename.quote());
        }

        fileno += 1;
    }
    Ok(())
}
<commit_msg>split: method to convert ArgMatches to Settings<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Akira Hayakawa <ruby.wktk@gmail.com>
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (ToDO) PREFIXaa

mod filenames;
mod platform;

use crate::filenames::FilenameFactory;
use clap::{crate_version, App, Arg, ArgMatches};
use std::convert::TryFrom;
use std::env;
use std::fs::remove_file;
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::parse_size::parse_size;

static OPT_BYTES: &str = "bytes";
static OPT_LINE_BYTES: &str = "line-bytes";
static OPT_LINES: &str = "lines";
static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix";
static OPT_FILTER: &str = "filter";
static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes";
static OPT_SUFFIX_LENGTH: &str = "suffix-length";
static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0";
static OPT_VERBOSE: &str = "verbose";

static ARG_INPUT: &str = "input";
static ARG_PREFIX: &str = "prefix";

fn usage() -> String {
    format!(
        "{0} [OPTION]... [INPUT [PREFIX]]",
        uucore::execution_phrase()
    )
}
fn get_long_usage() -> String {
    format!(
        "Usage:
  {0}

Output fixed-size pieces of INPUT to PREFIXaa, PREFIX ab, ...; default
size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is
-, read standard input.",
        usage()
    )
}

#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let usage = usage();
    let long_usage = get_long_usage();
    let matches = uu_app()
        .usage(&usage[..])
        .after_help(&long_usage[..])
        .get_matches_from(args);
    let settings = Settings::from(matches)?;
    split(settings)
}

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .version(crate_version!())
        .about("Create output files containing consecutive or interleaved sections of input")
        // strategy (mutually exclusive)
        .arg(
            Arg::with_name(OPT_BYTES)
                .short("b")
                .long(OPT_BYTES)
                .takes_value(true)
                .help("put SIZE bytes per output file"),
        )
        .arg(
            Arg::with_name(OPT_LINE_BYTES)
                .short("C")
                .long(OPT_LINE_BYTES)
                .takes_value(true)
                .default_value("2")
                .help("put at most SIZE bytes of lines per output file"),
        )
        .arg(
            Arg::with_name(OPT_LINES)
                .short("l")
                .long(OPT_LINES)
                .takes_value(true)
                .default_value("1000")
                .help("put NUMBER lines/records per output file"),
        )
        // rest of the arguments
        .arg(
            Arg::with_name(OPT_ADDITIONAL_SUFFIX)
                .long(OPT_ADDITIONAL_SUFFIX)
                .takes_value(true)
                .default_value("")
                .help("additional suffix to append to output file names"),
        )
        .arg(
            Arg::with_name(OPT_FILTER)
                .long(OPT_FILTER)
                .takes_value(true)
                .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"),
        )
        .arg(
            Arg::with_name(OPT_NUMERIC_SUFFIXES)
                .short("d")
                .long(OPT_NUMERIC_SUFFIXES)
                .takes_value(true)
                .default_value("0")
                .help("use numeric suffixes instead of alphabetic"),
        )
        .arg(
            Arg::with_name(OPT_SUFFIX_LENGTH)
                .short("a")
                .long(OPT_SUFFIX_LENGTH)
                .takes_value(true)
                .default_value(OPT_DEFAULT_SUFFIX_LENGTH)
                .help("use suffixes of length N (default 2)"),
        )
        .arg(
            Arg::with_name(OPT_VERBOSE)
                .long(OPT_VERBOSE)
                .help("print a diagnostic just before each output file is opened"),
        )
        .arg(
            Arg::with_name(ARG_INPUT)
            .takes_value(true)
            .default_value("-")
            .index(1)
        )
        .arg(
            Arg::with_name(ARG_PREFIX)
            .takes_value(true)
            .default_value("x")
            .index(2)
        )
}

/// The strategy for breaking up the input file into chunks.
enum Strategy {
    /// Each chunk has the specified number of lines.
    Lines(usize),

    /// Each chunk has the specified number of bytes.
    Bytes(usize),

    /// Each chunk has as many lines as possible without exceeding the
    /// specified number of bytes.
    LineBytes(usize),
}

impl Strategy {
    /// Parse a strategy from the command-line arguments.
    fn from(matches: &ArgMatches) -> UResult<Self> {
        // Check that the user is not specifying more than one strategy.
        //
        // Note: right now, this exact behavior cannot be handled by
        // `ArgGroup` since `ArgGroup` considers a default value `Arg`
        // as "defined".
        match (
            matches.occurrences_of(OPT_LINES),
            matches.occurrences_of(OPT_BYTES),
            matches.occurrences_of(OPT_LINE_BYTES),
        ) {
            (0, 0, 0) => Ok(Strategy::Lines(1000)),
            (1, 0, 0) => {
                let s = matches.value_of(OPT_LINES).unwrap();
                let n = parse_size(s)
                    .map_err(|e| USimpleError::new(1, format!("invalid number of lines: {}", e)))?;
                Ok(Strategy::Lines(n))
            }
            (0, 1, 0) => {
                let s = matches.value_of(OPT_BYTES).unwrap();
                let n = parse_size(s)
                    .map_err(|e| USimpleError::new(1, format!("invalid number of bytes: {}", e)))?;
                Ok(Strategy::Bytes(n))
            }
            (0, 0, 1) => {
                let s = matches.value_of(OPT_LINE_BYTES).unwrap();
                let n = parse_size(s)
                    .map_err(|e| USimpleError::new(1, format!("invalid number of bytes: {}", e)))?;
                Ok(Strategy::LineBytes(n))
            }
            _ => Err(UUsageError::new(1, "cannot split in more than one way")),
        }
    }
}

/// Parameters that control how a file gets split.
///
/// You can convert an [`ArgMatches`] instance into a [`Settings`]
/// instance by calling [`Settings::from`].
struct Settings {
    prefix: String,
    numeric_suffix: bool,
    suffix_length: usize,
    additional_suffix: String,
    input: String,
    /// When supplied, a shell command to output to instead of xaa, xab …
    filter: Option<String>,
    strategy: Strategy,
    verbose: bool,
}

impl Settings {
    /// Parse a strategy from the command-line arguments.
    fn from(matches: ArgMatches) -> UResult<Self> {
        let result = Settings {
            suffix_length: matches
                .value_of(OPT_SUFFIX_LENGTH)
                .unwrap()
                .parse()
                .unwrap_or_else(|_| panic!("Invalid number for {}", OPT_SUFFIX_LENGTH)),
            numeric_suffix: matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0,
            additional_suffix: matches.value_of(OPT_ADDITIONAL_SUFFIX).unwrap().to_owned(),
            verbose: matches.occurrences_of("verbose") > 0,
            strategy: Strategy::from(&matches)?,
            input: matches.value_of(ARG_INPUT).unwrap().to_owned(),
            prefix: matches.value_of(ARG_PREFIX).unwrap().to_owned(),
            filter: matches.value_of(OPT_FILTER).map(|s| s.to_owned()),
        };
        #[cfg(windows)]
        if result.filter.is_some() {
            // see https://github.com/rust-lang/rust/issues/29494
            return Err(USimpleError::new(
                -1,
                format!("{} is currently not supported in this platform", OPT_FILTER),
            ));
        }

        Ok(result)
    }
}

trait Splitter {
    // Consume as much as possible from `reader` so as to saturate `writer`.
    // Equivalent to finishing one of the part files. Returns the number of
    // bytes that have been moved.
    fn consume(
        &mut self,
        reader: &mut BufReader<Box<dyn Read>>,
        writer: &mut BufWriter<Box<dyn Write>>,
    ) -> std::io::Result<u128>;
}

struct LineSplitter {
    lines_per_split: usize,
}

impl LineSplitter {
    fn new(chunk_size: usize) -> LineSplitter {
        LineSplitter {
            lines_per_split: chunk_size,
        }
    }
}

impl Splitter for LineSplitter {
    fn consume(
        &mut self,
        reader: &mut BufReader<Box<dyn Read>>,
        writer: &mut BufWriter<Box<dyn Write>>,
    ) -> std::io::Result<u128> {
        let mut bytes_consumed = 0u128;
        let mut buffer = String::with_capacity(1024);
        for _ in 0..self.lines_per_split {
            let bytes_read = reader.read_line(&mut buffer)?;
            // If we ever read 0 bytes then we know we've hit EOF.
            if bytes_read == 0 {
                return Ok(bytes_consumed);
            }

            writer.write_all(buffer.as_bytes())?;
            // Empty out the String buffer since `read_line` appends instead of
            // replaces.
            buffer.clear();

            bytes_consumed += bytes_read as u128;
        }

        Ok(bytes_consumed)
    }
}

struct ByteSplitter {
    bytes_per_split: u128,
}

impl ByteSplitter {
    fn new(chunk_size: usize) -> ByteSplitter {
        ByteSplitter {
            bytes_per_split: u128::try_from(chunk_size).unwrap(),
        }
    }
}

impl Splitter for ByteSplitter {
    fn consume(
        &mut self,
        reader: &mut BufReader<Box<dyn Read>>,
        writer: &mut BufWriter<Box<dyn Write>>,
    ) -> std::io::Result<u128> {
        // We buffer reads and writes. We proceed until `bytes_consumed` is
        // equal to `self.bytes_per_split` or we reach EOF.
        let mut bytes_consumed = 0u128;
        const BUFFER_SIZE: usize = 1024;
        let mut buffer = [0u8; BUFFER_SIZE];
        while bytes_consumed < self.bytes_per_split {
            // Don't overshoot `self.bytes_per_split`! Note: Using std::cmp::min
            // doesn't really work since we have to get types to match which
            // can't be done in a way that keeps all conversions safe.
            let bytes_desired = if (BUFFER_SIZE as u128) <= self.bytes_per_split - bytes_consumed {
                BUFFER_SIZE
            } else {
                // This is a safe conversion since the difference must be less
                // than BUFFER_SIZE in this branch.
                (self.bytes_per_split - bytes_consumed) as usize
            };
            let bytes_read = reader.read(&mut buffer[0..bytes_desired])?;
            // If we ever read 0 bytes then we know we've hit EOF.
            if bytes_read == 0 {
                return Ok(bytes_consumed);
            }

            writer.write_all(&buffer[0..bytes_read])?;

            bytes_consumed += bytes_read as u128;
        }

        Ok(bytes_consumed)
    }
}

fn split(settings: Settings) -> UResult<()> {
    let mut reader = BufReader::new(if settings.input == "-" {
        Box::new(stdin()) as Box<dyn Read>
    } else {
        let r = File::open(Path::new(&settings.input)).map_err_context(|| {
            format!(
                "cannot open {} for reading: No such file or directory",
                settings.input.quote()
            )
        })?;
        Box::new(r) as Box<dyn Read>
    });

    let mut splitter: Box<dyn Splitter> = match settings.strategy {
        Strategy::Lines(chunk_size) => Box::new(LineSplitter::new(chunk_size)),
        Strategy::Bytes(chunk_size) | Strategy::LineBytes(chunk_size) => {
            Box::new(ByteSplitter::new(chunk_size))
        }
    };

    // This object is responsible for creating the filename for each chunk.
    let filename_factory = FilenameFactory::new(
        settings.prefix,
        settings.additional_suffix,
        settings.suffix_length,
        settings.numeric_suffix,
    );
    let mut fileno = 0;
    loop {
        // Get a new part file set up, and construct `writer` for it.
        let filename = filename_factory
            .make(fileno)
            .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
        let mut writer = platform::instantiate_current_writer(&settings.filter, filename.as_str());

        let bytes_consumed = splitter
            .consume(&mut reader, &mut writer)
            .map_err_context(|| "input/output error".to_string())?;
        writer
            .flush()
            .map_err_context(|| "error flushing to output file".to_string())?;

        // If we didn't write anything we should clean up the empty file, and
        // break from the loop.
        if bytes_consumed == 0 {
            // The output file is only ever created if --filter isn't used.
            // Complicated, I know...
            if settings.filter.is_none() {
                remove_file(filename)
                    .map_err_context(|| "error removing empty file".to_string())?;
            }
            break;
        }

        // TODO It is silly to have the "creating file" message here
        // after the file has been already created. However, because
        // of the way the main loop has been written, an extra file
        // gets created and then deleted in the last iteration of the
        // loop. So we need to make sure we are not in that case when
        // printing this message.
        //
        // This is only here temporarily while we make some
        // improvements to the architecture of the main loop in this
        // function. In the future, it will move to a more appropriate
        // place---at the point where the file is actually created.
        if settings.verbose {
            println!("creating file {}", filename.quote());
        }

        fileno += 1;
    }
    Ok(())
}
<|endoftext|>


<commit_before>//! Execute benchmarks in a sysroot.

use std::env;
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use std::str;
use std::f64;
use std::fs::{self, File};
use std::collections::{HashSet, HashMap};

use tempdir::TempDir;

use collector::{Patch, BenchmarkState, Run, Stat, Benchmark as BenchmarkData};

use cargo_metadata;
use errors::{Result, ResultExt};
use rust_sysroot::sysroot::Sysroot;
use serde_json;

#[derive(Debug, Clone, Default, Deserialize)]
struct BenchmarkConfig {
    cargo_opts: Option<String>,
    cargo_rustc_opts: Option<String>,
    cargo_toml: Option<PathBuf>,
}

pub struct Benchmark {
    pub name: String,
    pub path: PathBuf,
    patches: Vec<Patch>,
    config: BenchmarkConfig,
}

impl Benchmark {
    pub fn new(name: String, path: PathBuf) -> Result<Self> {
        let mut patches = vec![];
        for entry in fs::read_dir(&path)? {
            let entry = entry?;
            let path = entry.path();
            if let Some(ext) = path.extension() {
                if ext == "patch" {
                    patches.push(path.clone());
                }
            }
        }

        patches.sort();

        let patches = patches.into_iter().map(|p| Patch::new(p)).collect();

        let config_path = path.join("perf-config.json");
        let config: BenchmarkConfig = if config_path.exists() {
            serde_json::from_reader(File::open(&config_path)?)?
        } else {
            BenchmarkConfig::default()
        };

        Ok(Benchmark {
            name, path, patches, config
        })
    }

    pub fn command<P: AsRef<Path>>(&self, sysroot: &Sysroot, path: P) -> Command {
        let mut command = sysroot.command(path);
        command.current_dir(&self.path);
        command
    }

    fn make_temp_dir(&self, sysroot: &Sysroot) -> Result<TempDir> {
        let tmp_dir = TempDir::new(&format!("rustc-benchmark-{}", self.name))?;
        info!("temporary directory is {}", tmp_dir.path().display());

        info!("copying files to temporary directory");
        let output = self.command(sysroot, "cp")
        .arg("-r")
        .arg("-T")
        .arg("--")
        .arg(".")
        .arg(tmp_dir.path())
        .output()?;

        if !output.status.success() {
            bail!("copy failed: {}", String::from_utf8_lossy(&output.stderr));
        }
        Ok(tmp_dir)
    }

    /// Run a specific benchmark on a specific commit
    pub fn run(&self, sysroot: &Sysroot) -> Result<BenchmarkData> {
        info!("processing {}", self.name);

        let has_perf = Command::new("perf").output().is_ok();
        let mut fake_rustc = env::current_exe().unwrap();
        fake_rustc.pop();
        fake_rustc.push("rustc-fake");

        let cargo_toml = self.config.cargo_toml.clone().unwrap_or(PathBuf::from("Cargo.toml"));
        let mut metadata = cargo_metadata::metadata_deps(Some(&self.path.join(cargo_toml)), true)?;
        assert_eq!(metadata.workspace_members.len(), 1,
            "Only one workspace member for {:?}", self.path);
        let package_id = metadata.workspace_members.pop().unwrap();
        let package = metadata.packages.into_iter().find(|p| p.id == package_id).unwrap();
        assert_eq!(package.targets.len(), 1, "Only one target for {}", package.name);

        let mut ret = BenchmarkData {
            name: self.name.clone(),
            runs: Vec::new(),
        };

        for opt in &[false, true] {
            let mut clean_stats = Vec::new();
            let mut incr_stats = Vec::new();
            let mut incr_clean_stats = Vec::new();
            let mut incr_patched_stats: Vec<(Patch, Vec<Vec<Stat>>)> = vec![];

            let mut standard_args = vec!["rustc".to_string()];
            standard_args.extend(
                self.config.cargo_opts.clone()
                    .map(|s| s.split_whitespace()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>())
                    .unwrap_or_default());
            standard_args.extend(vec![
                "-p".to_string(),
                package_id.clone(),
            ]);
            if self.config.cargo_opts.as_ref().map_or(true, |s| !s.contains("feature")) {
                standard_args.push("--all-features".to_string());
            }
            if *opt {
                standard_args.push("--release".to_string());
            }

            standard_args.push("--".to_string());
            standard_args.extend(
                self.config.cargo_rustc_opts.clone()
                    .map(|s| s.split_whitespace()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>())
                    .unwrap_or_default());
            standard_args.push("-Ztime-passes".to_string());
            for _ in 0..3 {
                let tmp_dir = self.make_temp_dir(sysroot)?;
                let cargo_no_args = |incremental: bool| {
                    let mut cargo = sysroot.command("cargo");
                    cargo
                        .current_dir(tmp_dir.path())
                        .env("RUSTC", &fake_rustc)
                        .env("RUSTC_REAL", &sysroot.rustc)
                        .env("CARGO_INCREMENTAL", &format!("{}", incremental as usize));

                    if has_perf {
                        cargo.env("USE_PERF", "1");
                    }

                    cargo
                };
                let cargo = |incremental: bool| {
                    let mut cargo = cargo_no_args(incremental);
                    cargo.args(&standard_args);
                    cargo
                };

                let run = |mut cmd: Command| -> Result<process::Output> {
                    info!("running: {:?}", cmd);
                    let output = cmd.output()?;
                    if !output.status.success() {
                        bail!(
                            "expected success, got {}\n\nstderr={}\n\n stdout={}",
                            output.status,
                            String::from_utf8_lossy(&output.stderr),
                            String::from_utf8_lossy(&output.stdout)
                        );
                    }
                    Ok(output)
                };

                let touch_all = || -> Result<()> {
                    let mut cmd = sysroot.command("bash");
                    cmd.current_dir(tmp_dir.path())
                        .args(&["-c", "find . -name *.rs | xargs touch"]);
                    run(cmd)?;
                    Ok(())
                };

                // Only one thing is required from each benchmark: a println.diff file which adds a
                // println! statement to a random function. All benchmarks are uniform in this.

                // prebuild the dependencies without recording timing information
                {
                    let mut cargo = cargo(false);
                    cargo.env("USE_PERF", "0");
                    run(cargo)?;
                    let mut cargo = cargo_no_args(false);
                    cargo.args(&["clean", "-p", &package_id]);
                    run(cargo)?;
                }

                clean_stats.push(process_output(&self.name, run(cargo(false))?.stdout)?);
                touch_all()?;
                incr_stats.push(process_output(&self.name, run(cargo(true))?.stdout)?);
                touch_all()?;
                incr_clean_stats.push(process_output(&self.name, run(cargo(true))?.stdout)?);
                for patch in &self.patches {
                    patch.apply(tmp_dir.path())?;
                    touch_all()?;
                    if let Some(mut entry) = incr_patched_stats.iter_mut()
                        .find(|s| &s.0 == patch) {
                        entry.1.push(process_output(&self.name, run(cargo(true))?.stdout)?);
                        continue;
                    }

                    incr_patched_stats.push((
                            patch.clone(),
                            vec![process_output(&self.name, run(cargo(true))?.stdout)?]
                    ));
                }
            }

            let process_stats = |state: BenchmarkState, runs: Vec<Vec<Stat>>| -> Run {
                let mut stats: HashMap<String, Vec<f64>> = HashMap::new();
                for run in runs {
                    for stat in run {
                        stats.entry(stat.name.clone()).or_insert_with(|| Vec::new()).push(stat.cnt);
                    }
                }
                // all stats should be present in all runs
                assert_eq!(stats.values().map(|v| v.len()).collect::<HashSet<_>>().len(), 1);
                let stats = stats.into_iter()
                    .map(|(stat, counts)| {
                        Stat {
                            name: stat,
                            cnt: counts.into_iter().fold(f64::INFINITY, |acc, v| f64::min(acc, v)),
                        }
                    })
                    .collect();

                Run {
                    stats,
                    release: *opt,
                    state: state,
                }
            };

            ret.runs.push(process_stats(BenchmarkState::Clean, clean_stats));
            ret.runs.push(process_stats(BenchmarkState::IncrementalStart, incr_stats));
            ret.runs.push(process_stats(BenchmarkState::IncrementalClean, incr_clean_stats));

            for (patch, results) in incr_patched_stats {
                ret.runs.push(process_stats(
                    BenchmarkState::IncrementalPatched(patch),
                    results,
                ));
            }
        }

        Ok(ret)
    }
}

fn process_output(name: &str, output: Vec<u8>) -> Result<Vec<Stat>> {
    let output = String::from_utf8(output)
        .chain_err(|| format!("unable to convert output of {} to UTF-8", name))?;
    let mut stats = Vec::new();

    for line in output.lines() {
        // https://github.com/torvalds/linux/blob/bc78d646e708/tools/perf/Documentation/perf-stat.txt#L281
        macro_rules! get {
            ($e:expr) => (match $e {
                Some(s) => s,
                None => {
                    warn!("unhandled line: {}", line);
                    continue;
                },
            });
        }
        let mut parts = line.split(';').map(|s| s.trim());
        let cnt = get!(parts.next());
        let _unit = get!(parts.next());
        let name = get!(parts.next());
        let _time = get!(parts.next());
        let pct = get!(parts.next());
        if cnt == "<not supported>" {
            continue;
        }
        if !pct.starts_with("100.") {
            panic!(
                "measurement of `{}` only active for {}% of the time",
                name,
                pct
            );
        }
        stats.push(Stat {
            name: name.to_string(),
            cnt: cnt.parse().chain_err(|| format!("failed to parse `{}` as an float", cnt))?,
        });
    }

    Ok(stats)
}
<commit_msg>Use `--cap-lints warn` to ignore `#[deny(warnings)]` in benchmarks<commit_after>//! Execute benchmarks in a sysroot.

use std::env;
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use std::str;
use std::f64;
use std::fs::{self, File};
use std::collections::{HashSet, HashMap};

use tempdir::TempDir;

use collector::{Patch, BenchmarkState, Run, Stat, Benchmark as BenchmarkData};

use cargo_metadata;
use errors::{Result, ResultExt};
use rust_sysroot::sysroot::Sysroot;
use serde_json;

#[derive(Debug, Clone, Default, Deserialize)]
struct BenchmarkConfig {
    cargo_opts: Option<String>,
    cargo_rustc_opts: Option<String>,
    cargo_toml: Option<PathBuf>,
}

pub struct Benchmark {
    pub name: String,
    pub path: PathBuf,
    patches: Vec<Patch>,
    config: BenchmarkConfig,
}

impl Benchmark {
    pub fn new(name: String, path: PathBuf) -> Result<Self> {
        let mut patches = vec![];
        for entry in fs::read_dir(&path)? {
            let entry = entry?;
            let path = entry.path();
            if let Some(ext) = path.extension() {
                if ext == "patch" {
                    patches.push(path.clone());
                }
            }
        }

        patches.sort();

        let patches = patches.into_iter().map(|p| Patch::new(p)).collect();

        let config_path = path.join("perf-config.json");
        let config: BenchmarkConfig = if config_path.exists() {
            serde_json::from_reader(File::open(&config_path)?)?
        } else {
            BenchmarkConfig::default()
        };

        Ok(Benchmark {
            name, path, patches, config
        })
    }

    pub fn command<P: AsRef<Path>>(&self, sysroot: &Sysroot, path: P) -> Command {
        let mut command = sysroot.command(path);
        command.current_dir(&self.path);
        command
    }

    fn make_temp_dir(&self, sysroot: &Sysroot) -> Result<TempDir> {
        let tmp_dir = TempDir::new(&format!("rustc-benchmark-{}", self.name))?;
        info!("temporary directory is {}", tmp_dir.path().display());

        info!("copying files to temporary directory");
        let output = self.command(sysroot, "cp")
        .arg("-r")
        .arg("-T")
        .arg("--")
        .arg(".")
        .arg(tmp_dir.path())
        .output()?;

        if !output.status.success() {
            bail!("copy failed: {}", String::from_utf8_lossy(&output.stderr));
        }
        Ok(tmp_dir)
    }

    /// Run a specific benchmark on a specific commit
    pub fn run(&self, sysroot: &Sysroot) -> Result<BenchmarkData> {
        info!("processing {}", self.name);

        let has_perf = Command::new("perf").output().is_ok();
        let mut fake_rustc = env::current_exe().unwrap();
        fake_rustc.pop();
        fake_rustc.push("rustc-fake");

        let cargo_toml = self.config.cargo_toml.clone().unwrap_or(PathBuf::from("Cargo.toml"));
        let mut metadata = cargo_metadata::metadata_deps(Some(&self.path.join(cargo_toml)), true)?;
        assert_eq!(metadata.workspace_members.len(), 1,
            "Only one workspace member for {:?}", self.path);
        let package_id = metadata.workspace_members.pop().unwrap();
        let package = metadata.packages.into_iter().find(|p| p.id == package_id).unwrap();
        assert_eq!(package.targets.len(), 1, "Only one target for {}", package.name);

        let mut ret = BenchmarkData {
            name: self.name.clone(),
            runs: Vec::new(),
        };

        for opt in &[false, true] {
            let mut clean_stats = Vec::new();
            let mut incr_stats = Vec::new();
            let mut incr_clean_stats = Vec::new();
            let mut incr_patched_stats: Vec<(Patch, Vec<Vec<Stat>>)> = vec![];

            let mut standard_args = vec!["rustc".to_string()];
            standard_args.extend(
                self.config.cargo_opts.clone()
                    .map(|s| s.split_whitespace()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>())
                    .unwrap_or_default());
            standard_args.extend(vec![
                "-p".to_string(),
                package_id.clone(),
            ]);
            if self.config.cargo_opts.as_ref().map_or(true, |s| !s.contains("feature")) {
                standard_args.push("--all-features".to_string());
            }
            if *opt {
                standard_args.push("--release".to_string());
            }

            standard_args.push("--".to_string());
            standard_args.extend(
                self.config.cargo_rustc_opts.clone()
                    .map(|s| s.split_whitespace()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>())
                    .unwrap_or_default());
            standard_args.push("-Ztime-passes".to_string());
            for _ in 0..3 {
                let tmp_dir = self.make_temp_dir(sysroot)?;
                let cargo_no_args = |incremental: bool| {
                    let mut cargo = sysroot.command("cargo");
                    cargo
                        .current_dir(tmp_dir.path())
                        .env("RUSTFLAGS", "--cap-lints warn")
                        .env("RUSTC", &fake_rustc)
                        .env("RUSTC_REAL", &sysroot.rustc)
                        .env("CARGO_INCREMENTAL", &format!("{}", incremental as usize));

                    if has_perf {
                        cargo.env("USE_PERF", "1");
                    }

                    cargo
                };
                let cargo = |incremental: bool| {
                    let mut cargo = cargo_no_args(incremental);
                    cargo.args(&standard_args);
                    cargo
                };

                let run = |mut cmd: Command| -> Result<process::Output> {
                    info!("running: {:?}", cmd);
                    let output = cmd.output()?;
                    if !output.status.success() {
                        bail!(
                            "expected success, got {}\n\nstderr={}\n\n stdout={}",
                            output.status,
                            String::from_utf8_lossy(&output.stderr),
                            String::from_utf8_lossy(&output.stdout)
                        );
                    }
                    Ok(output)
                };

                let touch_all = || -> Result<()> {
                    let mut cmd = sysroot.command("bash");
                    cmd.current_dir(tmp_dir.path())
                        .args(&["-c", "find . -name *.rs | xargs touch"]);
                    run(cmd)?;
                    Ok(())
                };

                // Only one thing is required from each benchmark: a println.diff file which adds a
                // println! statement to a random function. All benchmarks are uniform in this.

                // prebuild the dependencies without recording timing information
                {
                    let mut cargo = cargo(false);
                    cargo.env("USE_PERF", "0");
                    run(cargo)?;
                    let mut cargo = cargo_no_args(false);
                    cargo.args(&["clean", "-p", &package_id]);
                    run(cargo)?;
                }

                clean_stats.push(process_output(&self.name, run(cargo(false))?.stdout)?);
                touch_all()?;
                incr_stats.push(process_output(&self.name, run(cargo(true))?.stdout)?);
                touch_all()?;
                incr_clean_stats.push(process_output(&self.name, run(cargo(true))?.stdout)?);
                for patch in &self.patches {
                    patch.apply(tmp_dir.path())?;
                    touch_all()?;
                    if let Some(mut entry) = incr_patched_stats.iter_mut()
                        .find(|s| &s.0 == patch) {
                        entry.1.push(process_output(&self.name, run(cargo(true))?.stdout)?);
                        continue;
                    }

                    incr_patched_stats.push((
                            patch.clone(),
                            vec![process_output(&self.name, run(cargo(true))?.stdout)?]
                    ));
                }
            }

            let process_stats = |state: BenchmarkState, runs: Vec<Vec<Stat>>| -> Run {
                let mut stats: HashMap<String, Vec<f64>> = HashMap::new();
                for run in runs {
                    for stat in run {
                        stats.entry(stat.name.clone()).or_insert_with(|| Vec::new()).push(stat.cnt);
                    }
                }
                // all stats should be present in all runs
                assert_eq!(stats.values().map(|v| v.len()).collect::<HashSet<_>>().len(), 1);
                let stats = stats.into_iter()
                    .map(|(stat, counts)| {
                        Stat {
                            name: stat,
                            cnt: counts.into_iter().fold(f64::INFINITY, |acc, v| f64::min(acc, v)),
                        }
                    })
                    .collect();

                Run {
                    stats,
                    release: *opt,
                    state: state,
                }
            };

            ret.runs.push(process_stats(BenchmarkState::Clean, clean_stats));
            ret.runs.push(process_stats(BenchmarkState::IncrementalStart, incr_stats));
            ret.runs.push(process_stats(BenchmarkState::IncrementalClean, incr_clean_stats));

            for (patch, results) in incr_patched_stats {
                ret.runs.push(process_stats(
                    BenchmarkState::IncrementalPatched(patch),
                    results,
                ));
            }
        }

        Ok(ret)
    }
}

fn process_output(name: &str, output: Vec<u8>) -> Result<Vec<Stat>> {
    let output = String::from_utf8(output)
        .chain_err(|| format!("unable to convert output of {} to UTF-8", name))?;
    let mut stats = Vec::new();

    for line in output.lines() {
        // https://github.com/torvalds/linux/blob/bc78d646e708/tools/perf/Documentation/perf-stat.txt#L281
        macro_rules! get {
            ($e:expr) => (match $e {
                Some(s) => s,
                None => {
                    warn!("unhandled line: {}", line);
                    continue;
                },
            });
        }
        let mut parts = line.split(';').map(|s| s.trim());
        let cnt = get!(parts.next());
        let _unit = get!(parts.next());
        let name = get!(parts.next());
        let _time = get!(parts.next());
        let pct = get!(parts.next());
        if cnt == "<not supported>" {
            continue;
        }
        if !pct.starts_with("100.") {
            panic!(
                "measurement of `{}` only active for {}% of the time",
                name,
                pct
            );
        }
        stats.push(Stat {
            name: name.to_string(),
            cnt: cnt.parse().chain_err(|| format!("failed to parse `{}` as an float", cnt))?,
        });
    }

    Ok(stats)
}
<|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 devtools;
use devtools_traits::DevtoolScriptControlMsg;
use dom::abstractworker::WorkerScriptMsg;
use dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::DomObject;
use dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots};
use dom::bindings::str::DOMString;
use dom::dedicatedworkerglobalscope::AutoWorkerReset;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::extendableevent::ExtendableEvent;
use dom::extendablemessageevent::ExtendableMessageEvent;
use dom::globalscope::GlobalScope;
use dom::worker::TrustedWorkerAddress;
use dom::workerglobalscope::WorkerGlobalScope;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self, IpcSender, IpcReceiver};
use js::jsapi::{JSAutoCompartment, JSContext, JS_AddInterruptCallback};
use js::jsval::UndefinedValue;
use net_traits::{load_whole_resource, IpcSend, CustomResponseMediator};
use net_traits::request::{CredentialsMode, Destination, RequestInit};
use script_runtime::{CommonScriptMsg, ScriptChan, new_rt_and_cx, Runtime};
use script_traits::{TimerEvent, WorkerGlobalScopeInit, ScopeThings, ServiceWorkerMsg, WorkerScriptLoadOrigin};
use servo_channel::{channel, route_ipc_receiver_to_new_servo_sender, Receiver, Sender};
use servo_config::prefs::PREFS;
use servo_rand::random;
use servo_url::ServoUrl;
use std::thread;
use std::time::Duration;
use style::thread_state::{self, ThreadState};
use task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
use task_source::TaskSourceName;

/// Messages used to control service worker event loop
pub enum ServiceWorkerScriptMsg {
    /// Message common to all workers
    CommonWorker(WorkerScriptMsg),
    /// Message to request a custom response by the service worker
    Response(CustomResponseMediator),
    /// Wake-up call from the task queue.
    WakeUp,
}

impl QueuedTaskConversion for ServiceWorkerScriptMsg {
    fn task_source_name(&self) -> Option<&TaskSourceName> {
        let script_msg = match self {
            ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
            _ => return None,
        };
        match script_msg {
            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
                Some(&task_source)
            },
            _ => return None,
        }
    }

    fn into_queued_task(self) -> Option<QueuedTask> {
        let script_msg = match self {
            ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
            _ => return None,
        };
        let (category, boxed, pipeline_id, task_source) = match script_msg {
            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
                (category, boxed, pipeline_id, task_source)
            },
            _ => return None,
        };
        Some((None, category, boxed, pipeline_id, task_source))
    }

    fn from_queued_task(queued_task: QueuedTask) -> Self {
        let (_worker, category, boxed, pipeline_id, task_source) = queued_task;
        let script_msg = CommonScriptMsg::Task(category, boxed, pipeline_id, task_source);
        ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
    }

    fn wake_up_msg() -> Self {
        ServiceWorkerScriptMsg::WakeUp
    }

    fn is_wake_up(&self) -> bool {
        match self {
            ServiceWorkerScriptMsg::WakeUp => true,
            _ => false,
        }
    }
}

pub enum MixedMessage {
    FromServiceWorker(ServiceWorkerScriptMsg),
    FromDevtools(DevtoolScriptControlMsg),
    FromTimeoutThread(()),
}

#[derive(Clone, JSTraceable)]
pub struct ServiceWorkerChan {
    pub sender: Sender<ServiceWorkerScriptMsg>,
}

impl ScriptChan for ServiceWorkerChan {
    fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
        self.sender
            .send(ServiceWorkerScriptMsg::CommonWorker(
                WorkerScriptMsg::Common(msg),
            ))
            .map_err(|_| ())
    }

    fn clone(&self) -> Box<ScriptChan + Send> {
        Box::new(ServiceWorkerChan {
            sender: self.sender.clone(),
        })
    }
}

unsafe_no_jsmanaged_fields!(TaskQueue<ServiceWorkerScriptMsg>);

#[dom_struct]
pub struct ServiceWorkerGlobalScope {
    workerglobalscope: WorkerGlobalScope,
    #[ignore_malloc_size_of = "Defined in std"]
    task_queue: TaskQueue<ServiceWorkerScriptMsg>,
    #[ignore_malloc_size_of = "Defined in std"]
    own_sender: Sender<ServiceWorkerScriptMsg>,
    #[ignore_malloc_size_of = "Defined in std"]
    timer_event_port: Receiver<()>,
    #[ignore_malloc_size_of = "Defined in std"]
    swmanager_sender: IpcSender<ServiceWorkerMsg>,
    scope_url: ServoUrl,
}

impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
    type TimerMsg = ();
    type WorkerMsg = ServiceWorkerScriptMsg;
    type Event = MixedMessage;

    fn timer_event_port(&self) -> &Receiver<()> {
        &self.timer_event_port
    }

    fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
        &self.task_queue
    }

    fn handle_event(&self, event: MixedMessage) {
        self.handle_mixed_message(event);
    }

    fn handle_worker_post_event(&self, _worker: &TrustedWorkerAddress) -> Option<AutoWorkerReset> {
        None
    }

    fn from_worker_msg(&self, msg: ServiceWorkerScriptMsg) -> MixedMessage {
        MixedMessage::FromServiceWorker(msg)
    }

    fn from_timer_msg(&self, msg: ()) -> MixedMessage {
        MixedMessage::FromTimeoutThread(msg)
    }

    fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> MixedMessage {
        MixedMessage::FromDevtools(msg)
    }
}

impl ServiceWorkerGlobalScope {
    fn new_inherited(
        init: WorkerGlobalScopeInit,
        worker_url: ServoUrl,
        from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
        runtime: Runtime,
        own_sender: Sender<ServiceWorkerScriptMsg>,
        receiver: Receiver<ServiceWorkerScriptMsg>,
        timer_event_chan: IpcSender<TimerEvent>,
        timer_event_port: Receiver<()>,
        swmanager_sender: IpcSender<ServiceWorkerMsg>,
        scope_url: ServoUrl,
    ) -> ServiceWorkerGlobalScope {
        ServiceWorkerGlobalScope {
            workerglobalscope: WorkerGlobalScope::new_inherited(
                init,
                worker_url,
                runtime,
                from_devtools_receiver,
                timer_event_chan,
                None,
            ),
            task_queue: TaskQueue::new(receiver, own_sender.clone()),
            timer_event_port: timer_event_port,
            own_sender: own_sender,
            swmanager_sender: swmanager_sender,
            scope_url: scope_url,
        }
    }

    #[allow(unsafe_code)]
    pub fn new(
        init: WorkerGlobalScopeInit,
        worker_url: ServoUrl,
        from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
        runtime: Runtime,
        own_sender: Sender<ServiceWorkerScriptMsg>,
        receiver: Receiver<ServiceWorkerScriptMsg>,
        timer_event_chan: IpcSender<TimerEvent>,
        timer_event_port: Receiver<()>,
        swmanager_sender: IpcSender<ServiceWorkerMsg>,
        scope_url: ServoUrl,
    ) -> DomRoot<ServiceWorkerGlobalScope> {
        let cx = runtime.cx();
        let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
            init,
            worker_url,
            from_devtools_receiver,
            runtime,
            own_sender,
            receiver,
            timer_event_chan,
            timer_event_port,
            swmanager_sender,
            scope_url,
        ));
        unsafe { ServiceWorkerGlobalScopeBinding::Wrap(cx, scope) }
    }

    #[allow(unsafe_code)]
    // https://html.spec.whatwg.org/multipage/#run-a-worker
    pub fn run_serviceworker_scope(
        scope_things: ScopeThings,
        own_sender: Sender<ServiceWorkerScriptMsg>,
        receiver: Receiver<ServiceWorkerScriptMsg>,
        devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
        swmanager_sender: IpcSender<ServiceWorkerMsg>,
        scope_url: ServoUrl,
    ) {
        let ScopeThings {
            script_url,
            init,
            worker_load_origin,
            ..
        } = scope_things;

        let serialized_worker_url = script_url.to_string();
        let origin = GlobalScope::current()
            .expect("No current global object")
            .origin()
            .immutable()
            .clone();
        thread::Builder::new()
            .name(format!("ServiceWorker for {}", serialized_worker_url))
            .spawn(move || {
                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
                let roots = RootCollection::new();
                let _stack_roots = ThreadLocalStackRoots::new(&roots);

                let WorkerScriptLoadOrigin {
                    referrer_url,
                    referrer_policy,
                    pipeline_id,
                } = worker_load_origin;

                let request = RequestInit {
                    url: script_url.clone(),
                    destination: Destination::ServiceWorker,
                    credentials_mode: CredentialsMode::Include,
                    use_url_credentials: true,
                    pipeline_id: pipeline_id,
                    referrer_url: referrer_url,
                    referrer_policy: referrer_policy,
                    origin,
                    ..RequestInit::default()
                };

                let (url, source) =
                    match load_whole_resource(request, &init.resource_threads.sender()) {
                        Err(_) => {
                            println!("error loading script {}", serialized_worker_url);
                            return;
                        },
                        Ok((metadata, bytes)) => {
                            (metadata.final_url, String::from_utf8(bytes).unwrap())
                        },
                    };

                let runtime = unsafe { new_rt_and_cx() };

                let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
                route_ipc_receiver_to_new_servo_sender(devtools_receiver, devtools_mpsc_chan);
                // TODO XXXcreativcoder use this timer_ipc_port, when we have a service worker instance here
                let (timer_ipc_chan, _timer_ipc_port) = ipc::channel().unwrap();
                let (timer_chan, timer_port) = channel();
                let global = ServiceWorkerGlobalScope::new(
                    init,
                    url,
                    devtools_mpsc_port,
                    runtime,
                    own_sender,
                    receiver,
                    timer_ipc_chan,
                    timer_port,
                    swmanager_sender,
                    scope_url,
                );
                let scope = global.upcast::<WorkerGlobalScope>();

                unsafe {
                    // Handle interrupt requests
                    JS_AddInterruptCallback(scope.get_cx(), Some(interrupt_callback));
                }

                scope.execute_script(DOMString::from(source));
                // Service workers are time limited
                thread::Builder::new()
                    .name("SWTimeoutThread".to_owned())
                    .spawn(move || {
                        let sw_lifetime_timeout = PREFS
                            .get("dom.serviceworker.timeout_seconds")
                            .as_u64()
                            .unwrap();
                        thread::sleep(Duration::new(sw_lifetime_timeout, 0));
                        let _ = timer_chan.send(());
                    })
                    .expect("Thread spawning failed");

                global.dispatch_activate();
                let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
                scope
                    .upcast::<GlobalScope>()
                    .mem_profiler_chan()
                    .run_with_memory_reporting(
                        || {
                            // Step 29, Run the responsible event loop specified
                            // by inside settings until it is destroyed.
                            // The worker processing model remains on this step
                            // until the event loop is destroyed,
                            // which happens after the closing flag is set to true.
                            while !scope.is_closing() {
                                run_worker_event_loop(&*global, None);
                            }
                        },
                        reporter_name,
                        scope.script_chan(),
                        CommonScriptMsg::CollectReports,
                    );
            })
            .expect("Thread spawning failed");
    }

    fn handle_mixed_message(&self, msg: MixedMessage) -> bool {
        match msg {
            MixedMessage::FromDevtools(msg) => {
                match msg {
                    DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => {
                        devtools::handle_evaluate_js(self.upcast(), string, sender)
                    },
                    DevtoolScriptControlMsg::GetCachedMessages(pipe_id, message_types, sender) => {
                        devtools::handle_get_cached_messages(pipe_id, message_types, sender)
                    },
                    DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => {
                        devtools::handle_wants_live_notifications(self.upcast(), bool_val)
                    },
                    _ => debug!("got an unusable devtools control message inside the worker!"),
                }
                true
            },
            MixedMessage::FromServiceWorker(msg) => {
                self.handle_script_event(msg);
                true
            },
            MixedMessage::FromTimeoutThread(_) => {
                let _ = self
                    .swmanager_sender
                    .send(ServiceWorkerMsg::Timeout(self.scope_url.clone()));
                false
            },
        }
    }

    fn handle_script_event(&self, msg: ServiceWorkerScriptMsg) {
        use self::ServiceWorkerScriptMsg::*;

        match msg {
            CommonWorker(WorkerScriptMsg::DOMMessage(data)) => {
                let scope = self.upcast::<WorkerGlobalScope>();
                let target = self.upcast();
                let _ac =
                    JSAutoCompartment::new(scope.get_cx(), scope.reflector().get_jsobject().get());
                rooted!(in(scope.get_cx()) let mut message = UndefinedValue());
                data.read(scope.upcast(), message.handle_mut());
                ExtendableMessageEvent::dispatch_jsval(target, scope.upcast(), message.handle());
            },
            CommonWorker(WorkerScriptMsg::Common(msg)) => {
                self.upcast::<WorkerGlobalScope>().process_event(msg);
            },
            Response(mediator) => {
                // TODO XXXcreativcoder This will eventually use a FetchEvent interface to fire event
                // when we have the Request and Response dom api's implemented
                // https://slightlyoff.github.io/ServiceWorker/spec/service_worker_1/index.html#fetch-event-section
                self.upcast::<EventTarget>().fire_event(atom!("fetch"));
                let _ = mediator.response_chan.send(None);
            },
            WakeUp => {},
        }
    }

    pub fn script_chan(&self) -> Box<ScriptChan + Send> {
        Box::new(ServiceWorkerChan {
            sender: self.own_sender.clone(),
        })
    }

    fn dispatch_activate(&self) {
        let event = ExtendableEvent::new(self, atom!("activate"), false, false);
        let event = (&*event).upcast::<Event>();
        self.upcast::<EventTarget>().dispatch_event(event);
    }
}

#[allow(unsafe_code)]
unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
    let worker = DomRoot::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx))
        .expect("global is not a worker scope");
    assert!(worker.is::<ServiceWorkerGlobalScope>());

    // A false response causes the script to terminate
    !worker.is_closing()
}

impl ServiceWorkerGlobalScopeMethods for ServiceWorkerGlobalScope {
    // https://w3c.github.io/ServiceWorker/#service-worker-global-scope-onmessage-attribute
    event_handler!(message, GetOnmessage, SetOnmessage);
}
<commit_msg>Update to new ServiceWorker spec link<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 devtools;
use devtools_traits::DevtoolScriptControlMsg;
use dom::abstractworker::WorkerScriptMsg;
use dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::DomObject;
use dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots};
use dom::bindings::str::DOMString;
use dom::dedicatedworkerglobalscope::AutoWorkerReset;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::extendableevent::ExtendableEvent;
use dom::extendablemessageevent::ExtendableMessageEvent;
use dom::globalscope::GlobalScope;
use dom::worker::TrustedWorkerAddress;
use dom::workerglobalscope::WorkerGlobalScope;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self, IpcSender, IpcReceiver};
use js::jsapi::{JSAutoCompartment, JSContext, JS_AddInterruptCallback};
use js::jsval::UndefinedValue;
use net_traits::{load_whole_resource, IpcSend, CustomResponseMediator};
use net_traits::request::{CredentialsMode, Destination, RequestInit};
use script_runtime::{CommonScriptMsg, ScriptChan, new_rt_and_cx, Runtime};
use script_traits::{TimerEvent, WorkerGlobalScopeInit, ScopeThings, ServiceWorkerMsg, WorkerScriptLoadOrigin};
use servo_channel::{channel, route_ipc_receiver_to_new_servo_sender, Receiver, Sender};
use servo_config::prefs::PREFS;
use servo_rand::random;
use servo_url::ServoUrl;
use std::thread;
use std::time::Duration;
use style::thread_state::{self, ThreadState};
use task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
use task_source::TaskSourceName;

/// Messages used to control service worker event loop
pub enum ServiceWorkerScriptMsg {
    /// Message common to all workers
    CommonWorker(WorkerScriptMsg),
    /// Message to request a custom response by the service worker
    Response(CustomResponseMediator),
    /// Wake-up call from the task queue.
    WakeUp,
}

impl QueuedTaskConversion for ServiceWorkerScriptMsg {
    fn task_source_name(&self) -> Option<&TaskSourceName> {
        let script_msg = match self {
            ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
            _ => return None,
        };
        match script_msg {
            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
                Some(&task_source)
            },
            _ => return None,
        }
    }

    fn into_queued_task(self) -> Option<QueuedTask> {
        let script_msg = match self {
            ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
            _ => return None,
        };
        let (category, boxed, pipeline_id, task_source) = match script_msg {
            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
                (category, boxed, pipeline_id, task_source)
            },
            _ => return None,
        };
        Some((None, category, boxed, pipeline_id, task_source))
    }

    fn from_queued_task(queued_task: QueuedTask) -> Self {
        let (_worker, category, boxed, pipeline_id, task_source) = queued_task;
        let script_msg = CommonScriptMsg::Task(category, boxed, pipeline_id, task_source);
        ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
    }

    fn wake_up_msg() -> Self {
        ServiceWorkerScriptMsg::WakeUp
    }

    fn is_wake_up(&self) -> bool {
        match self {
            ServiceWorkerScriptMsg::WakeUp => true,
            _ => false,
        }
    }
}

pub enum MixedMessage {
    FromServiceWorker(ServiceWorkerScriptMsg),
    FromDevtools(DevtoolScriptControlMsg),
    FromTimeoutThread(()),
}

#[derive(Clone, JSTraceable)]
pub struct ServiceWorkerChan {
    pub sender: Sender<ServiceWorkerScriptMsg>,
}

impl ScriptChan for ServiceWorkerChan {
    fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
        self.sender
            .send(ServiceWorkerScriptMsg::CommonWorker(
                WorkerScriptMsg::Common(msg),
            ))
            .map_err(|_| ())
    }

    fn clone(&self) -> Box<ScriptChan + Send> {
        Box::new(ServiceWorkerChan {
            sender: self.sender.clone(),
        })
    }
}

unsafe_no_jsmanaged_fields!(TaskQueue<ServiceWorkerScriptMsg>);

#[dom_struct]
pub struct ServiceWorkerGlobalScope {
    workerglobalscope: WorkerGlobalScope,
    #[ignore_malloc_size_of = "Defined in std"]
    task_queue: TaskQueue<ServiceWorkerScriptMsg>,
    #[ignore_malloc_size_of = "Defined in std"]
    own_sender: Sender<ServiceWorkerScriptMsg>,
    #[ignore_malloc_size_of = "Defined in std"]
    timer_event_port: Receiver<()>,
    #[ignore_malloc_size_of = "Defined in std"]
    swmanager_sender: IpcSender<ServiceWorkerMsg>,
    scope_url: ServoUrl,
}

impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
    type TimerMsg = ();
    type WorkerMsg = ServiceWorkerScriptMsg;
    type Event = MixedMessage;

    fn timer_event_port(&self) -> &Receiver<()> {
        &self.timer_event_port
    }

    fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
        &self.task_queue
    }

    fn handle_event(&self, event: MixedMessage) {
        self.handle_mixed_message(event);
    }

    fn handle_worker_post_event(&self, _worker: &TrustedWorkerAddress) -> Option<AutoWorkerReset> {
        None
    }

    fn from_worker_msg(&self, msg: ServiceWorkerScriptMsg) -> MixedMessage {
        MixedMessage::FromServiceWorker(msg)
    }

    fn from_timer_msg(&self, msg: ()) -> MixedMessage {
        MixedMessage::FromTimeoutThread(msg)
    }

    fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> MixedMessage {
        MixedMessage::FromDevtools(msg)
    }
}

impl ServiceWorkerGlobalScope {
    fn new_inherited(
        init: WorkerGlobalScopeInit,
        worker_url: ServoUrl,
        from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
        runtime: Runtime,
        own_sender: Sender<ServiceWorkerScriptMsg>,
        receiver: Receiver<ServiceWorkerScriptMsg>,
        timer_event_chan: IpcSender<TimerEvent>,
        timer_event_port: Receiver<()>,
        swmanager_sender: IpcSender<ServiceWorkerMsg>,
        scope_url: ServoUrl,
    ) -> ServiceWorkerGlobalScope {
        ServiceWorkerGlobalScope {
            workerglobalscope: WorkerGlobalScope::new_inherited(
                init,
                worker_url,
                runtime,
                from_devtools_receiver,
                timer_event_chan,
                None,
            ),
            task_queue: TaskQueue::new(receiver, own_sender.clone()),
            timer_event_port: timer_event_port,
            own_sender: own_sender,
            swmanager_sender: swmanager_sender,
            scope_url: scope_url,
        }
    }

    #[allow(unsafe_code)]
    pub fn new(
        init: WorkerGlobalScopeInit,
        worker_url: ServoUrl,
        from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
        runtime: Runtime,
        own_sender: Sender<ServiceWorkerScriptMsg>,
        receiver: Receiver<ServiceWorkerScriptMsg>,
        timer_event_chan: IpcSender<TimerEvent>,
        timer_event_port: Receiver<()>,
        swmanager_sender: IpcSender<ServiceWorkerMsg>,
        scope_url: ServoUrl,
    ) -> DomRoot<ServiceWorkerGlobalScope> {
        let cx = runtime.cx();
        let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
            init,
            worker_url,
            from_devtools_receiver,
            runtime,
            own_sender,
            receiver,
            timer_event_chan,
            timer_event_port,
            swmanager_sender,
            scope_url,
        ));
        unsafe { ServiceWorkerGlobalScopeBinding::Wrap(cx, scope) }
    }

    #[allow(unsafe_code)]
    // https://html.spec.whatwg.org/multipage/#run-a-worker
    pub fn run_serviceworker_scope(
        scope_things: ScopeThings,
        own_sender: Sender<ServiceWorkerScriptMsg>,
        receiver: Receiver<ServiceWorkerScriptMsg>,
        devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
        swmanager_sender: IpcSender<ServiceWorkerMsg>,
        scope_url: ServoUrl,
    ) {
        let ScopeThings {
            script_url,
            init,
            worker_load_origin,
            ..
        } = scope_things;

        let serialized_worker_url = script_url.to_string();
        let origin = GlobalScope::current()
            .expect("No current global object")
            .origin()
            .immutable()
            .clone();
        thread::Builder::new()
            .name(format!("ServiceWorker for {}", serialized_worker_url))
            .spawn(move || {
                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
                let roots = RootCollection::new();
                let _stack_roots = ThreadLocalStackRoots::new(&roots);

                let WorkerScriptLoadOrigin {
                    referrer_url,
                    referrer_policy,
                    pipeline_id,
                } = worker_load_origin;

                let request = RequestInit {
                    url: script_url.clone(),
                    destination: Destination::ServiceWorker,
                    credentials_mode: CredentialsMode::Include,
                    use_url_credentials: true,
                    pipeline_id: pipeline_id,
                    referrer_url: referrer_url,
                    referrer_policy: referrer_policy,
                    origin,
                    ..RequestInit::default()
                };

                let (url, source) =
                    match load_whole_resource(request, &init.resource_threads.sender()) {
                        Err(_) => {
                            println!("error loading script {}", serialized_worker_url);
                            return;
                        },
                        Ok((metadata, bytes)) => {
                            (metadata.final_url, String::from_utf8(bytes).unwrap())
                        },
                    };

                let runtime = unsafe { new_rt_and_cx() };

                let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
                route_ipc_receiver_to_new_servo_sender(devtools_receiver, devtools_mpsc_chan);
                // TODO XXXcreativcoder use this timer_ipc_port, when we have a service worker instance here
                let (timer_ipc_chan, _timer_ipc_port) = ipc::channel().unwrap();
                let (timer_chan, timer_port) = channel();
                let global = ServiceWorkerGlobalScope::new(
                    init,
                    url,
                    devtools_mpsc_port,
                    runtime,
                    own_sender,
                    receiver,
                    timer_ipc_chan,
                    timer_port,
                    swmanager_sender,
                    scope_url,
                );
                let scope = global.upcast::<WorkerGlobalScope>();

                unsafe {
                    // Handle interrupt requests
                    JS_AddInterruptCallback(scope.get_cx(), Some(interrupt_callback));
                }

                scope.execute_script(DOMString::from(source));
                // Service workers are time limited
                thread::Builder::new()
                    .name("SWTimeoutThread".to_owned())
                    .spawn(move || {
                        let sw_lifetime_timeout = PREFS
                            .get("dom.serviceworker.timeout_seconds")
                            .as_u64()
                            .unwrap();
                        thread::sleep(Duration::new(sw_lifetime_timeout, 0));
                        let _ = timer_chan.send(());
                    })
                    .expect("Thread spawning failed");

                global.dispatch_activate();
                let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
                scope
                    .upcast::<GlobalScope>()
                    .mem_profiler_chan()
                    .run_with_memory_reporting(
                        || {
                            // Step 29, Run the responsible event loop specified
                            // by inside settings until it is destroyed.
                            // The worker processing model remains on this step
                            // until the event loop is destroyed,
                            // which happens after the closing flag is set to true.
                            while !scope.is_closing() {
                                run_worker_event_loop(&*global, None);
                            }
                        },
                        reporter_name,
                        scope.script_chan(),
                        CommonScriptMsg::CollectReports,
                    );
            })
            .expect("Thread spawning failed");
    }

    fn handle_mixed_message(&self, msg: MixedMessage) -> bool {
        match msg {
            MixedMessage::FromDevtools(msg) => {
                match msg {
                    DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => {
                        devtools::handle_evaluate_js(self.upcast(), string, sender)
                    },
                    DevtoolScriptControlMsg::GetCachedMessages(pipe_id, message_types, sender) => {
                        devtools::handle_get_cached_messages(pipe_id, message_types, sender)
                    },
                    DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => {
                        devtools::handle_wants_live_notifications(self.upcast(), bool_val)
                    },
                    _ => debug!("got an unusable devtools control message inside the worker!"),
                }
                true
            },
            MixedMessage::FromServiceWorker(msg) => {
                self.handle_script_event(msg);
                true
            },
            MixedMessage::FromTimeoutThread(_) => {
                let _ = self
                    .swmanager_sender
                    .send(ServiceWorkerMsg::Timeout(self.scope_url.clone()));
                false
            },
        }
    }

    fn handle_script_event(&self, msg: ServiceWorkerScriptMsg) {
        use self::ServiceWorkerScriptMsg::*;

        match msg {
            CommonWorker(WorkerScriptMsg::DOMMessage(data)) => {
                let scope = self.upcast::<WorkerGlobalScope>();
                let target = self.upcast();
                let _ac =
                    JSAutoCompartment::new(scope.get_cx(), scope.reflector().get_jsobject().get());
                rooted!(in(scope.get_cx()) let mut message = UndefinedValue());
                data.read(scope.upcast(), message.handle_mut());
                ExtendableMessageEvent::dispatch_jsval(target, scope.upcast(), message.handle());
            },
            CommonWorker(WorkerScriptMsg::Common(msg)) => {
                self.upcast::<WorkerGlobalScope>().process_event(msg);
            },
            Response(mediator) => {
                // TODO XXXcreativcoder This will eventually use a FetchEvent interface to fire event
                // when we have the Request and Response dom api's implemented
                // https://w3c.github.io/ServiceWorker/#fetchevent-interface
                self.upcast::<EventTarget>().fire_event(atom!("fetch"));
                let _ = mediator.response_chan.send(None);
            },
            WakeUp => {},
        }
    }

    pub fn script_chan(&self) -> Box<ScriptChan + Send> {
        Box::new(ServiceWorkerChan {
            sender: self.own_sender.clone(),
        })
    }

    fn dispatch_activate(&self) {
        let event = ExtendableEvent::new(self, atom!("activate"), false, false);
        let event = (&*event).upcast::<Event>();
        self.upcast::<EventTarget>().dispatch_event(event);
    }
}

#[allow(unsafe_code)]
unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
    let worker = DomRoot::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx))
        .expect("global is not a worker scope");
    assert!(worker.is::<ServiceWorkerGlobalScope>());

    // A false response causes the script to terminate
    !worker.is_closing()
}

impl ServiceWorkerGlobalScopeMethods for ServiceWorkerGlobalScope {
    // https://w3c.github.io/ServiceWorker/#service-worker-global-scope-onmessage-attribute
    event_handler!(message, GetOnmessage, SetOnmessage);
}
<|endoftext|>


<commit_before>// Copyright 2015-2016, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use ffi;
use translate::*;
use std::mem::transmute;
use std::mem;
use ffi as glib_ffi;
use ffi::{gpointer, gboolean};

use MainContext;
use Source;
use SourceId;

use source::Priority;

impl MainContext {
    pub fn prepare(&self) -> (bool, i32) {
        unsafe {
            let mut priority = mem::uninitialized();

            let res = from_glib(ffi::g_main_context_prepare(self.to_glib_none().0, &mut priority));

            (res, priority)
        }
    }

    pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
        unsafe {
            from_glib_none(ffi::g_main_context_find_source_by_id(self.to_glib_none().0, source_id.to_glib()))
        }
    }

    /// Invokes `func` on the main context.
    pub fn invoke<F>(&self, func: F)
    where F: FnOnce() + Send + 'static {
        self.invoke_with_priority(::PRIORITY_DEFAULT_IDLE, func);
    }

    /// Invokes `func` on the main context with the given priority.
    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
    where F: FnOnce() + Send + 'static {
        unsafe {
            self.invoke_unsafe(priority, func);
        }
    }

    /// Invokes `func` on the main context.
    ///
    /// Different to `invoke()`, this does not require `func` to be
    /// `Send` but can only be called from the thread that owns the main context.
    ///
    /// This function panics if called from a different thread than the one that
    /// owns the main context.
    pub fn invoke_local<F>(&self, func: F)
    where F: FnOnce() + 'static {
        self.invoke_local_with_priority(::PRIORITY_DEFAULT_IDLE, func);
    }

    /// Invokes `func` on the main context with the given priority.
    ///
    /// Different to `invoke_with_priority()`, this does not require `func` to be
    /// `Send` but can only be called from the thread that owns the main context.
    ///
    /// This function panics if called from a different thread than the one that
    /// owns the main context.
    pub fn invoke_local_with_priority<F>(&self, priority: Priority, func: F)
    where F: FnOnce() + 'static {
        unsafe {
            assert!(self.is_owner());
            self.invoke_unsafe(priority, func);
        }
    }

    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
    where F: FnOnce() + 'static {
        let func = Box::into_raw(Box::new(Some(func)));
        glib_ffi::g_main_context_invoke_full(self.to_glib_none().0, priority.to_glib(), Some(trampoline::<F>),
            func as gpointer, Some(destroy_closure::<F>))
    }

    /// Calls closure with context configured as the thread default one.
    ///
    /// Thread default context is changed in panic-safe manner by calling
    /// [`push_thread_default`][push_thread_default] before calling closure
    /// and [`pop_thread_default`][pop_thread_default] afterwards regardless
    /// of whether closure panicked or not.
    ///
    /// [push_thread_default]: struct.MainContext.html#method.push_thread_default
    /// [pop_thread_default]: struct.MainContext.html#method.pop_thread_default
    pub fn with_thread_default<R, F: Sized>(&self, func: F) -> R
    where F: FnOnce() -> R {
        let _thread_default = ThreadDefaultContext::new(self);
        func()
    }
}

#[cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ref))]
unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
    let func: &mut Option<F> = transmute(func);
    let func = func.take().expect("MainContext::invoke() closure called multiple times");
    func();
    glib_ffi::G_SOURCE_REMOVE
}

unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
    Box::<Option<F>>::from_raw(ptr as *mut _);
}

struct ThreadDefaultContext<'a>(&'a MainContext);

impl<'a> ThreadDefaultContext<'a> {
    fn new(ctx: &MainContext) -> ThreadDefaultContext {
        ctx.push_thread_default();
        ThreadDefaultContext(ctx)
    }
}

impl<'a> Drop for ThreadDefaultContext<'a> {
    fn drop(&mut self) {
        self.0.pop_thread_default();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::panic;
    use std::ptr;
    use std::thread;

    #[test]
    fn test_invoke() {
        let c = MainContext::new();
        let l = ::MainLoop::new(&c, false);

        let l_clone = l.clone();
        thread::spawn(move || {
            c.invoke(move || l_clone.quit());
        });

        l.run();
    }

    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
    }

    #[test]
    fn test_with_thread_default() {
        let a = MainContext::new();
        let b = MainContext::new();

        assert!(!is_same_context(&a, &b));

        a.with_thread_default(|| {
            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));

            &b.with_thread_default(|| {
                let t = MainContext::get_thread_default().unwrap();
                assert!(is_same_context(&b, &t));
            });

            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));
        });
    }

    #[test]
    fn test_with_thread_default_is_panic_safe() {
        let a = MainContext::new();
        let b = MainContext::new();

        assert!(!is_same_context(&a, &b));

        a.with_thread_default(|| {
            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));

            let result = panic::catch_unwind(|| {
                &b.with_thread_default(|| {
                    panic!();
                });
            });
            assert!(result.is_err());

            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));
        });

    }
}
<commit_msg>fix test<commit_after>// Copyright 2015-2016, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use ffi;
use translate::*;
use std::mem::transmute;
use std::mem;
use ffi as glib_ffi;
use ffi::{gpointer, gboolean};

use MainContext;
use Source;
use SourceId;

use source::Priority;

impl MainContext {
    pub fn prepare(&self) -> (bool, i32) {
        unsafe {
            let mut priority = mem::uninitialized();

            let res = from_glib(ffi::g_main_context_prepare(self.to_glib_none().0, &mut priority));

            (res, priority)
        }
    }

    pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
        unsafe {
            from_glib_none(ffi::g_main_context_find_source_by_id(self.to_glib_none().0, source_id.to_glib()))
        }
    }

    /// Invokes `func` on the main context.
    pub fn invoke<F>(&self, func: F)
    where F: FnOnce() + Send + 'static {
        self.invoke_with_priority(::PRIORITY_DEFAULT_IDLE, func);
    }

    /// Invokes `func` on the main context with the given priority.
    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
    where F: FnOnce() + Send + 'static {
        unsafe {
            self.invoke_unsafe(priority, func);
        }
    }

    /// Invokes `func` on the main context.
    ///
    /// Different to `invoke()`, this does not require `func` to be
    /// `Send` but can only be called from the thread that owns the main context.
    ///
    /// This function panics if called from a different thread than the one that
    /// owns the main context.
    pub fn invoke_local<F>(&self, func: F)
    where F: FnOnce() + 'static {
        self.invoke_local_with_priority(::PRIORITY_DEFAULT_IDLE, func);
    }

    /// Invokes `func` on the main context with the given priority.
    ///
    /// Different to `invoke_with_priority()`, this does not require `func` to be
    /// `Send` but can only be called from the thread that owns the main context.
    ///
    /// This function panics if called from a different thread than the one that
    /// owns the main context.
    pub fn invoke_local_with_priority<F>(&self, priority: Priority, func: F)
    where F: FnOnce() + 'static {
        unsafe {
            assert!(self.is_owner());
            self.invoke_unsafe(priority, func);
        }
    }

    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
    where F: FnOnce() + 'static {
        let func = Box::into_raw(Box::new(Some(func)));
        glib_ffi::g_main_context_invoke_full(self.to_glib_none().0, priority.to_glib(), Some(trampoline::<F>),
            func as gpointer, Some(destroy_closure::<F>))
    }

    /// Calls closure with context configured as the thread default one.
    ///
    /// Thread default context is changed in panic-safe manner by calling
    /// [`push_thread_default`][push_thread_default] before calling closure
    /// and [`pop_thread_default`][pop_thread_default] afterwards regardless
    /// of whether closure panicked or not.
    ///
    /// [push_thread_default]: struct.MainContext.html#method.push_thread_default
    /// [pop_thread_default]: struct.MainContext.html#method.pop_thread_default
    pub fn with_thread_default<R, F: Sized>(&self, func: F) -> R
    where F: FnOnce() -> R {
        let _thread_default = ThreadDefaultContext::new(self);
        func()
    }
}

#[cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ref))]
unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
    let func: &mut Option<F> = transmute(func);
    let func = func.take().expect("MainContext::invoke() closure called multiple times");
    func();
    glib_ffi::G_SOURCE_REMOVE
}

unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
    Box::<Option<F>>::from_raw(ptr as *mut _);
}

struct ThreadDefaultContext<'a>(&'a MainContext);

impl<'a> ThreadDefaultContext<'a> {
    fn new(ctx: &MainContext) -> ThreadDefaultContext {
        ctx.push_thread_default();
        ThreadDefaultContext(ctx)
    }
}

impl<'a> Drop for ThreadDefaultContext<'a> {
    fn drop(&mut self) {
        self.0.pop_thread_default();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::panic;
    use std::ptr;
    use std::thread;

    #[test]
    fn test_invoke() {
        let c = MainContext::new();
        let l = ::MainLoop::new(Some(&c), false);

        let l_clone = l.clone();
        thread::spawn(move || {
            c.invoke(move || l_clone.quit());
        });

        l.run();
    }

    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
    }

    #[test]
    fn test_with_thread_default() {
        let a = MainContext::new();
        let b = MainContext::new();

        assert!(!is_same_context(&a, &b));

        a.with_thread_default(|| {
            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));

            &b.with_thread_default(|| {
                let t = MainContext::get_thread_default().unwrap();
                assert!(is_same_context(&b, &t));
            });

            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));
        });
    }

    #[test]
    fn test_with_thread_default_is_panic_safe() {
        let a = MainContext::new();
        let b = MainContext::new();

        assert!(!is_same_context(&a, &b));

        a.with_thread_default(|| {
            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));

            let result = panic::catch_unwind(|| {
                &b.with_thread_default(|| {
                    panic!();
                });
            });
            assert!(result.is_err());

            let t = MainContext::get_thread_default().unwrap();
            assert!(is_same_context(&a, &t));
        });

    }
}
<|endoftext|>