diff options
author | Zhen Zhang <izgzhen@gmail.com> | 2016-07-15 01:02:21 +0800 |
---|---|---|
committer | Zhen Zhang <izgzhen@gmail.com> | 2016-07-15 20:33:51 +0800 |
commit | fdc3a8e3ac7ea5512e6fa09be892cd67ff6e8657 (patch) | |
tree | 51410a9abbf6d58aee8df62bd84d9737952c75dd | |
parent | 4b78b9adab916cc4fdde6248e785030b79f406da (diff) | |
download | servo-fdc3a8e3ac7ea5512e6fa09be892cd67ff6e8657.tar.gz servo-fdc3a8e3ac7ea5512e6fa09be892cd67ff6e8657.zip |
Put Blob URL online
-rw-r--r-- | components/net/blob_loader.rs | 115 | ||||
-rw-r--r-- | components/net/filemanager_thread.rs | 82 | ||||
-rw-r--r-- | components/net/resource_thread.rs | 21 | ||||
-rw-r--r-- | components/net_traits/blob_url_store.rs | 31 | ||||
-rw-r--r-- | components/net_traits/filemanager_thread.rs | 13 | ||||
-rw-r--r-- | components/script/dom/blob.rs | 44 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 24 | ||||
-rw-r--r-- | components/script/dom/url.rs | 22 | ||||
-rw-r--r-- | components/script/dom/xmlhttprequest.rs | 6 | ||||
-rw-r--r-- | tests/unit/net/filemanager_thread.rs | 8 | ||||
-rw-r--r-- | tests/unit/net/resource_thread.rs | 12 | ||||
-rw-r--r-- | tests/wpt/metadata/FileAPI/url/url_xmlhttprequest_img.html.ini | 5 | ||||
-rw-r--r-- | tests/wpt/metadata/workers/constructors/Worker/Blob-url.html.ini | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 24 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/mozilla/blob_url_upload.html.ini | 3 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/blob_url_upload.html | 22 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/blob_url_upload_ref.html | 7 |
17 files changed, 257 insertions, 188 deletions
diff --git a/components/net/blob_loader.rs b/components/net/blob_loader.rs index df0c7468d42..0ded019553f 100644 --- a/components/net/blob_loader.rs +++ b/components/net/blob_loader.rs @@ -5,53 +5,92 @@ use hyper::header::{DispositionType, ContentDisposition, DispositionParam}; use hyper::header::{Headers, ContentType, ContentLength, Charset}; use hyper::http::RawStatus; +use ipc_channel::ipc::{self, IpcSender}; use mime::{Mime, Attr}; use mime_classifier::MimeClassifier; -use net_traits::ProgressMsg::Done; -use net_traits::blob_url_store::BlobBuf; +use net_traits::ProgressMsg::{Payload, Done}; +use net_traits::blob_url_store::parse_blob_url; +use net_traits::filemanager_thread::{FileManagerThreadMsg, SelectedFileId}; use net_traits::response::HttpsState; -use net_traits::{LoadConsumer, LoadData, Metadata}; -use resource_thread::start_sending_sniffed_opt; +use net_traits::{LoadConsumer, LoadData, Metadata, NetworkError}; +use resource_thread::CancellationListener; +use resource_thread::{start_sending_sniffed_opt, send_error}; +use std::boxed::FnBox; use std::sync::Arc; +use util::thread::spawn_named; // TODO: Check on GET // https://w3c.github.io/FileAPI/#requestResponseModel -pub fn load_blob(load_data: LoadData, start_chan: LoadConsumer, - classifier: Arc<MimeClassifier>, blob_buf: BlobBuf) { - let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime!(Text / Plain)); - let charset = content_type.get_param(Attr::Charset); - - let mut headers = Headers::new(); - - if let Some(name) = blob_buf.filename { - let charset = charset.and_then(|c| c.as_str().parse().ok()); - headers.set(ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::Filename(charset.unwrap_or(Charset::Us_Ascii), - None, name.as_bytes().to_vec()) - ] - }); +pub fn factory(filemanager_chan: IpcSender<FileManagerThreadMsg>) + -> Box<FnBox(LoadData, + LoadConsumer, + Arc<MimeClassifier>, + CancellationListener) + Send> { + box move |load_data: LoadData, start_chan, classifier, _cancel_listener| { + spawn_named(format!("blob loader for {}", load_data.url), move || { + load_blob(load_data, start_chan, classifier, filemanager_chan); + }) } +} + +fn load_blob(load_data: LoadData, start_chan: LoadConsumer, + classifier: Arc<MimeClassifier>, + filemanager_chan: IpcSender<FileManagerThreadMsg>) { + let (chan, recv) = ipc::channel().unwrap(); + if let Ok((id, origin, _fragment)) = parse_blob_url(&load_data.url.clone()) { + let id = SelectedFileId(id.simple().to_string()); + let check_url_validity = true; + let msg = FileManagerThreadMsg::ReadFile(chan, id, check_url_validity, origin); + let _ = filemanager_chan.send(msg); + + match recv.recv().unwrap() { + Ok(blob_buf) => { + let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime!(Text / Plain)); + let charset = content_type.get_param(Attr::Charset); + + let mut headers = Headers::new(); + + if let Some(name) = blob_buf.filename { + let charset = charset.and_then(|c| c.as_str().parse().ok()); + headers.set(ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![ + DispositionParam::Filename(charset.unwrap_or(Charset::Us_Ascii), + None, name.as_bytes().to_vec()) + ] + }); + } + + headers.set(ContentType(content_type.clone())); + headers.set(ContentLength(blob_buf.size as u64)); + + let metadata = Metadata { + final_url: load_data.url.clone(), + content_type: Some(ContentType(content_type.clone())), + charset: charset.map(|c| c.as_str().to_string()), + headers: Some(headers), + // https://w3c.github.io/FileAPI/#TwoHundredOK + status: Some(RawStatus(200, "OK".into())), + https_state: HttpsState::None, + referrer: None, + }; - headers.set(ContentType(content_type.clone())); - headers.set(ContentLength(blob_buf.size as u64)); - - let metadata = Metadata { - final_url: load_data.url.clone(), - content_type: Some(ContentType(content_type.clone())), - charset: charset.map(|c| c.as_str().to_string()), - headers: Some(headers), - // https://w3c.github.io/FileAPI/#TwoHundredOK - status: Some(RawStatus(200, "OK".into())), - https_state: HttpsState::None, - referrer: None - }; - - if let Ok(chan) = - start_sending_sniffed_opt(start_chan, metadata, classifier, - &blob_buf.bytes, load_data.context.clone()) { - let _ = chan.send(Done(Ok(()))); + if let Ok(chan) = + start_sending_sniffed_opt(start_chan, metadata, classifier, + &blob_buf.bytes, load_data.context.clone()) { + let _ = chan.send(Payload(blob_buf.bytes)); + let _ = chan.send(Done(Ok(()))); + } + } + Err(e) => { + let err = NetworkError::Internal(format!("{:?}", e)); + send_error(load_data.url, err, start_chan); + } + } + } else { + let e = format!("Invalid blob URL format {:?}", load_data.url); + let format_err = NetworkError::Internal(e); + send_error(load_data.url.clone(), format_err, start_chan); } } diff --git a/components/net/filemanager_thread.rs b/components/net/filemanager_thread.rs index 3f19949f0e3..175d1ca3fe1 100644 --- a/components/net/filemanager_thread.rs +++ b/components/net/filemanager_thread.rs @@ -2,15 +2,11 @@ * 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 blob_loader::load_blob; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; -use mime_classifier::MimeClassifier; use mime_guess::guess_mime_type_opt; -use net_traits::blob_url_store::{BlobBuf, BlobURLStoreError, parse_blob_url}; +use net_traits::blob_url_store::{BlobBuf, BlobURLStoreError}; use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult, FilterPattern, FileOrigin}; use net_traits::filemanager_thread::{SelectedFile, RelativePos, FileManagerThreadError, SelectedFileId}; -use net_traits::{LoadConsumer, LoadData, NetworkError}; -use resource_thread::send_error; use std::collections::HashMap; use std::fs::File; use std::io::{Read, Seek, SeekFrom}; @@ -133,7 +129,6 @@ enum FileImpl { struct FileManager<UI: 'static + UIProvider> { receiver: IpcReceiver<FileManagerThreadMsg>, store: Arc<FileManagerStore<UI>>, - classifier: Arc<MimeClassifier>, } impl<UI: 'static + UIProvider> FileManager<UI> { @@ -141,7 +136,6 @@ impl<UI: 'static + UIProvider> FileManager<UI> { FileManager { receiver: recv, store: Arc::new(FileManagerStore::new(ui)), - classifier: Arc::new(MimeClassifier::new()), } } @@ -160,9 +154,9 @@ impl<UI: 'static + UIProvider> FileManager<UI> { store.select_files(filter, sender, origin, opt_test_paths); }) } - FileManagerThreadMsg::ReadFile(sender, id, origin) => { + FileManagerThreadMsg::ReadFile(sender, id, check_url_validity, origin) => { spawn_named("read file".to_owned(), move || { - match store.try_read_file(id, origin) { + match store.try_read_file(id, check_url_validity, origin) { Ok(buffer) => { let _ = sender.send(Ok(buffer)); } Err(e) => { let _ = sender.send(Err(FileManagerThreadError::BlobURLStoreError(e))); @@ -170,9 +164,9 @@ impl<UI: 'static + UIProvider> FileManager<UI> { } }) } - FileManagerThreadMsg::PromoteMemory(blob_buf, sender, origin) => { + FileManagerThreadMsg::PromoteMemory(blob_buf, set_valid, sender, origin) => { spawn_named("transfer memory".to_owned(), move || { - store.promote_memory(blob_buf, sender, origin); + store.promote_memory(blob_buf, set_valid, sender, origin); }) } FileManagerThreadMsg::AddSlicedURLEntry(id, rel_pos, sender, origin) =>{ @@ -180,18 +174,6 @@ impl<UI: 'static + UIProvider> FileManager<UI> { store.add_sliced_url_entry(id, rel_pos, sender, origin); }) } - FileManagerThreadMsg::LoadBlob(load_data, consumer) => { - match parse_blob_url(&load_data.url.clone()) { - None => { - let e = format!("Invalid blob URL format {:?}", load_data.url); - let format_err = NetworkError::Internal(e); - send_error(load_data.url.clone(), format_err, consumer); - } - Some((id, _fragment)) => { - self.process_request(load_data, consumer, id); - } - } - }, FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => { if let Ok(id) = Uuid::parse_str(&id.0) { spawn_named("revoke blob url".to_owned(), move || { @@ -233,18 +215,6 @@ impl<UI: 'static + UIProvider> FileManager<UI> { }; } } - - fn process_request(&self, load_data: LoadData, consumer: LoadConsumer, id: Uuid) { - let origin_in = load_data.url.origin().unicode_serialization(); - // check_url_validity is true since content is requested by this URL - match self.store.get_blob_buf(&id, &origin_in, RelativePos::full_range(), true) { - Ok(blob_buf) => { - let classifier = self.classifier.clone(); - spawn_named("load blob".to_owned(), move || load_blob(load_data, consumer, classifier, blob_buf)); - } - Err(e) => send_error(load_data.url.clone(), NetworkError::Internal(format!("{:?}", e)), consumer), - } - } } /// File manager's data store. It maintains a thread-safe mapping @@ -271,7 +241,8 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> { if *origin_in != *entry.origin { Err(BlobURLStoreError::InvalidOrigin) } else { - if check_url_validity && !entry.is_valid_url.load(Ordering::Acquire) { + let is_valid = entry.is_valid_url.load(Ordering::Acquire); + if check_url_validity && !is_valid { Err(BlobURLStoreError::InvalidFileID) } else { Ok(entry.file_impl.clone()) @@ -497,44 +468,47 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> { } } - fn try_read_file(&self, id: SelectedFileId, origin_in: FileOrigin) -> Result<Vec<u8>, BlobURLStoreError> { + // Convenient wrapper over get_blob_buf + fn try_read_file(&self, id: SelectedFileId, check_url_validity: bool, + origin_in: FileOrigin) -> Result<BlobBuf, BlobURLStoreError> { let id = try!(Uuid::parse_str(&id.0).map_err(|_| BlobURLStoreError::InvalidFileID)); - - // No need to check URL validity in reading a file by FileReader - let blob_buf = try!(self.get_blob_buf(&id, &origin_in, RelativePos::full_range(), false)); - - Ok(blob_buf.bytes) + self.get_blob_buf(&id, &origin_in, RelativePos::full_range(), check_url_validity) } fn dec_ref(&self, id: &Uuid, origin_in: &FileOrigin, unset_url_validity: bool) -> Result<(), BlobURLStoreError> { - let (is_last_ref, opt_parent_id) = match self.entries.read().unwrap().get(id) { + let (do_remove, opt_parent_id) = match self.entries.read().unwrap().get(id) { Some(entry) => { if *entry.origin == *origin_in { let old_refs = entry.refs.fetch_sub(1, Ordering::Release); - if old_refs > 1 { - if unset_url_validity { - entry.is_valid_url.store(false, Ordering::Release); - } + if unset_url_validity { + entry.is_valid_url.store(false, Ordering::Release); + } + if old_refs > 1 { + // not the last reference, no need to touch parent (false, None) } else { + // last reference, and if it has a reference to parent id + // dec_ref on parent later if necessary + let is_valid = entry.is_valid_url.load(Ordering::Acquire); if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl { - // if it has a reference to parent id, dec_ref on parent later - (true, Some(parent_id.clone())) + (!is_valid, Some(parent_id.clone())) } else { - (true, None) + (!is_valid, None) } } - } else { // Invalid origin + } else { return Err(BlobURLStoreError::InvalidOrigin); } } None => return Err(BlobURLStoreError::InvalidFileID), }; - if is_last_ref { + // Trigger removing if its last reference is gone and it is + // not a part of a valid Blob URL + if do_remove { atomic::fence(Ordering::Acquire); self.remove(id); @@ -548,7 +522,7 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> { Ok(()) } - fn promote_memory(&self, blob_buf: BlobBuf, + fn promote_memory(&self, blob_buf: BlobBuf, set_valid: bool, sender: IpcSender<Result<SelectedFileId, BlobURLStoreError>>, origin: FileOrigin) { match Url::parse(&origin) { // parse to check sanity Ok(_) => { @@ -558,7 +532,7 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> { file_impl: FileImpl::Memory(blob_buf), refs: AtomicUsize::new(1), // Valid here since PromoteMemory implies URL creation - is_valid_url: AtomicBool::new(true), + is_valid_url: AtomicBool::new(set_valid), }); let _ = sender.send(Ok(SelectedFileId(id.simple().to_string()))); diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 56caf7ce886..bffd40fed6e 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -4,6 +4,7 @@ //! A thread that takes a URL and streams back the binary data. use about_loader; +use blob_loader; use chrome_loader; use connector::{Connector, create_http_connector}; use content_blocker::BLOCKED_CONTENT_RULES; @@ -173,18 +174,20 @@ fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata, pub fn new_resource_threads(user_agent: String, devtools_chan: Option<Sender<DevtoolsControlMsg>>, profiler_chan: ProfilerChan) -> (ResourceThreads, ResourceThreads) { - let (public_core, private_core) = new_core_resource_thread(user_agent, devtools_chan, profiler_chan); + let filemanager_chan: IpcSender<FileManagerThreadMsg> = FileManagerThreadFactory::new(TFD_PROVIDER); + let (public_core, private_core) = new_core_resource_thread(user_agent, devtools_chan, + profiler_chan, filemanager_chan.clone()); let storage: IpcSender<StorageThreadMsg> = StorageThreadFactory::new(); - let filemanager: IpcSender<FileManagerThreadMsg> = FileManagerThreadFactory::new(TFD_PROVIDER); - (ResourceThreads::new(public_core, storage.clone(), filemanager.clone()), - ResourceThreads::new(private_core, storage, filemanager)) + (ResourceThreads::new(public_core, storage.clone(), filemanager_chan.clone()), + ResourceThreads::new(private_core, storage, filemanager_chan)) } /// Create a CoreResourceThread pub fn new_core_resource_thread(user_agent: String, devtools_chan: Option<Sender<DevtoolsControlMsg>>, - profiler_chan: ProfilerChan) + profiler_chan: ProfilerChan, + filemanager_chan: IpcSender<FileManagerThreadMsg>) -> (CoreResourceThread, CoreResourceThread) { let (public_setup_chan, public_setup_port) = ipc::channel().unwrap(); let (private_setup_chan, private_setup_port) = ipc::channel().unwrap(); @@ -192,7 +195,7 @@ pub fn new_core_resource_thread(user_agent: String, let private_setup_chan_clone = private_setup_chan.clone(); spawn_named("ResourceManager".to_owned(), move || { let resource_manager = CoreResourceManager::new( - user_agent, devtools_chan, profiler_chan + user_agent, devtools_chan, profiler_chan, filemanager_chan ); let mut channel_manager = ResourceChannelManager { @@ -462,6 +465,7 @@ pub struct CoreResourceManager { mime_classifier: Arc<MimeClassifier>, devtools_chan: Option<Sender<DevtoolsControlMsg>>, profiler_chan: ProfilerChan, + filemanager_chan: IpcSender<FileManagerThreadMsg>, cancel_load_map: HashMap<ResourceId, Sender<()>>, next_resource_id: ResourceId, } @@ -469,12 +473,14 @@ pub struct CoreResourceManager { impl CoreResourceManager { pub fn new(user_agent: String, devtools_channel: Option<Sender<DevtoolsControlMsg>>, - profiler_chan: ProfilerChan) -> CoreResourceManager { + profiler_chan: ProfilerChan, + filemanager_chan: IpcSender<FileManagerThreadMsg>) -> CoreResourceManager { CoreResourceManager { user_agent: user_agent, mime_classifier: Arc::new(MimeClassifier::new()), devtools_chan: devtools_channel, profiler_chan: profiler_chan, + filemanager_chan: filemanager_chan, cancel_load_map: HashMap::new(), next_resource_id: ResourceId(0), } @@ -548,6 +554,7 @@ impl CoreResourceManager { }, "data" => from_factory(data_loader::factory), "about" => from_factory(about_loader::factory), + "blob" => blob_loader::factory(self.filemanager_chan.clone()), _ => { debug!("resource_thread: no loader for scheme {}", load_data.url.scheme()); send_error(load_data.url, NetworkError::Internal("no loader for scheme".to_owned()), consumer); diff --git a/components/net_traits/blob_url_store.rs b/components/net_traits/blob_url_store.rs index fe9e83046fb..1de26f90a2f 100644 --- a/components/net_traits/blob_url_store.rs +++ b/components/net_traits/blob_url_store.rs @@ -2,6 +2,7 @@ * 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 filemanager_thread::FileOrigin; use std::str::FromStr; use url::Url; use uuid::Uuid; @@ -20,7 +21,7 @@ pub enum BlobURLStoreError { } /// Standalone blob buffer object -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct BlobBuf { pub filename: Option<String>, /// MIME type string @@ -33,13 +34,25 @@ pub struct BlobBuf { /// Parse URL as Blob URL scheme's definition /// https://w3c.github.io/FileAPI/#DefinitionOfScheme -pub fn parse_blob_url(url: &Url) -> Option<(Uuid, Option<&str>)> { - url.path_segments().and_then(|mut segments| { - let id_str = match (segments.next(), segments.next()) { - (Some(s), None) => s, - _ => return None, - }; +pub fn parse_blob_url(url: &Url) -> Result<(Uuid, FileOrigin, Option<String>), ()> { + let url_inner = try!(Url::parse(url.path()).map_err(|_| ())); + let fragment = url_inner.fragment().map(|s| s.to_string()); + let mut segs = try!(url_inner.path_segments().ok_or(())); + let id = try!(segs.nth(0).ok_or(())); + let id = try!(Uuid::from_str(id).map_err(|_| ())); + Ok((id, get_blob_origin(&url_inner), fragment)) +} - Uuid::from_str(id_str).map(|id| (id, url.fragment())).ok() - }) +/// Given an URL, returning the Origin that a Blob created under this +/// URL should have. +/// HACK(izgzhen): Not well-specified on spec, and it is a bit a hack +/// both due to ambiguity of spec and that we have to serialization the +/// Origin here. +pub fn get_blob_origin(url: &Url) -> FileOrigin { + if url.scheme() == "file" { + // NOTE: by default this is "null" (Opaque), which is not ideal + "file://".to_string() + } else { + url.origin().unicode_serialization() + } } diff --git a/components/net_traits/filemanager_thread.rs b/components/net_traits/filemanager_thread.rs index 455ed1fd253..957205fcc11 100644 --- a/components/net_traits/filemanager_thread.rs +++ b/components/net_traits/filemanager_thread.rs @@ -8,7 +8,6 @@ use num_traits::ToPrimitive; use std::cmp::{max, min}; use std::ops::Range; use std::path::PathBuf; -use super::{LoadConsumer, LoadData}; // HACK: Not really process-safe now, we should send Origin // directly instead of this in future, blocked on #11722 @@ -128,15 +127,13 @@ pub enum FileManagerThreadMsg { /// Select multiple files, return a vector of triples SelectFiles(Vec<FilterPattern>, IpcSender<FileManagerResult<Vec<SelectedFile>>>, FileOrigin, Option<Vec<String>>), - /// Read file, return the bytes - ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId, FileOrigin), - - /// Load resource by Blob URL - LoadBlob(LoadData, LoadConsumer), + /// Read file by FileID, optionally check URL validity based on + /// third flag, return the blob buffer object + ReadFile(IpcSender<FileManagerResult<BlobBuf>>, SelectedFileId, bool, FileOrigin), /// Add an entry as promoted memory-based blob and send back the associated FileID - /// as part of a valid Blob URL - PromoteMemory(BlobBuf, IpcSender<Result<SelectedFileId, BlobURLStoreError>>, FileOrigin), + /// as part of a valid/invalid Blob URL depending on the second bool flag + PromoteMemory(BlobBuf, bool, IpcSender<Result<SelectedFileId, BlobURLStoreError>>, FileOrigin), /// Add a sliced entry pointing to the parent FileID, and send back the associated FileID /// as part of a valid Blob URL diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index 9a7ddc7e6b7..b2bce4f2ece 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -15,7 +15,7 @@ use encoding::all::UTF_8; use encoding::types::{EncoderTrap, Encoding}; use ipc_channel::ipc; use net_traits::IpcSend; -use net_traits::blob_url_store::BlobBuf; +use net_traits::blob_url_store::{BlobBuf, get_blob_origin}; use net_traits::filemanager_thread::{FileManagerThreadMsg, SelectedFileId, RelativePos}; use std::ascii::AsciiExt; use std::cell::Cell; @@ -174,7 +174,7 @@ impl Blob { match *self.blob_impl.borrow() { BlobImpl::File(ref f) => { let global = self.global(); - let origin = global.r().get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&global.r().get_url()); let filemanager = global.r().resource_threads().sender(); let (tx, rx) = ipc::channel().unwrap(); @@ -185,7 +185,9 @@ impl Blob { Err(_) => SelectedFileId("".to_string()) // Return a dummy id on error } } - BlobImpl::Memory(ref slice) => self.promote_to_file(slice), + BlobImpl::Memory(ref slice) => { + self.promote(slice, true) + } BlobImpl::Sliced(ref parent, ref rel_pos) => { match *parent.blob_impl.borrow() { BlobImpl::Sliced(_, _) => { @@ -196,7 +198,7 @@ impl Blob { BlobImpl::File(ref f) => self.create_sliced_url_id(&f.id, rel_pos), BlobImpl::Memory(ref bytes) => { - let parent_id = parent.promote_to_file(bytes); + let parent_id = parent.promote(bytes, false); *self.blob_impl.borrow_mut() = BlobImpl::Sliced(parent.clone(), rel_pos.clone()); self.create_sliced_url_id(&parent_id, rel_pos) } @@ -206,10 +208,12 @@ impl Blob { } /// Promite memory-based Blob to file-based, - /// The bytes in data slice will be transferred to file manager thread - fn promote_to_file(&self, bytes: &[u8]) -> SelectedFileId { + /// The bytes in data slice will be transferred to file manager thread. + /// Depending on set_valid, the returned FileID can be part of + /// valid or invalid Blob URL. + fn promote(&self, bytes: &[u8], set_valid: bool) -> SelectedFileId { let global = self.global(); - let origin = global.r().get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&global.r().get_url()); let filemanager = global.r().resource_threads().sender(); let blob_buf = BlobBuf { @@ -220,7 +224,7 @@ impl Blob { }; let (tx, rx) = ipc::channel().unwrap(); - let _ = filemanager.send(FileManagerThreadMsg::PromoteMemory(blob_buf, tx, origin.clone())); + let _ = filemanager.send(FileManagerThreadMsg::PromoteMemory(blob_buf, set_valid, tx, origin.clone())); match rx.recv().unwrap() { Ok(new_id) => SelectedFileId(new_id.0), @@ -234,7 +238,7 @@ impl Blob { rel_pos: &RelativePos) -> SelectedFileId { let global = self.global(); - let origin = global.r().get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&global.r().get_url()); let filemanager = global.r().resource_threads().sender(); let (tx, rx) = ipc::channel().unwrap(); @@ -252,7 +256,7 @@ impl Blob { fn clean_up_file_resource(&self) { if let BlobImpl::File(ref f) = *self.blob_impl.borrow() { let global = self.global(); - let origin = global.r().get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&global.r().get_url()); let filemanager = global.r().resource_threads().sender(); let (tx, rx) = ipc::channel().unwrap(); @@ -275,19 +279,15 @@ impl Drop for Blob { fn read_file(global: GlobalRef, id: SelectedFileId) -> Result<Vec<u8>, ()> { let file_manager = global.filemanager_thread(); let (chan, recv) = ipc::channel().map_err(|_|())?; - let origin = global.get_url().origin().unicode_serialization(); - let msg = FileManagerThreadMsg::ReadFile(chan, id, origin); + let origin = get_blob_origin(&global.get_url()); + let check_url_validity = false; + let msg = FileManagerThreadMsg::ReadFile(chan, id, check_url_validity, origin); let _ = file_manager.send(msg); - let result = match recv.recv() { - Ok(ret) => ret, - Err(e) => { - debug!("File manager thread has problem {:?}", e); - return Err(()) - } - }; - - result.map_err(|_|()) + match recv.recv().unwrap() { + Ok(blob_buf) => Ok(blob_buf.bytes), + Err(_) => Err(()), + } } /// Extract bytes from BlobParts, used by Blob and File constructor @@ -389,7 +389,7 @@ fn is_ascii_printable(string: &str) -> bool { /// Bump the reference counter in file manager thread fn inc_ref_id(global: GlobalRef, id: SelectedFileId) { let file_manager = global.filemanager_thread(); - let origin = global.get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&global.get_url()); let msg = FileManagerThreadMsg::IncRef(id, origin); let _ = file_manager.send(msg); } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 45964355382..7f524cac29d 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -35,6 +35,7 @@ use ipc_channel::ipc::{self, IpcSender}; use mime_guess; use msg::constellation_msg::Key; use net_traits::IpcSend; +use net_traits::blob_url_store::get_blob_origin; use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern}; use script_traits::ScriptMsg as ConstellationMsg; use std::borrow::ToOwned; @@ -749,7 +750,7 @@ impl HTMLInputElement { // Select files by invoking UI or by passed in argument fn select_files(&self, opt_test_paths: Option<Vec<DOMString>>) { let window = window_from_node(self); - let origin = window.get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&window.get_url()); let filemanager = window.resource_threads().sender(); let mut files: Vec<Root<File>> = vec![]; @@ -770,13 +771,6 @@ impl HTMLInputElement { for selected in selected_files { files.push(File::new_from_selected(window.r(), selected)); } - - target.fire_event("input", - EventBubbles::Bubbles, - EventCancelable::NotCancelable); - target.fire_event("change", - EventBubbles::Bubbles, - EventCancelable::NotCancelable); }, Err(err) => error = Some(err), }; @@ -799,13 +793,6 @@ impl HTMLInputElement { match recv.recv().expect("IpcSender side error") { Ok(selected) => { files.push(File::new_from_selected(window.r(), selected)); - - target.fire_event("input", - EventBubbles::Bubbles, - EventCancelable::NotCancelable); - target.fire_event("change", - EventBubbles::Bubbles, - EventCancelable::NotCancelable); }, Err(err) => error = Some(err), }; @@ -816,6 +803,13 @@ impl HTMLInputElement { } else { let filelist = FileList::new(window.r(), files); self.filelist.set(Some(&filelist)); + + target.fire_event("input", + EventBubbles::Bubbles, + EventCancelable::NotCancelable); + target.fire_event("change", + EventBubbles::Bubbles, + EventCancelable::NotCancelable); } } } diff --git a/components/script/dom/url.rs b/components/script/dom/url.rs index 961d3c2fd29..81c806de806 100644 --- a/components/script/dom/url.rs +++ b/components/script/dom/url.rs @@ -15,8 +15,8 @@ use dom::urlhelper::UrlHelper; use dom::urlsearchparams::URLSearchParams; use ipc_channel::ipc; use net_traits::IpcSend; -use net_traits::blob_url_store::parse_blob_url; -use net_traits::filemanager_thread::{FileOrigin, SelectedFileId, FileManagerThreadMsg}; +use net_traits::blob_url_store::{get_blob_origin, parse_blob_url}; +use net_traits::filemanager_thread::{SelectedFileId, FileManagerThreadMsg}; use std::borrow::ToOwned; use std::default::Default; use url::quirks::domain_to_unicode; @@ -117,7 +117,7 @@ impl URL { pub fn CreateObjectURL(global: GlobalRef, blob: &Blob) -> DOMString { /// XXX: Second field is an unicode-serialized Origin, it is a temporary workaround /// and should not be trusted. See issue https://github.com/servo/servo/issues/11722 - let origin = URL::get_blob_origin(&global.get_url()); + let origin = get_blob_origin(&global.get_url()); if blob.IsClosed() { // Generate a dummy id @@ -141,10 +141,10 @@ impl URL { NOTE: The first step is unnecessary, since closed blobs do not exist in the store */ - let origin = global.get_url().origin().unicode_serialization(); + let origin = get_blob_origin(&global.get_url()); if let Ok(url) = Url::parse(&url) { - if let Some((id, _)) = parse_blob_url(&url) { + if let Ok((id, _, _)) = parse_blob_url(&url) { let filemanager = global.resource_threads().sender(); let id = SelectedFileId(id.simple().to_string()); let (tx, rx) = ipc::channel().unwrap(); @@ -172,18 +172,6 @@ impl URL { result } - - /* NOTE(izgzhen): WebKit will return things like blob:file:///XXX - while Chrome will return blob:null/XXX - This is not well-specified, and I prefer the WebKit way here - */ - fn get_blob_origin(url: &Url) -> FileOrigin { - if url.scheme() == "file" { - "file://".to_string() - } else { - url.origin().unicode_serialization() - } - } } impl URLMethods for URL { diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 4a6dba72f6f..532c4d2ec1c 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -368,6 +368,12 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 11 - abort existing requests self.terminate_ongoing_fetch(); + // TODO(izgzhen): In the WPT test: FileAPI/blob/Blob-XHR-revoke.html, + // the xhr.open(url) is expected to hold a reference to the URL, + // thus renders following revocations invalid. Though we won't + // implement this for now, if ever needed, we should check blob + // scheme and trigger corresponding actions here. + // Step 12 *self.request_method.borrow_mut() = parsed_method; *self.request_url.borrow_mut() = Some(parsed_url); diff --git a/tests/unit/net/filemanager_thread.rs b/tests/unit/net/filemanager_thread.rs index 7c203fec115..40549eb2101 100644 --- a/tests/unit/net/filemanager_thread.rs +++ b/tests/unit/net/filemanager_thread.rs @@ -52,12 +52,12 @@ fn test_filemanager() { // Test by reading, expecting same content { let (tx2, rx2) = ipc::channel().unwrap(); - chan.send(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), origin.clone())).unwrap(); + chan.send(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), false, origin.clone())).unwrap(); let msg = rx2.recv().expect("Broken channel"); - let vec = msg.expect("File manager reading failure is unexpected"); - assert_eq!(test_file_content, vec, "Read content differs"); + let blob_buf = msg.expect("File manager reading failure is unexpected"); + assert_eq!(test_file_content, blob_buf.bytes, "Read content differs"); } // Delete the id @@ -72,7 +72,7 @@ fn test_filemanager() { // Test by reading again, expecting read error because we invalidated the id { let (tx2, rx2) = ipc::channel().unwrap(); - chan.send(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), origin.clone())).unwrap(); + chan.send(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), false, origin.clone())).unwrap(); let msg = rx2.recv().expect("Broken channel"); diff --git a/tests/unit/net/resource_thread.rs b/tests/unit/net/resource_thread.rs index 15b7ad6f882..5e298926e6f 100644 --- a/tests/unit/net/resource_thread.rs +++ b/tests/unit/net/resource_thread.rs @@ -4,6 +4,7 @@ use ipc_channel::ipc; use msg::constellation_msg::{PipelineId, ReferrerPolicy}; +use net::filemanager_thread::{FileManagerThreadFactory, TFDProvider}; use net::resource_thread::new_core_resource_thread; use net_traits::hosts::{parse_hostsfile, host_replacement}; use net_traits::{CoreResourceMsg, LoadData, LoadConsumer, LoadContext}; @@ -15,6 +16,8 @@ use std::net::IpAddr; use std::sync::mpsc::channel; use url::Url; +const TFD_PROVIDER: &'static TFDProvider = &TFDProvider; + fn ip(s: &str) -> IpAddr { s.parse().unwrap() } @@ -40,7 +43,8 @@ impl LoadOrigin for ResourceTest { fn test_exit() { let (tx, _rx) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap(); - let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx)); + let filemanager_chan = FileManagerThreadFactory::new(TFD_PROVIDER); + let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx), filemanager_chan); resource_thread.send(CoreResourceMsg::Exit(sender)).unwrap(); receiver.recv().unwrap(); } @@ -49,7 +53,8 @@ fn test_exit() { fn test_bad_scheme() { let (tx, _rx) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap(); - let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx)); + let filemanager_chan = FileManagerThreadFactory::new(TFD_PROVIDER); + let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx), filemanager_chan); let (start_chan, start) = ipc::channel().unwrap(); let url = Url::parse("bogus://whatever").unwrap(); resource_thread.send(CoreResourceMsg::Load(LoadData::new(LoadContext::Browsing, url, &ResourceTest), @@ -228,7 +233,8 @@ fn test_cancelled_listener() { let (tx, _rx) = ipc::channel().unwrap(); let (exit_sender, exit_receiver) = ipc::channel().unwrap(); - let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx)); + let filemanager_chan = FileManagerThreadFactory::new(TFD_PROVIDER); + let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx), filemanager_chan); let (sender, receiver) = ipc::channel().unwrap(); let (id_sender, id_receiver) = ipc::channel().unwrap(); let (sync_sender, sync_receiver) = ipc::channel().unwrap(); diff --git a/tests/wpt/metadata/FileAPI/url/url_xmlhttprequest_img.html.ini b/tests/wpt/metadata/FileAPI/url/url_xmlhttprequest_img.html.ini deleted file mode 100644 index 08c0d4d0d67..00000000000 --- a/tests/wpt/metadata/FileAPI/url/url_xmlhttprequest_img.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[url_xmlhttprequest_img.html] - type: reftest - reftype: == - refurl: /FileAPI/url/url_xmlhttprequest_img-ref.html - expected: FAIL diff --git a/tests/wpt/metadata/workers/constructors/Worker/Blob-url.html.ini b/tests/wpt/metadata/workers/constructors/Worker/Blob-url.html.ini deleted file mode 100644 index 498c1c7498c..00000000000 --- a/tests/wpt/metadata/workers/constructors/Worker/Blob-url.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[Blob-url.html] - type: testharness - expected: TIMEOUT - [Worker supports Blob url] - expected: TIMEOUT - diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 25e8c932b27..d11b5f3e1cc 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -5782,6 +5782,18 @@ "url": "/_mozilla/css/word_break_a.html" } ], + "mozilla/blob_url_upload.html": [ + { + "path": "mozilla/blob_url_upload.html", + "references": [ + [ + "/_mozilla/mozilla/blob_url_upload_ref.html", + "==" + ] + ], + "url": "/_mozilla/mozilla/blob_url_upload.html" + } + ], "mozilla/canvas/drawimage_html_image_1.html": [ { "path": "mozilla/canvas/drawimage_html_image_1.html", @@ -14852,6 +14864,18 @@ "url": "/_mozilla/css/word_break_a.html" } ], + "mozilla/blob_url_upload.html": [ + { + "path": "mozilla/blob_url_upload.html", + "references": [ + [ + "/_mozilla/mozilla/blob_url_upload_ref.html", + "==" + ] + ], + "url": "/_mozilla/mozilla/blob_url_upload.html" + } + ], "mozilla/canvas/drawimage_html_image_1.html": [ { "path": "mozilla/canvas/drawimage_html_image_1.html", diff --git a/tests/wpt/mozilla/meta/mozilla/blob_url_upload.html.ini b/tests/wpt/mozilla/meta/mozilla/blob_url_upload.html.ini new file mode 100644 index 00000000000..6ad257ede65 --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/blob_url_upload.html.ini @@ -0,0 +1,3 @@ +[blob_url_upload.html] + type: reftest + prefs: [dom.testing.htmlinputelement.select_files.enabled:true] diff --git a/tests/wpt/mozilla/tests/mozilla/blob_url_upload.html b/tests/wpt/mozilla/tests/mozilla/blob_url_upload.html new file mode 100644 index 00000000000..17c8e3ad0b4 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/blob_url_upload.html @@ -0,0 +1,22 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Blob URL with File Upload</title> +<link rel="match" href="blob_url_upload_ref.html"> +<body> + <img src="" id="image"> + <input type="file" id="file-input""> +</body> +<script type="text/javascript"> + var image = document.getElementById("image"); + + var inputElem = document.getElementById("file-input"); + + inputElem.selectFiles(["./tests/wpt/mozilla/tests/mozilla/test.jpg"]); + + var f = inputElem.files[0]; + + var url = URL.createObjectURL(f); + + image.src = url; + +</script> diff --git a/tests/wpt/mozilla/tests/mozilla/blob_url_upload_ref.html b/tests/wpt/mozilla/tests/mozilla/blob_url_upload_ref.html new file mode 100644 index 00000000000..2ae08600b45 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/blob_url_upload_ref.html @@ -0,0 +1,7 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Reference: Blob URL with File Upload</title> +<body> + <img src="test.jpg" id="image"> + <input type="file" id="file-input""> +</body> |