implement related sw interface and register method

This commit is contained in:
Rahul Sharma 2016-04-15 17:40:45 +05:30
parent 0594d58bc8
commit 15a2064c0d
33 changed files with 1285 additions and 217 deletions

View file

@ -149,6 +149,7 @@ impl Formattable for ProfilerCategory {
ProfilerCategory::ScriptStylesheetLoad => "Script Stylesheet Load",
ProfilerCategory::ScriptWebSocketEvent => "Script Web Socket Event",
ProfilerCategory::ScriptWorkerEvent => "Script Worker Event",
ProfilerCategory::ScriptServiceWorkerEvent => "Script Service Worker Event",
ProfilerCategory::ApplicationHeartbeat => "Application Heartbeat",
};
format!("{}{}", padding, name)

View file

@ -80,6 +80,7 @@ pub enum ProfilerCategory {
ScriptUpdateReplacedElement,
ScriptWebSocketEvent,
ScriptWorkerEvent,
ScriptServiceWorkerEvent,
ApplicationHeartbeat,
}

View file

@ -0,0 +1,104 @@
/* 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 dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::Reflectable;
use dom::bindings::str::DOMString;
use dom::bindings::structuredclone::StructuredCloneData;
use js::jsapi::{JSRuntime, JS_RequestInterruptCallback};
use js::rust::Runtime;
use msg::constellation_msg::{PipelineId, ReferrerPolicy};
use net_traits::{LoadOrigin, RequestSource};
use script_runtime::CommonScriptMsg;
use url::Url;
/// Messages used to control the worker event loops
pub enum WorkerScriptMsg {
/// Common variants associated with the script messages
Common(CommonScriptMsg),
/// Message sent through Worker.postMessage
DOMMessage(StructuredCloneData),
}
#[derive(Clone)]
pub struct WorkerScriptLoadOrigin {
pub referrer_url: Option<Url>,
pub referrer_policy: Option<ReferrerPolicy>,
pub request_source: RequestSource,
pub pipeline_id: Option<PipelineId>
}
impl LoadOrigin for WorkerScriptLoadOrigin {
fn referrer_url(&self) -> Option<Url> {
self.referrer_url.clone()
}
fn referrer_policy(&self) -> Option<ReferrerPolicy> {
self.referrer_policy.clone()
}
fn request_source(&self) -> RequestSource {
self.request_source.clone()
}
fn pipeline_id(&self) -> Option<PipelineId> {
self.pipeline_id.clone()
}
}
pub struct SimpleWorkerErrorHandler<T: Reflectable> {
pub addr: Trusted<T>,
}
impl<T: Reflectable> SimpleWorkerErrorHandler<T> {
pub fn new(addr: Trusted<T>) -> SimpleWorkerErrorHandler<T> {
SimpleWorkerErrorHandler {
addr: addr
}
}
}
pub struct WorkerErrorHandler<T: Reflectable> {
pub addr: Trusted<T>,
pub msg: DOMString,
pub file_name: DOMString,
pub line_num: u32,
pub col_num: u32,
}
impl<T: Reflectable> WorkerErrorHandler<T> {
pub fn new(addr: Trusted<T>, msg: DOMString, file_name: DOMString, line_num: u32, col_num: u32)
-> WorkerErrorHandler<T> {
WorkerErrorHandler {
addr: addr,
msg: msg,
file_name: file_name,
line_num: line_num,
col_num: col_num,
}
}
}
#[derive(Copy, Clone)]
pub struct SharedRt {
pub rt: *mut JSRuntime
}
impl SharedRt {
pub fn new(rt: &Runtime) -> SharedRt {
SharedRt {
rt: rt.rt()
}
}
#[allow(unsafe_code)]
pub fn request_interrupt(&self) {
unsafe {
JS_RequestInterruptCallback(self.rt);
}
}
pub fn rt(&self) -> *mut JSRuntime {
self.rt
}
}
#[allow(unsafe_code)]
unsafe impl Send for SharedRt {}

View file

@ -0,0 +1,65 @@
/* 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 dom::abstractworker::WorkerScriptMsg;
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::Reflectable;
use script_runtime::{ScriptChan, CommonScriptMsg, ScriptPort};
use std::sync::mpsc::{Receiver, Sender};
/// A ScriptChan that can be cloned freely and will silently send a TrustedWorkerAddress with
/// common event loop messages. While this SendableWorkerScriptChan is alive, the associated
/// Worker object will remain alive.
#[derive(JSTraceable, Clone)]
pub struct SendableWorkerScriptChan<T: Reflectable> {
pub sender: Sender<(Trusted<T>, CommonScriptMsg)>,
pub worker: Trusted<T>,
}
impl<T: Reflectable + 'static> ScriptChan for SendableWorkerScriptChan<T> {
fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
self.sender.send((self.worker.clone(), msg)).map_err(|_| ())
}
fn clone(&self) -> Box<ScriptChan + Send> {
box SendableWorkerScriptChan {
sender: self.sender.clone(),
worker: self.worker.clone(),
}
}
}
/// A ScriptChan that can be cloned freely and will silently send a TrustedWorkerAddress with
/// worker event loop messages. While this SendableWorkerScriptChan is alive, the associated
/// Worker object will remain alive.
#[derive(JSTraceable, Clone)]
pub struct WorkerThreadWorkerChan<T: Reflectable> {
pub sender: Sender<(Trusted<T>, WorkerScriptMsg)>,
pub worker: Trusted<T>,
}
impl<T: Reflectable + 'static> ScriptChan for WorkerThreadWorkerChan<T> {
fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
self.sender
.send((self.worker.clone(), WorkerScriptMsg::Common(msg)))
.map_err(|_| ())
}
fn clone(&self) -> Box<ScriptChan + Send> {
box WorkerThreadWorkerChan {
sender: self.sender.clone(),
worker: self.worker.clone(),
}
}
}
impl<T: Reflectable> ScriptPort for Receiver<(Trusted<T>, WorkerScriptMsg)> {
fn recv(&self) -> Result<CommonScriptMsg, ()> {
match self.recv().map(|(_, msg)| msg) {
Ok(WorkerScriptMsg::Common(script_msg)) => Ok(script_msg),
Ok(WorkerScriptMsg::DOMMessage(_)) => panic!("unexpected worker event message!"),
Err(_) => Err(()),
}
}
}

View file

@ -81,6 +81,7 @@ impl ops::Deref for ByteString {
/// A string that is constructed from a UCS-2 buffer by replacing invalid code
/// points with the replacement character.
#[derive(Clone, HeapSizeOf)]
pub struct USVString(pub String);

View file

@ -34,12 +34,12 @@ use canvas_traits::{CompositionOrBlending, LineCapStyle, LineJoinStyle, Repetiti
use cssparser::RGBA;
use devtools_traits::CSSError;
use devtools_traits::WorkerId;
use dom::abstractworker::SharedRt;
use dom::bindings::js::{JS, Root};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::str::DOMString;
use dom::bindings::str::{DOMString, USVString};
use dom::bindings::utils::WindowProxyHandler;
use dom::worker::SharedRt;
use encoding::types::EncodingRef;
use euclid::length::Length as EuclidLength;
use euclid::matrix2d::Matrix2D;
@ -81,6 +81,7 @@ use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::mpsc::{Receiver, Sender};
use std::time::SystemTime;
use string_cache::{Atom, Namespace, QualName};
use style::attr::{AttrIdentifier, AttrValue};
use style::element_state::*;
@ -320,8 +321,10 @@ no_jsmanaged_fields!(ElementSnapshot);
no_jsmanaged_fields!(HttpsState);
no_jsmanaged_fields!(SharedRt);
no_jsmanaged_fields!(TouchpadPressurePhase);
no_jsmanaged_fields!(USVString);
no_jsmanaged_fields!(ReferrerPolicy);
no_jsmanaged_fields!(ResourceThreads);
no_jsmanaged_fields!(SystemTime);
impl JSTraceable for Box<ScriptChan + Send> {
#[inline]

View file

@ -0,0 +1,59 @@
/* 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 dom::bindings::codegen::Bindings::ClientBinding::FrameType;
use dom::bindings::codegen::Bindings::ClientBinding::{ClientMethods, Wrap};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::JS;
use dom::bindings::js::Root;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::{DOMString, USVString};
use dom::serviceworker::ServiceWorker;
use dom::window::Window;
use url::Url;
use uuid::Uuid;
#[dom_struct]
pub struct Client {
reflector_: Reflector,
active_worker: Option<JS<ServiceWorker>>,
url: USVString,
frame_type: FrameType,
#[ignore_heap_size_of = "Defined in uuid"]
id: Uuid
}
impl Client {
fn new_inherited(url: Url) -> Client {
Client {
reflector_: Reflector::new(),
active_worker: None,
url: USVString(url.as_str().to_owned()),
frame_type: FrameType::None,
id: Uuid::new_v4()
}
}
pub fn new(window: &Window) -> Root<Client> {
reflect_dom_object(box Client::new_inherited(window.get_url()), GlobalRef::Window(window), Wrap)
}
}
impl ClientMethods for Client {
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#client-url-attribute
fn Url(&self) -> USVString {
self.url.clone()
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#client-frametype
fn FrameType(&self) -> FrameType {
self.frame_type
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#client-id
fn Id(&self) -> DOMString {
let uid_str = format!("{}", self.id);
DOMString::from_string(uid_str)
}
}

View file

@ -4,6 +4,8 @@
use devtools;
use devtools_traits::DevtoolScriptControlMsg;
use dom::abstractworker::{WorkerScriptLoadOrigin, WorkerScriptMsg, SharedRt , SimpleWorkerErrorHandler};
use dom::abstractworkerglobalscope::{SendableWorkerScriptChan, WorkerThreadWorkerChan};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
@ -17,8 +19,7 @@ use dom::bindings::reflector::Reflectable;
use dom::bindings::str::DOMString;
use dom::bindings::structuredclone::StructuredCloneData;
use dom::messageevent::MessageEvent;
use dom::worker::{SimpleWorkerErrorHandler, SharedRt, TrustedWorkerAddress};
use dom::worker::{WorkerScriptLoadOrigin, WorkerMessageHandler};
use dom::worker::{TrustedWorkerAddress, WorkerMessageHandler};
use dom::workerglobalscope::WorkerGlobalScope;
use dom::workerglobalscope::WorkerGlobalScopeInit;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@ -40,70 +41,6 @@ use url::Url;
use util::thread::spawn_named_with_send_on_panic;
use util::thread_state::{IN_WORKER, SCRIPT};
/// Messages used to control the worker event loops
pub enum WorkerScriptMsg {
/// Common variants associated with the script messages
Common(CommonScriptMsg),
/// Message sent through Worker.postMessage
DOMMessage(StructuredCloneData),
}
/// A ScriptChan that can be cloned freely and will silently send a TrustedWorkerAddress with
/// common event loop messages. While this SendableWorkerScriptChan is alive, the associated
/// Worker object will remain alive.
#[derive(JSTraceable, Clone)]
pub struct SendableWorkerScriptChan {
sender: Sender<(TrustedWorkerAddress, CommonScriptMsg)>,
worker: TrustedWorkerAddress,
}
impl ScriptChan for SendableWorkerScriptChan {
fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
self.sender.send((self.worker.clone(), msg)).map_err(|_| ())
}
fn clone(&self) -> Box<ScriptChan + Send> {
box SendableWorkerScriptChan {
sender: self.sender.clone(),
worker: self.worker.clone(),
}
}
}
/// A ScriptChan that can be cloned freely and will silently send a TrustedWorkerAddress with
/// worker event loop messages. While this SendableWorkerScriptChan is alive, the associated
/// Worker object will remain alive.
#[derive(JSTraceable, Clone)]
pub struct WorkerThreadWorkerChan {
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
worker: TrustedWorkerAddress,
}
impl ScriptChan for WorkerThreadWorkerChan {
fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
self.sender
.send((self.worker.clone(), WorkerScriptMsg::Common(msg)))
.map_err(|_| ())
}
fn clone(&self) -> Box<ScriptChan + Send> {
box WorkerThreadWorkerChan {
sender: self.sender.clone(),
worker: self.worker.clone(),
}
}
}
impl ScriptPort for Receiver<(TrustedWorkerAddress, WorkerScriptMsg)> {
fn recv(&self) -> Result<CommonScriptMsg, ()> {
match self.recv().map(|(_, msg)| msg) {
Ok(WorkerScriptMsg::Common(script_msg)) => Ok(script_msg),
Ok(WorkerScriptMsg::DOMMessage(_)) => panic!("unexpected worker event message!"),
Err(_) => Err(()),
}
}
}
/// Set the `worker` field of a related DedicatedWorkerGlobalScope object to a particular
/// value for the duration of this object's lifetime. This ensures that the related Worker
/// object only lives as long as necessary (ie. while events are being executed), while

View file

@ -209,6 +209,8 @@ pub mod types {
include!(concat!(env!("OUT_DIR"), "/InterfaceTypes.rs"));
}
pub mod abstractworker;
pub mod abstractworkerglobalscope;
pub mod activation;
pub mod attr;
pub mod beforeunloadevent;
@ -231,6 +233,7 @@ pub mod canvasgradient;
pub mod canvaspattern;
pub mod canvasrenderingcontext2d;
pub mod characterdata;
pub mod client;
pub mod closeevent;
pub mod comment;
pub mod console;
@ -363,6 +366,10 @@ pub mod progressevent;
pub mod radionodelist;
pub mod range;
pub mod screen;
pub mod serviceworker;
pub mod serviceworkercontainer;
pub mod serviceworkerglobalscope;
pub mod serviceworkerregistration;
pub mod servohtmlparser;
pub mod servoxmlparser;
pub mod storage;

View file

@ -12,6 +12,7 @@ use dom::bluetooth::Bluetooth;
use dom::mimetypearray::MimeTypeArray;
use dom::navigatorinfo;
use dom::pluginarray::PluginArray;
use dom::serviceworkercontainer::ServiceWorkerContainer;
use dom::window::Window;
#[dom_struct]
@ -20,6 +21,7 @@ pub struct Navigator {
bluetooth: MutNullableHeap<JS<Bluetooth>>,
plugins: MutNullableHeap<JS<PluginArray>>,
mime_types: MutNullableHeap<JS<MimeTypeArray>>,
serviceWorker: MutNullableHeap<JS<ServiceWorkerContainer>>,
}
impl Navigator {
@ -29,6 +31,7 @@ impl Navigator {
bluetooth: Default::default(),
plugins: Default::default(),
mime_types: Default::default(),
serviceWorker: Default::default(),
}
}
@ -99,4 +102,9 @@ impl NavigatorMethods for Navigator {
fn JavaEnabled(&self) -> bool {
false
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-attribute
fn ServiceWorker(&self) -> Root<ServiceWorkerContainer> {
self.serviceWorker.or_init(|| ServiceWorkerContainer::new(self.global().r()))
}
}

View file

@ -0,0 +1,172 @@
/* 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 dom::abstractworker::{SimpleWorkerErrorHandler, WorkerErrorHandler};
use dom::abstractworker::{WorkerScriptMsg, WorkerScriptLoadOrigin, SharedRt};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::ServiceWorkerBinding::{ServiceWorkerMethods, ServiceWorkerState, Wrap};
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::Root;
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflectable, reflect_dom_object};
use dom::bindings::str::{DOMString, USVString};
use dom::client::Client;
use dom::errorevent::ErrorEvent;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::EventTarget;
use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use dom::workerglobalscope::prepare_workerscope_init;
use ipc_channel::ipc;
use js::jsapi::RootedValue;
use js::jsval::UndefinedValue;
use script_thread::Runnable;
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Sender, channel};
use std::sync::{Arc, Mutex};
use url::Url;
pub type TrustedServiceWorkerAddress = Trusted<ServiceWorker>;
#[dom_struct]
pub struct ServiceWorker {
eventtarget: EventTarget,
script_url: DOMRefCell<String>,
state: Cell<ServiceWorkerState>,
closing: Arc<AtomicBool>,
#[ignore_heap_size_of = "Defined in std"]
sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
#[ignore_heap_size_of = "Defined in rust-mozjs"]
runtime: Arc<Mutex<Option<SharedRt>>>,
skip_waiting: Cell<bool>
}
impl ServiceWorker {
fn new_inherited(sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
closing: Arc<AtomicBool>,
script_url: &str,
skip_waiting: bool) -> ServiceWorker {
ServiceWorker {
eventtarget: EventTarget::new_inherited(),
closing: closing,
sender: sender,
script_url: DOMRefCell::new(String::from(script_url)),
state: Cell::new(ServiceWorkerState::Installing),
runtime: Arc::new(Mutex::new(None)),
skip_waiting: Cell::new(skip_waiting)
}
}
pub fn new(global: GlobalRef,
closing: Arc<AtomicBool>,
sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
script_url: &str,
skip_waiting: bool) -> Root<ServiceWorker> {
reflect_dom_object(box ServiceWorker::new_inherited(sender, closing, script_url, skip_waiting), global, Wrap)
}
pub fn dispatch_simple_error(address: TrustedServiceWorkerAddress) {
let service_worker = address.root();
service_worker.upcast().fire_simple_event("error");
}
pub fn is_closing(&self) -> bool {
self.closing.load(Ordering::SeqCst)
}
pub fn set_transition_state(&self, state: ServiceWorkerState) {
self.state.set(state);
self.upcast::<EventTarget>().fire_simple_event("statechange");
}
pub fn handle_error_message(address: TrustedServiceWorkerAddress, message: DOMString,
filename: DOMString, lineno: u32, colno: u32) {
let worker = address.root();
if worker.is_closing() {
return;
}
let global = worker.r().global();
let error = RootedValue::new(global.r().get_cx(), UndefinedValue());
let errorevent = ErrorEvent::new(global.r(), atom!("error"),
EventBubbles::Bubbles, EventCancelable::Cancelable,
message, filename, lineno, colno, error.handle());
errorevent.upcast::<Event>().fire(worker.upcast());
}
#[allow(unsafe_code)]
pub fn init_service_worker(global: GlobalRef,
script_url: Url,
skip_waiting: bool) -> Root<ServiceWorker> {
let (sender, receiver) = channel();
let closing = Arc::new(AtomicBool::new(false));
let worker = ServiceWorker::new(global,
closing.clone(),
sender.clone(),
script_url.as_str(),
skip_waiting);
let worker_ref = Trusted::new(worker.r());
let worker_load_origin = WorkerScriptLoadOrigin {
referrer_url: None,
referrer_policy: None,
request_source: global.request_source(),
pipeline_id: Some(global.pipeline())
};
let (devtools_sender, devtools_receiver) = ipc::channel().unwrap();
let init = prepare_workerscope_init(global,
"Service Worker".to_owned(),
script_url.clone(),
devtools_sender.clone(),
closing);
// represents a service worker client
let sw_client = Client::new(global.as_window());
let trusted_client = Trusted::new(&*sw_client);
ServiceWorkerGlobalScope::run_serviceworker_scope(
init, script_url, global.pipeline(), devtools_receiver, worker.runtime.clone(), worker_ref,
global.script_chan(), sender, receiver, trusted_client, worker_load_origin);
worker
}
}
impl ServiceWorkerMethods for ServiceWorker {
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-state-attribute
fn State(&self) -> ServiceWorkerState {
self.state.get()
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-url-attribute
fn ScriptURL(&self) -> USVString {
USVString(self.script_url.borrow().clone())
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-container-onerror-attribute
event_handler!(error, GetOnerror, SetOnerror);
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#ref-for-service-worker-onstatechange-attribute-1
event_handler!(statechange, GetOnstatechange, SetOnstatechange);
}
impl Runnable for SimpleWorkerErrorHandler<ServiceWorker> {
#[allow(unrooted_must_root)]
fn handler(self: Box<SimpleWorkerErrorHandler<ServiceWorker>>) {
let this = *self;
ServiceWorker::dispatch_simple_error(this.addr);
}
}
impl Runnable for WorkerErrorHandler<ServiceWorker> {
#[allow(unrooted_must_root)]
fn handler(self: Box<WorkerErrorHandler<ServiceWorker>>) {
let this = *self;
ServiceWorker::handle_error_message(this.addr, this.msg, this.file_name, this.line_num, this.col_num);
}
}

View file

@ -0,0 +1,104 @@
/* 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 dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions;
use dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{ServiceWorkerContainerMethods, Wrap};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::bindings::reflector::{Reflectable, reflect_dom_object};
use dom::bindings::str::USVString;
use dom::eventtarget::EventTarget;
use dom::serviceworker::ServiceWorker;
use dom::serviceworkerregistration::ServiceWorkerRegistration;
use script_thread::ScriptThread;
use std::ascii::AsciiExt;
use std::default::Default;
#[dom_struct]
pub struct ServiceWorkerContainer {
eventtarget: EventTarget,
controller: MutNullableHeap<JS<ServiceWorker>>,
}
impl ServiceWorkerContainer {
fn new_inherited() -> ServiceWorkerContainer {
ServiceWorkerContainer {
eventtarget: EventTarget::new_inherited(),
controller: Default::default(),
}
}
pub fn new(global: GlobalRef) -> Root<ServiceWorkerContainer> {
reflect_dom_object(box ServiceWorkerContainer::new_inherited(), global, Wrap)
}
}
pub trait Controllable {
fn set_controller(&self, active_worker: &ServiceWorker);
}
impl Controllable for ServiceWorkerContainer {
fn set_controller(&self, active_worker: &ServiceWorker) {
self.controller.set(Some(active_worker))
}
}
impl ServiceWorkerContainerMethods for ServiceWorkerContainer {
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-container-controller-attribute
fn GetController(&self) -> Option<Root<ServiceWorker>> {
return self.controller.get()
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-container-register-method
fn Register(&self,
script_url: USVString,
options: &RegistrationOptions) -> Fallible<Root<ServiceWorkerRegistration>> {
let USVString(ref script_url) = script_url;
// Step 3-4
let script_url = match self.global().r().api_base_url().join(script_url) {
Ok(url) => url,
Err(_) => return Err(Error::Type("Invalid script URL".to_owned()))
};
// Step 5
match script_url.scheme() {
"https" | "http" => {},
_ => return Err(Error::Type("Only secure origins are allowed".to_owned()))
}
// Step 6
if script_url.path().to_ascii_lowercase().contains("%2f") ||
script_url.path().to_ascii_lowercase().contains("%5c") {
return Err(Error::Type("Script URL contains forbidden characters".to_owned()));
}
// Step 8-9
let scope = match options.scope {
Some(ref scope) => {
let &USVString(ref inner_scope) = scope;
match self.global().r().api_base_url().join(inner_scope) {
Ok(url) => url,
Err(_) => return Err(Error::Type("Invalid scope URL".to_owned()))
}
},
None => script_url.join("./").unwrap()
};
// Step 11
match scope.scheme() {
"https" | "http" => {},
_ => return Err(Error::Type("Only secure origins are allowed".to_owned()))
}
// Step 12
if scope.path().to_ascii_lowercase().contains("%2f") ||
scope.path().to_ascii_lowercase().contains("%5c") {
return Err(Error::Type("Scope URL contains forbidden characters".to_owned()));
}
let scope_str = scope.as_str().to_owned();
let worker_registration = ServiceWorkerRegistration::new(self.global().r(),
script_url,
scope_str.clone(),
self);
ScriptThread::set_registration(scope, &*worker_registration);
Ok(worker_registration)
}
}

View file

@ -0,0 +1,382 @@
/* 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::{WorkerScriptLoadOrigin, WorkerScriptMsg, SharedRt, SimpleWorkerErrorHandler};
use dom::abstractworkerglobalscope::{SendableWorkerScriptChan, WorkerThreadWorkerChan};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
use dom::bindings::global::{GlobalRef, global_root_from_context};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{Root, RootCollection};
use dom::bindings::refcounted::{Trusted, LiveDOMReferences};
use dom::bindings::reflector::Reflectable;
use dom::bindings::str::DOMString;
use dom::client::Client;
use dom::messageevent::MessageEvent;
use dom::serviceworker::TrustedServiceWorkerAddress;
use dom::workerglobalscope::WorkerGlobalScope;
use dom::workerglobalscope::WorkerGlobalScopeInit;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use js::jsapi::{JS_SetInterruptCallback, JSAutoCompartment, JSContext, RootedValue};
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use msg::constellation_msg::PipelineId;
use net_traits::{LoadContext, load_whole_resource, CustomResponse, IpcSend};
use rand::random;
use script_runtime::ScriptThreadEventCategory::ServiceWorkerEvent;
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, StackRootTLS, get_reports, new_rt_and_cx};
use script_traits::{TimerEvent, TimerSource};
use std::mem::replace;
use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use url::Url;
use util::prefs;
use util::thread::spawn_named;
use util::thread_state;
use util::thread_state::{IN_WORKER, SCRIPT};
/// Set the `worker` field of a related ServiceWorkerGlobalScope object to a particular
/// value for the duration of this object's lifetime. This ensures that the related Worker
/// object only lives as long as necessary (ie. while events are being executed), while
/// providing a reference that can be cloned freely.
struct AutoWorkerReset<'a> {
workerscope: &'a ServiceWorkerGlobalScope,
old_worker: Option<TrustedServiceWorkerAddress>,
}
impl<'a> AutoWorkerReset<'a> {
fn new(workerscope: &'a ServiceWorkerGlobalScope,
worker: TrustedServiceWorkerAddress)
-> AutoWorkerReset<'a> {
AutoWorkerReset {
workerscope: workerscope,
old_worker: replace(&mut *workerscope.worker.borrow_mut(), Some(worker)),
}
}
}
impl<'a> Drop for AutoWorkerReset<'a> {
fn drop(&mut self) {
*self.workerscope.worker.borrow_mut() = self.old_worker.clone();
}
}
enum MixedMessage {
FromServiceWorker((TrustedServiceWorkerAddress, WorkerScriptMsg)),
FromScheduler((TrustedServiceWorkerAddress, TimerEvent)),
FromDevtools(DevtoolScriptControlMsg),
FromNetwork(IpcSender<Option<CustomResponse>>),
}
#[dom_struct]
pub struct ServiceWorkerGlobalScope {
workerglobalscope: WorkerGlobalScope,
id: PipelineId,
#[ignore_heap_size_of = "Defined in std"]
receiver: Receiver<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
#[ignore_heap_size_of = "Defined in std"]
own_sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
#[ignore_heap_size_of = "Defined in std"]
timer_event_port: Receiver<(TrustedServiceWorkerAddress, TimerEvent)>,
#[ignore_heap_size_of = "Trusted<T> has unclear ownership like JS<T>"]
worker: DOMRefCell<Option<TrustedServiceWorkerAddress>>,
#[ignore_heap_size_of = "Can't measure trait objects"]
/// Sender to the parent thread.
parent_sender: Box<ScriptChan + Send>,
#[ignore_heap_size_of = "Defined in std"]
service_worker_client: Trusted<Client>
}
impl ServiceWorkerGlobalScope {
fn new_inherited(init: WorkerGlobalScopeInit,
worker_url: Url,
id: PipelineId,
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
runtime: Runtime,
parent_sender: Box<ScriptChan + Send>,
own_sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
receiver: Receiver<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
timer_event_chan: IpcSender<TimerEvent>,
timer_event_port: Receiver<(TrustedServiceWorkerAddress, TimerEvent)>,
client: Trusted<Client>)
-> ServiceWorkerGlobalScope {
ServiceWorkerGlobalScope {
workerglobalscope: WorkerGlobalScope::new_inherited(init,
worker_url,
runtime,
from_devtools_receiver,
timer_event_chan),
id: id,
receiver: receiver,
own_sender: own_sender,
timer_event_port: timer_event_port,
parent_sender: parent_sender,
worker: DOMRefCell::new(None),
service_worker_client: client
}
}
pub fn new(init: WorkerGlobalScopeInit,
worker_url: Url,
id: PipelineId,
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
runtime: Runtime,
parent_sender: Box<ScriptChan + Send>,
own_sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
receiver: Receiver<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
timer_event_chan: IpcSender<TimerEvent>,
timer_event_port: Receiver<(TrustedServiceWorkerAddress, TimerEvent)>,
client: Trusted<Client>)
-> Root<ServiceWorkerGlobalScope> {
let cx = runtime.cx();
let scope = box ServiceWorkerGlobalScope::new_inherited(init,
worker_url,
id,
from_devtools_receiver,
runtime,
parent_sender,
own_sender,
receiver,
timer_event_chan,
timer_event_port,
client);
ServiceWorkerGlobalScopeBinding::Wrap(cx, scope)
}
#[allow(unsafe_code)]
pub fn run_serviceworker_scope(init: WorkerGlobalScopeInit,
worker_url: Url,
id: PipelineId,
from_devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
main_thread_rt: Arc<Mutex<Option<SharedRt>>>,
worker: TrustedServiceWorkerAddress,
parent_sender: Box<ScriptChan + Send>,
own_sender: Sender<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
receiver: Receiver<(TrustedServiceWorkerAddress, WorkerScriptMsg)>,
client: Trusted<Client>,
worker_load_origin: WorkerScriptLoadOrigin) {
let serialized_worker_url = worker_url.to_string();
spawn_named(format!("ServiceWorker for {}", serialized_worker_url), move || {
thread_state::initialize(SCRIPT | IN_WORKER);
let roots = RootCollection::new();
let _stack_roots_tls = StackRootTLS::new(&roots);
let (url, source) = match load_whole_resource(LoadContext::Script,
&init.resource_threads.sender(),
worker_url,
&worker_load_origin) {
Err(_) => {
println!("error loading script {}", serialized_worker_url);
parent_sender.send(CommonScriptMsg::RunnableMsg(ServiceWorkerEvent,
box SimpleWorkerErrorHandler::new(worker))).unwrap();
return;
}
Ok((metadata, bytes)) => {
(metadata.final_url, String::from_utf8(bytes).unwrap())
}
};
let runtime = unsafe { new_rt_and_cx() };
*main_thread_rt.lock().unwrap() = Some(SharedRt::new(&runtime));
let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
ROUTER.route_ipc_receiver_to_mpsc_sender(from_devtools_receiver, devtools_mpsc_chan);
let (timer_tx, timer_rx) = channel();
let (timer_ipc_chan, timer_ipc_port) = ipc::channel().unwrap();
let worker_for_route = worker.clone();
ROUTER.add_route(timer_ipc_port.to_opaque(), box move |message| {
let event = message.to().unwrap();
timer_tx.send((worker_for_route.clone(), event)).unwrap();
});
let global = ServiceWorkerGlobalScope::new(
init, url, id, devtools_mpsc_port, runtime,
parent_sender.clone(), own_sender, receiver,
timer_ipc_chan, timer_rx, client);
// FIXME(njn): workers currently don't have a unique ID suitable for using in reporter
// registration (#6631), so we instead use a random number and cross our fingers.
let scope = global.upcast::<WorkerGlobalScope>();
unsafe {
// Handle interrupt requests
JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback));
}
if scope.is_closing() {
return;
}
{
let _ar = AutoWorkerReset::new(global.r(), worker);
scope.execute_script(DOMString::from(source));
}
let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
scope.mem_profiler_chan().run_with_memory_reporting(|| {
// Service workers are time limited
let sw_lifetime = Instant::now();
let sw_lifetime_timeout = prefs::get_pref("dom.serviceworker.timeout_seconds").as_u64().unwrap();
while let Ok(event) = global.receive_event() {
if scope.is_closing() {
break;
}
global.handle_event(event);
if sw_lifetime.elapsed().as_secs() == sw_lifetime_timeout {
break;
}
}
}, reporter_name, parent_sender, CommonScriptMsg::CollectReports);
});
}
fn handle_event(&self, event: MixedMessage) {
match event {
MixedMessage::FromDevtools(msg) => {
let global_ref = GlobalRef::Worker(self.upcast());
match msg {
DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) =>
devtools::handle_evaluate_js(&global_ref, 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(&global_ref, bool_val),
_ => debug!("got an unusable devtools control message inside the worker!"),
}
},
MixedMessage::FromScheduler((linked_worker, timer_event)) => {
match timer_event {
TimerEvent(TimerSource::FromWorker, id) => {
let _ar = AutoWorkerReset::new(self, linked_worker);
let scope = self.upcast::<WorkerGlobalScope>();
scope.handle_fire_timer(id);
},
TimerEvent(_, _) => {
panic!("The service worker received a TimerEvent from a window.")
}
}
}
MixedMessage::FromServiceWorker((linked_worker, msg)) => {
let _ar = AutoWorkerReset::new(self, linked_worker);
self.handle_script_event(msg);
},
MixedMessage::FromNetwork(network_sender) => {
// We send None as of now
let _ = network_sender.send(None);
}
}
}
fn handle_script_event(&self, msg: WorkerScriptMsg) {
match msg {
WorkerScriptMsg::DOMMessage(data) => {
let scope = self.upcast::<WorkerGlobalScope>();
let target = self.upcast();
let _ac = JSAutoCompartment::new(scope.get_cx(),
scope.reflector().get_jsobject().get());
let mut message = RootedValue::new(scope.get_cx(), UndefinedValue());
data.read(GlobalRef::Worker(scope), message.handle_mut());
MessageEvent::dispatch_jsval(target, GlobalRef::Worker(scope), message.handle());
},
WorkerScriptMsg::Common(CommonScriptMsg::RunnableMsg(_, runnable)) => {
runnable.handler()
},
WorkerScriptMsg::Common(CommonScriptMsg::RefcountCleanup(addr)) => {
LiveDOMReferences::cleanup(addr);
},
WorkerScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan)) => {
let scope = self.upcast::<WorkerGlobalScope>();
let cx = scope.get_cx();
let path_seg = format!("url({})", scope.get_url());
let reports = get_reports(cx, path_seg);
reports_chan.send(reports);
},
}
}
#[allow(unsafe_code)]
fn receive_event(&self) -> Result<MixedMessage, RecvError> {
let scope = self.upcast::<WorkerGlobalScope>();
let worker_port = &self.receiver;
let timer_event_port = &self.timer_event_port;
let devtools_port = scope.from_devtools_receiver();
let msg_port = scope.custom_message_port();
let sel = Select::new();
let mut worker_handle = sel.handle(worker_port);
let mut timer_event_handle = sel.handle(timer_event_port);
let mut devtools_handle = sel.handle(devtools_port);
let mut msg_port_handle = sel.handle(msg_port);
unsafe {
worker_handle.add();
timer_event_handle.add();
if scope.from_devtools_sender().is_some() {
devtools_handle.add();
}
msg_port_handle.add();
}
let ret = sel.wait();
if ret == worker_handle.id() {
Ok(MixedMessage::FromServiceWorker(try!(worker_port.recv())))
} else if ret == timer_event_handle.id() {
Ok(MixedMessage::FromScheduler(try!(timer_event_port.recv())))
} else if ret == devtools_handle.id() {
Ok(MixedMessage::FromDevtools(try!(devtools_port.recv())))
} else if ret == msg_port_handle.id() {
Ok(MixedMessage::FromNetwork(try!(msg_port.recv())))
} else {
panic!("unexpected select result!")
}
}
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
box WorkerThreadWorkerChan {
sender: self.own_sender.clone(),
worker: self.worker.borrow().as_ref().unwrap().clone(),
}
}
pub fn pipeline(&self) -> PipelineId {
self.id
}
pub fn process_event(&self, msg: CommonScriptMsg) {
self.handle_script_event(WorkerScriptMsg::Common(msg));
}
pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
let (tx, rx) = channel();
let chan = box SendableWorkerScriptChan {
sender: tx,
worker: self.worker.borrow().as_ref().unwrap().clone(),
};
(chan, box rx)
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
let global = global_root_from_context(cx);
let worker = match global.r() {
GlobalRef::Worker(w) => w,
_ => panic!("global for worker 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://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-global-scope-onmessage-attribute
event_handler!(message, GetOnmessage, SetOnmessage);
}

View file

@ -0,0 +1,67 @@
/* 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 dom::bindings::codegen::Bindings::ServiceWorkerBinding::ServiceWorkerState;
use dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::{ServiceWorkerRegistrationMethods, Wrap};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::str::USVString;
use dom::eventtarget::EventTarget;
use dom::serviceworker::ServiceWorker;
use dom::serviceworkercontainer::Controllable;
use url::Url;
#[dom_struct]
pub struct ServiceWorkerRegistration {
eventtarget: EventTarget,
active: Option<JS<ServiceWorker>>,
installing: Option<JS<ServiceWorker>>,
waiting: Option<JS<ServiceWorker>>,
scope: String,
}
impl ServiceWorkerRegistration {
fn new_inherited(active_sw: &ServiceWorker, scope: String) -> ServiceWorkerRegistration {
ServiceWorkerRegistration {
eventtarget: EventTarget::new_inherited(),
active: Some(JS::from_ref(active_sw)),
installing: None,
waiting: None,
scope: scope
}
}
#[allow(unrooted_must_root)]
pub fn new(global: GlobalRef,
script_url: Url,
scope: String,
container: &Controllable) -> Root<ServiceWorkerRegistration> {
let active_worker = ServiceWorker::init_service_worker(global, script_url, true);
active_worker.set_transition_state(ServiceWorkerState::Installed);
container.set_controller(&*active_worker.clone());
reflect_dom_object(box ServiceWorkerRegistration::new_inherited(&*active_worker, scope), global, Wrap)
}
}
impl ServiceWorkerRegistrationMethods for ServiceWorkerRegistration {
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-registration-installing-attribute
fn GetInstalling(&self) -> Option<Root<ServiceWorker>> {
self.installing.as_ref().map(|sw| Root::from_ref(&**sw))
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-registration-active-attribute
fn GetActive(&self) -> Option<Root<ServiceWorker>> {
self.active.as_ref().map(|sw| Root::from_ref(&**sw))
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-registration-waiting-attribute
fn GetWaiting(&self) -> Option<Root<ServiceWorker>> {
self.waiting.as_ref().map(|sw| Root::from_ref(&**sw))
}
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-registration-scope-attribute
fn Scope(&self) -> USVString {
USVString(self.scope.clone())
}
}

View file

@ -0,0 +1,21 @@
/* 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/. */
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#client
// [Exposed=ServiceWorker]
[Pref="dom.serviceworker.enabled"]
interface Client {
readonly attribute USVString url;
readonly attribute FrameType frameType;
readonly attribute DOMString id;
//void postMessage(any message, optional sequence<Transferable> transfer);
};
enum FrameType {
"auxiliary",
"top-level",
"nested",
"none"
};

