/* 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 https://mozilla.org/MPL/2.0/. */ use std::sync::Arc; use pixels::Image; use script_traits::serializable::BlobImpl; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::file::File; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; /// #[derive(Clone)] pub enum Kind { Text(PlainString), File(Binary), } #[derive(Clone)] pub struct PlainString { data: DOMString, type_: DOMString, } impl PlainString { pub fn new(data: DOMString, type_: DOMString) -> Self { Self { data, type_ } } } #[derive(Clone)] pub struct Binary { bytes: Vec, name: DOMString, type_: String, } impl Binary { pub fn new(bytes: Vec, name: DOMString, type_: String) -> Self { Self { bytes, name, type_ } } } impl Kind { pub fn type_(&self) -> DOMString { match self { Kind::Text(string) => string.type_.clone(), Kind::File(binary) => DOMString::from(binary.type_.clone()), } } pub fn as_string(&self) -> Option { match self { Kind::Text(string) => Some(string.data.clone()), Kind::File(_) => None, } } // TODO for now we create a new BlobImpl // since File constructor requires moving it. pub fn as_file(&self, global: &GlobalScope) -> Option> { match self { Kind::Text(_) => None, Kind::File(binary) => Some(File::new( global, BlobImpl::new_from_bytes(binary.bytes.clone(), binary.type_.clone()), binary.name.clone(), None, CanGc::note(), )), } } fn text_type_matches(&self, type_: &str) -> bool { matches!(self, Kind::Text(string) if string.type_.eq(type_)) } fn is_file(&self) -> bool { matches!(self, Kind::File(_)) } } /// #[allow(dead_code)] // TODO this used by DragEvent. struct Bitmap { image: Option>, x: i32, y: i32, } /// Control the behaviour of the drag data store #[derive(Clone, Copy, Eq, PartialEq)] pub enum Mode { /// ReadWrite, /// #[allow(dead_code)] // TODO this used by ClipboardEvent. ReadOnly, /// Protected, } #[allow(dead_code)] // TODO some fields are used by DragEvent. pub struct DragDataStore { /// item_list: Vec, /// default_feedback: Option, bitmap: Option, mode: Mode, /// allowed_effects_state: String, } impl DragDataStore { /// // We don't really need it since it's only instantiated by DataTransfer. #[allow(clippy::new_without_default)] pub fn new() -> DragDataStore { DragDataStore { item_list: Vec::new(), default_feedback: None, bitmap: None, mode: Mode::Protected, allowed_effects_state: String::from("uninitialized"), } } /// Get the drag data store mode pub fn mode(&self) -> Mode { self.mode } /// Set the drag data store mode pub fn set_mode(&mut self, mode: Mode) { self.mode = mode; } pub fn set_bitmap(&mut self, image: Option>, x: i32, y: i32) { self.bitmap = Some(Bitmap { image, x, y }); } /// pub fn types(&self) -> Vec { let mut types = Vec::new(); let has_files = self.item_list.iter().fold(false, |has_files, item| { // Step 2.1 For each item in the item list whose kind is text, // add an entry to L consisting of the item's type string. match item { Kind::Text(string) => types.push(string.type_.clone()), Kind::File(_) => return true, } has_files }); // Step 2.2 If there are any items in the item list whose kind is File, // add an entry to L consisting of the string "Files". if has_files { types.push(DOMString::from("Files")); } types } pub fn find_matching_text(&self, type_: &str) -> Option { self.item_list .iter() .find(|item| item.text_type_matches(type_)) .and_then(|item| item.as_string()) } pub fn add(&mut self, kind: Kind) -> Fallible<()> { if let Kind::Text(ref string) = kind { // Step 2.1 If there is already an item in the item list whose kind is text // and whose type string is equal to the method's second argument, throw "NotSupportedError". if self .item_list .iter() .any(|item| item.text_type_matches(&string.type_)) { return Err(Error::NotSupported); } } // Step 2.2 self.item_list.push(kind); Ok(()) } pub fn set_data(&mut self, format: DOMString, data: DOMString) { // Step 3-4 let type_ = normalize_mime(format); // Step 5 Remove the item in the drag data store item list whose kind is text // and whose type string is equal to format, if there is one. self.item_list .retain(|item| !item.text_type_matches(&type_)); // Step 6 Add an item whose kind is text, whose type is format, and whose data is the method's second argument. self.item_list.push(Kind::Text(PlainString { data, type_ })); } pub fn clear_data(&mut self, format: Option) -> bool { let mut was_modified = false; if let Some(format) = format { // Step 4-5 let type_ = normalize_mime(format); // Step 6 Remove the item in the item list whose kind is text and whose type is format. self.item_list.retain(|item| { let matches = item.text_type_matches(&type_); if matches { was_modified = true; } !matches }); } else { // Step 3 Remove each item in the item list whose kind is text. self.item_list.retain(|item| { let matches = item.is_file(); if !matches { was_modified = true; } matches }); } was_modified } pub fn files(&self, global: &GlobalScope, file_list: &mut Vec>) { // Step 3 If the data store is in the protected mode return the empty list. if self.mode == Mode::Protected { return; } // Step 4 For each item in the drag data store item list whose kind is File, add the item's data to the list L. self.item_list .iter() .filter_map(|item| item.as_file(global)) .for_each(|file| file_list.push(file)); } pub fn list_len(&self) -> usize { self.item_list.len() } pub fn get_item(&self, index: usize) -> Option { self.item_list.get(index).cloned() } pub fn remove(&mut self, index: usize) { self.item_list.remove(index); } pub fn clear_list(&mut self) { self.item_list.clear(); } } fn normalize_mime(mut format: DOMString) -> DOMString { // Convert format to ASCII lowercase. format.make_ascii_lowercase(); match format.as_ref() { // If format equals "text", change it to "text/plain". "text" => DOMString::from("text/plain"), // If format equals "url", change it to "text/uri-list". "url" => DOMString::from("text/uri-list"), s => DOMString::from(s), } }