View file

@ -31,6 +31,11 @@ interface NavigatorBluetooth {
readonly attribute Bluetooth bluetooth;
};
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker
partial interface Navigator {
[SameObject, Pref="dom.serviceworker.enabled"] readonly attribute ServiceWorkerContainer serviceWorker;
};
// https://html.spec.whatwg.org/multipage/#navigatorlanguage
[NoInterfaceObject/*, Exposed=Window,Worker*/]
interface NavigatorLanguage {

View file

@ -0,0 +1,25 @@
/* 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/. */
// http://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-obj
//[Exposed=(Window,Worker)]
[Pref="dom.serviceworker.enabled"]
interface ServiceWorker : EventTarget {
readonly attribute USVString scriptURL;
readonly attribute ServiceWorkerState state;
//[Throws] void postMessage(any message/*, optional sequence<Transferable> transfer*/);
// event
attribute EventHandler onstatechange;
};
ServiceWorker implements AbstractWorker;
enum ServiceWorkerState {
"installing",
"installed",
"activating",
"activated",
"redundant"
};

View file

@ -0,0 +1,27 @@
/* 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/. */
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-container
// [Exposed=(Window,Worker)]
[Pref="dom.serviceworker.enabled"]
interface ServiceWorkerContainer : EventTarget {
[Unforgeable] readonly attribute ServiceWorker? controller;
//[SameObject] readonly attribute Promise<ServiceWorkerRegistration> ready;
[NewObject, Throws] ServiceWorkerRegistration register(USVString scriptURL, optional RegistrationOptions options);
//[NewObject] /*Promise<any>*/ any getRegistration(optional USVString clientURL = "");
//[NewObject] /* Promise */<sequence<ServiceWorkerRegistration>> getRegistrations();
// events
//attribute EventHandler oncontrollerchange;
//attribute EventHandler onerror;
//attribute EventHandler onmessage; // event.source of message events is ServiceWorker object
};
dictionary RegistrationOptions {
USVString scope;
//WorkerType type = "classic";
};

View file

@ -0,0 +1,23 @@
/* 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/. */
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-global-scope
[Global, Pref="dom.serviceworker.enabled"/*=(Worker,ServiceWorker), Exposed=ServiceWorker*/]
interface ServiceWorkerGlobalScope : WorkerGlobalScope {
// A container for a list of Client objects that correspond to
// browsing contexts (or shared workers) that are on the origin of this SW
//[SameObject] readonly attribute Clients clients;
//[SameObject] readonly attribute ServiceWorkerRegistration registration;
//[NewObject] Promise<void> skipWaiting();
//attribute EventHandler oninstall;
//attribute EventHandler onactivate;
//attribute EventHandler onfetch;
//attribute EventHandler onforeignfetch;
// event
attribute EventHandler onmessage; // event.source of the message events is Client object
};

View file

@ -0,0 +1,20 @@
/* 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/. */
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-registration-obj
//[Exposed=(Window,Worker)]
[Pref="dom.serviceworker.enabled"]
interface ServiceWorkerRegistration : EventTarget {
[Unforgeable] readonly attribute ServiceWorker? installing;
[Unforgeable] readonly attribute ServiceWorker? waiting;
[Unforgeable] readonly attribute ServiceWorker? active;
readonly attribute USVString scope;
// [NewObject] Promise<void> update();
// [NewObject] Promise<boolean> unregister();
// event
// attribute EventHandler onupdatefound;
};

View file

@ -2,7 +2,9 @@
* 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_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg};
use dom::abstractworker::{SimpleWorkerErrorHandler, SharedRt, WorkerErrorHandler};
use dom::abstractworker::{WorkerScriptLoadOrigin, WorkerScriptMsg};
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::WorkerBinding;
use dom::bindings::codegen::Bindings::WorkerBinding::WorkerMethods;
@ -14,24 +16,19 @@ use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflectable, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bindings::structuredclone::StructuredCloneData;
use dom::dedicatedworkerglobalscope::{DedicatedWorkerGlobalScope, WorkerScriptMsg};
use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use dom::errorevent::ErrorEvent;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::EventTarget;
use dom::messageevent::MessageEvent;
use dom::workerglobalscope::WorkerGlobalScopeInit;
use dom::workerglobalscope::prepare_workerscope_init;
use ipc_channel::ipc;
use js::jsapi::{HandleValue, JSContext, JSRuntime, RootedValue};
use js::jsapi::{JSAutoCompartment, JS_RequestInterruptCallback};
use js::jsapi::{HandleValue, JSContext, RootedValue, JSAutoCompartment};
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use msg::constellation_msg::{PipelineId, ReferrerPolicy};
use net_traits::{RequestSource, LoadOrigin};
use script_thread::Runnable;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Sender, channel};
use std::sync::{Arc, Mutex};
use url::Url;
pub type TrustedWorkerAddress = Trusted<Worker>;
@ -48,29 +45,6 @@ pub struct Worker {
runtime: Arc<Mutex<Option<SharedRt>>>
}
#[derive(Clone)]
pub struct WorkerScriptLoadOrigin {
referrer_url: Option<Url>,
referrer_policy: Option<ReferrerPolicy>,
request_source: RequestSource,
pipeline_id: Option<PipelineId>
}
impl LoadOrigin for WorkerScriptLoadOrigin {
fn referrer_url(&self) -> Option<Url> {
self.referrer_url.clone()
}
fn referrer_policy(&self) -> Option<ReferrerPolicy> {
self.referrer_policy.clone()
}
fn request_source(&self) -> RequestSource {
self.request_source.clone()
}
fn pipeline_id(&self) -> Option<PipelineId> {
self.pipeline_id.clone()
}
}
impl Worker {
fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
closing: Arc<AtomicBool>) -> Worker {
@ -99,15 +73,10 @@ impl Worker {
Err(_) => return Err(Error::Syntax),
};
let resource_threads = global.resource_threads();
let constellation_chan = global.constellation_chan().clone();
let scheduler_chan = global.scheduler_chan().clone();
let (sender, receiver) = channel();
let closing = Arc::new(AtomicBool::new(false));
let worker = Worker::new(global, sender.clone(), closing.clone());
let worker_ref = Trusted::new(worker.r());
let worker_id = global.get_next_worker_id();
let worker_load_origin = WorkerScriptLoadOrigin {
referrer_url: None,
@ -117,34 +86,12 @@ impl Worker {
};
let (devtools_sender, devtools_receiver) = ipc::channel().unwrap();
let optional_sender = match global.devtools_chan() {
Some(ref chan) => {
let pipeline_id = global.pipeline();
let title = format!("Worker for {}", worker_url);
let page_info = DevtoolsPageInfo {
title: title,
url: worker_url.clone(),
};
chan.send(ScriptToDevtoolsControlMsg::NewGlobal((pipeline_id, Some(worker_id)),
devtools_sender.clone(),
page_info)).unwrap();
Some(devtools_sender)
},
None => None,
};
let init = WorkerGlobalScopeInit {
resource_threads: resource_threads,
mem_profiler_chan: global.mem_profiler_chan().clone(),
time_profiler_chan: global.time_profiler_chan().clone(),
to_devtools_sender: global.devtools_chan(),
from_devtools_sender: optional_sender,
constellation_chan: constellation_chan,
scheduler_chan: scheduler_chan,
panic_chan: global.panic_chan().clone(),
worker_id: worker_id,
closing: closing,
};
let init = prepare_workerscope_init(global,
"Worker".to_owned(),
worker_url.clone(),
devtools_sender.clone(),
closing);
DedicatedWorkerGlobalScope::run_worker_scope(
init, worker_url, global.pipeline(), devtools_receiver, worker.runtime.clone(), worker_ref,
@ -245,76 +192,18 @@ impl Runnable for WorkerMessageHandler {
}
}
pub struct SimpleWorkerErrorHandler {
addr: TrustedWorkerAddress,
}
impl SimpleWorkerErrorHandler {
pub fn new(addr: TrustedWorkerAddress) -> SimpleWorkerErrorHandler {
SimpleWorkerErrorHandler {
addr: addr
}
}
}
impl Runnable for SimpleWorkerErrorHandler {
fn handler(self: Box<SimpleWorkerErrorHandler>) {
impl Runnable for SimpleWorkerErrorHandler<Worker> {
#[allow(unrooted_must_root)]
fn handler(self: Box<SimpleWorkerErrorHandler<Worker>>) {
let this = *self;
Worker::dispatch_simple_error(this.addr);
}
}
pub struct WorkerErrorHandler {
addr: TrustedWorkerAddress,
msg: DOMString,
file_name: DOMString,
line_num: u32,
col_num: u32,
}
impl WorkerErrorHandler {
pub fn new(addr: TrustedWorkerAddress, msg: DOMString, file_name: DOMString, line_num: u32, col_num: u32)
-> WorkerErrorHandler {
WorkerErrorHandler {
addr: addr,
msg: msg,
file_name: file_name,
line_num: line_num,
col_num: col_num,
}
}
}
impl Runnable for WorkerErrorHandler {
fn handler(self: Box<WorkerErrorHandler>) {
impl Runnable for WorkerErrorHandler<Worker> {
#[allow(unrooted_must_root)]
fn handler(self: Box<WorkerErrorHandler<Worker>>) {
let this = *self;
Worker::handle_error_message(this.addr, this.msg, this.file_name, this.line_num, this.col_num);
}
}
#[derive(Copy, Clone)]
pub struct SharedRt {
rt: *mut JSRuntime
}
impl SharedRt {
pub fn new(rt: &Runtime) -> SharedRt {
SharedRt {
rt: rt.rt()
}
}
#[allow(unsafe_code)]
pub fn request_interrupt(&self) {
unsafe {
JS_RequestInterruptCallback(self.rt);
}
}
pub fn rt(&self) -> *mut JSRuntime {
self.rt
}
}
#[allow(unsafe_code)]
unsafe impl Send for SharedRt {}

View file

@ -2,7 +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 devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId, DevtoolsPageInfo};
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception};
@ -15,6 +15,7 @@ use dom::console::Console;
use dom::crypto::Crypto;
use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use dom::eventtarget::EventTarget;
use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use dom::window::{base64_atob, base64_btoa};
use dom::workerlocation::WorkerLocation;
use dom::workernavigator::WorkerNavigator;
@ -57,6 +58,44 @@ pub struct WorkerGlobalScopeInit {
pub closing: Arc<AtomicBool>,
}
pub fn prepare_workerscope_init(global: GlobalRef,
worker_type: String,
worker_url: Url,
devtools_sender: IpcSender<DevtoolScriptControlMsg>,
closing: Arc<AtomicBool>) -> WorkerGlobalScopeInit {
let worker_id = global.get_next_worker_id();
let optional_sender = match global.devtools_chan() {
Some(ref chan) => {
let pipeline_id = global.pipeline();
let title = format!("{} for {}", worker_type, worker_url);
let page_info = DevtoolsPageInfo {
title: title,
url: worker_url,
};
chan.send(ScriptToDevtoolsControlMsg::NewGlobal((pipeline_id, Some(worker_id)),
devtools_sender.clone(),
page_info)).unwrap();
Some(devtools_sender)
},
None => None,
};
let init = WorkerGlobalScopeInit {
resource_threads: global.resource_threads(),
mem_profiler_chan: global.mem_profiler_chan().clone(),
to_devtools_sender: global.devtools_chan(),
time_profiler_chan: global.time_profiler_chan().clone(),
from_devtools_sender: optional_sender,
constellation_chan: global.constellation_chan().clone(),
panic_chan: global.panic_chan().clone(),
scheduler_chan: global.scheduler_chan().clone(),
worker_id: worker_id,
closing: closing,
};
init
}
// https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface
#[dom_struct]
pub struct WorkerGlobalScope {
@ -392,36 +431,49 @@ impl WorkerGlobalScope {
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
let dedicated =
self.downcast::<DedicatedWorkerGlobalScope>();
match dedicated {
Some(dedicated) => dedicated.script_chan(),
None => panic!("need to implement a sender for SharedWorker"),
let service_worker = self.downcast::<ServiceWorkerGlobalScope>();
if let Some(dedicated) = dedicated {
return dedicated.script_chan();
} else if let Some(service_worker) = service_worker {
return service_worker.script_chan();
} else {
panic!("need to implement a sender for SharedWorker")
}
}
pub fn pipeline(&self) -> PipelineId {
let dedicated =
self.downcast::<DedicatedWorkerGlobalScope>();
match dedicated {
Some(dedicated) => dedicated.pipeline(),
None => panic!("need to add a pipeline for SharedWorker"),
let dedicated = self.downcast::<DedicatedWorkerGlobalScope>();
let service_worker = self.downcast::<ServiceWorkerGlobalScope>();
if let Some(dedicated) = dedicated {
return dedicated.pipeline();
} else if let Some(service_worker) = service_worker {
return service_worker.pipeline();
} else {
panic!("need to implement a sender for SharedWorker")
}
}
pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
let dedicated =
self.downcast::<DedicatedWorkerGlobalScope>();
match dedicated {
Some(dedicated) => dedicated.new_script_pair(),
None => panic!("need to implement creating isolated event loops for SharedWorker"),
let dedicated = self.downcast::<DedicatedWorkerGlobalScope>();
let service_worker = self.downcast::<ServiceWorkerGlobalScope>();
if let Some(dedicated) = dedicated {
return dedicated.new_script_pair();
} else if let Some(service_worker) = service_worker {
return service_worker.new_script_pair();
} else {
panic!("need to implement a sender for SharedWorker")
}
}
pub fn process_event(&self, msg: CommonScriptMsg) {
let dedicated =
self.downcast::<DedicatedWorkerGlobalScope>();
match dedicated {
Some(dedicated) => dedicated.process_event(msg),
None => panic!("need to implement processing single events for SharedWorker"),
let dedicated = self.downcast::<DedicatedWorkerGlobalScope>();
let service_worker = self.downcast::<ServiceWorkerGlobalScope>();
if let Some(dedicated) = dedicated {
return dedicated.process_event(msg);
} else if let Some(service_worker) = service_worker {
return service_worker.process_event(msg);
} else {
panic!("need to implement a sender for SharedWorker")
}
}

View file

@ -69,6 +69,7 @@ pub enum ScriptThreadEventCategory {
UpdateReplacedElement,
WebSocketEvent,
WorkerEvent,
ServiceWorkerEvent
}
/// An interface for receiving ScriptMsg values in an event loop. Used for synchronous DOM

View file

@ -40,6 +40,8 @@ use dom::element::Element;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::htmlanchorelement::HTMLAnchorElement;
use dom::node::{Node, NodeDamage, window_from_node};
use dom::serviceworker::TrustedServiceWorkerAddress;
use dom::serviceworkerregistration::ServiceWorkerRegistration;
use dom::servohtmlparser::ParserContext;
use dom::uievent::UIEvent;
use dom::window::{ReflowReason, ScriptHelpers, Window};
@ -86,7 +88,7 @@ use script_traits::{ScriptThreadFactory, TimerEvent, TimerEventRequest, TimerSou
use script_traits::{TouchEventType, TouchId};
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::option::Option;
use std::rc::Rc;
use std::result::Result;
@ -264,6 +266,12 @@ impl ScriptPort for Receiver<(TrustedWorkerAddress, MainThreadScriptMsg)> {
}
}
impl ScriptPort for Receiver<(TrustedServiceWorkerAddress, CommonScriptMsg)> {
fn recv(&self) -> Result<CommonScriptMsg, ()> {
self.recv().map(|(_, msg)| msg).map_err(|_| ())
}
}
/// Encapsulates internal communication of shared messages within the script thread.
#[derive(JSTraceable)]
pub struct SendableMainThreadScriptChan(pub Sender<CommonScriptMsg>);
@ -308,6 +316,8 @@ pub struct ScriptThread {
browsing_context: MutNullableHeap<JS<BrowsingContext>>,
/// A list of data pertaining to loads that have not yet received a network response
incomplete_loads: DOMRefCell<Vec<InProgressLoad>>,
/// A map to store service worker registrations for a given origin
registration_map: DOMRefCell<HashMap<Url, JS<ServiceWorkerRegistration>>>,
/// A handle to the image cache thread.
image_cache_thread: ImageCacheThread,
/// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if
@ -486,6 +496,14 @@ impl ScriptThread {
})
}
// stores a service worker registration
pub fn set_registration(scope_url: Url, registration:&ServiceWorkerRegistration) {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.borrow().unwrap() };
script_thread.handle_serviceworker_registration(scope_url, registration);
});
}
pub fn parsing_complete(id: PipelineId) {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.borrow().unwrap() };
@ -548,6 +566,7 @@ impl ScriptThread {
ScriptThread {
browsing_context: MutNullableHeap::new(None),
incomplete_loads: DOMRefCell::new(vec!()),
registration_map: DOMRefCell::new(HashMap::new()),
image_cache_thread: state.image_cache_thread,
image_cache_channel: ImageCacheChan(ipc_image_cache_channel),
@ -865,6 +884,7 @@ impl ScriptThread {
ScriptThreadEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
ScriptThreadEventCategory::ServiceWorkerEvent => ProfilerCategory::ScriptServiceWorkerEvent
};
profile(profiler_cat, None, self.time_profiler_chan.clone(), f)
} else {
@ -1340,6 +1360,10 @@ impl ScriptThread {
}
}
fn handle_serviceworker_registration(&self, scope: Url, registration: &ServiceWorkerRegistration) {
self.registration_map.borrow_mut().insert(scope, JS::from_ref(registration));
}
/// Handles a request for the window title.
fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);

View file

@ -272,7 +272,7 @@ pub enum TimerSource {
/// The event was requested from a window (ScriptThread).
FromWindow(PipelineId),
/// The event was requested from a worker (DedicatedGlobalWorkerScope).
FromWorker
FromWorker,
}
/// The id to be used for a TimerEvent is defined by the corresponding TimerEventRequest.

View file

@ -65,6 +65,13 @@ impl PrefValue {
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match *self {
PrefValue::Number(x) => Some(x as u64),
_ => None,
}
}
}
impl ToJson for PrefValue {

View file

@ -522,6 +522,7 @@ def check_webidl_spec(file_name, contents):
"//w3c.github.io",
"//heycam.github.io/webidl",
"//webbluetoothcg.github.io/web-bluetooth/",
"//slightlyoff.github.io/ServiceWorker/spec/service_worker/",
# Not a URL
"// This interface is entirely internal to Servo, and should not be" +
" accessible to\n// web pages."

View file

@ -3,6 +3,7 @@
"dom.forcetouch.enabled": false,
"dom.mouseevent.which.enabled": false,
"dom.mozbrowser.enabled": false,
"dom.serviceworker.timeout_seconds": 60,
"dom.testbinding.enabled": false,
"gfx.webrender.enabled": false,
"js.baseline.enabled": true,

View file

@ -6898,6 +6898,12 @@
"url": "/_mozilla/mozilla/sequence-hole.html"
}
],
"mozilla/service-workers/service-worker-registration.html": [
{
"path": "mozilla/service-workers/service-worker-registration.html",
"url": "/_mozilla/mozilla/service-workers/service-worker-registration.html"
}
],
"mozilla/storage.html": [
{
"path": "mozilla/storage.html",

View file

@ -0,0 +1 @@
prefs: ["dom.serviceworker.enabled:true"]

View file

@ -0,0 +1 @@
console.log("Hey Servo; lets cache something! :)");

View file

@ -0,0 +1,52 @@
<!doctype html>
<meta charset="utf-8">
<title></title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
function register_sw(script_url, scope_str='') {
var registration = navigator.serviceWorker.register(script_url, {'scope': scope_str });
return registration;
}
test(function (){
assert_true('serviceWorker' in navigator);
}, "Test: Asserts ServiceWorkerContainer in Navigator");
test(function() {
var sw_reg = register_sw('sw.js');
assert_class_string(sw_reg, "ServiceWorkerRegistration");
assert_class_string(sw_reg.active, "ServiceWorker");
assert_class_string(navigator.serviceWorker.controller, "ServiceWorker");
}, "Test: Asserts Active Service Worker and its Registration");
test(function() {
var sw_reg = register_sw('resources/sw.js', './');
assert_equals(sw_reg.scope, location.href.replace("service-worker-registration.html", ""));
}, "Test: Service Worker Registration property scope Url when no scope");
test(function() {
var sw_reg = register_sw('resources/sw.js', '/some/nested/cache/directory');
var expected_scope_url = location.protocol + '//' + location.host + '/some/nested/cache/directory';
assert_equals(sw_reg.scope, expected_scope_url);
}, "Test: Service Worker Registration property scope when provided a scope");
test(function () {
assert_throws(new TypeError(),
function() { var sw_reg = register_sw('./in%5Csome%5fdir/sw.js'); },
"Invalid URL Path");
}, "Test: Throws Error when Invalid Url Path");
test(function () {
assert_throws(new TypeError(),
function() { var sw_reg = register_sw('sw.js', './mal%5fformed/sco%5Cpe/'); },
"Invalid URL Path");
}, "Test: Throws Error when Invalid Scope");
test(function() {
var sw_reg = register_sw('resources/sw.js');
assert_equals(sw_reg.active.scriptURL, location.href.replace("service-worker-registration.html", "resources/sw.js"));
}, "Test: Active Service Worker ScriptURL property");
</script>

View file

@ -0,0 +1 @@
console.log("Hey Servo; lets cache something! :)");