Skip to main content

script/dom/serviceworker/
serviceworkerglobalscope.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::sync::Arc;
8use std::sync::atomic::AtomicBool;
9use std::thread::{self, JoinHandle};
10use std::time::{Duration, Instant};
11
12use crossbeam_channel::{Receiver, Sender, after};
13#[cfg(feature = "devtools")]
14use devtools_traits::DevtoolScriptControlMsg;
15use dom_struct::dom_struct;
16use fonts::FontContext;
17use js::context::{JSContext, RawJSContext};
18use js::jsapi::JS_AddInterruptCallback;
19use js::jsval::UndefinedValue;
20use js::realm::CurrentRealm;
21use net_traits::CustomResponseMediator;
22use net_traits::blob_url_store::UrlWithBlobClaim;
23use net_traits::request::{
24    CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
25};
26use rand::random;
27use script_bindings::interfaces::HasOrigin;
28use servo_base::generic_channel::{GenericReceiver, GenericSend, GenericSender, RoutedReceiver};
29use servo_base::id::{PipelineId, ServiceWorkerId};
30use servo_config::pref;
31use servo_constellation_traits::{
32    ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin,
33};
34use servo_url::{MutableOrigin, ServoUrl};
35use style::thread_state::{self, ThreadState};
36
37use crate::dom::abstractworker::WorkerScriptMsg;
38use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
39use crate::dom::bindings::codegen::Bindings::ClientBinding::FrameType;
40use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
41use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
42use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
43use crate::dom::bindings::codegen::UnionTypes::ClientOrServiceWorkerOrMessagePort;
44use crate::dom::bindings::inheritance::Castable;
45use crate::dom::bindings::root::DomRoot;
46use crate::dom::bindings::str::DOMString;
47use crate::dom::bindings::structuredclone;
48use crate::dom::bindings::trace::CustomTraceable;
49use crate::dom::bindings::utils::define_all_exposed_interfaces;
50use crate::dom::client::Client;
51use crate::dom::csp::Violation;
52#[cfg(feature = "devtools")]
53use crate::dom::debugger::debuggerglobalscope::DebuggerGlobalScope;
54use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
55use crate::dom::event::Event;
56use crate::dom::eventtarget::EventTarget;
57use crate::dom::extendableevent::ExtendableEvent;
58use crate::dom::extendablemessageevent::ExtendableMessageEvent;
59use crate::dom::globalscope::GlobalScope;
60#[cfg(feature = "webgpu")]
61use crate::dom::webgpu::identityhub::IdentityHub;
62use crate::dom::worker::TrustedWorkerAddress;
63use crate::dom::workerglobalscope::{
64    WorkerDebuggerGlobalScope, WorkerDevtoolsControlMsg, WorkerGlobalScope,
65};
66use crate::fetch::fetch::{CspViolationsProcessor, load_whole_resource};
67use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
68use crate::realms::enter_auto_realm;
69use crate::runtime::script_runtime::{IntroductionType, Runtime, ThreadSafeJSContext};
70use crate::scripting::fetching_options::ScriptFetchOptions;
71use crate::scripting::script_execution::{RethrowErrors, ScriptOptions};
72use crate::tasks::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
73
74/// Messages used to control service worker event loop
75#[derive(MallocSizeOf)]
76pub(crate) enum ServiceWorkerScriptMsg {
77    /// Message common to all workers
78    CommonWorker(WorkerScriptMsg),
79    /// Message to request a custom response by the service worker
80    Response(CustomResponseMediator),
81    /// Wake-up call from the task queue.
82    WakeUp,
83    /// Message to set online state on service worker
84    SetNetworkOnlineState(bool),
85}
86
87impl QueuedTaskConversion for ServiceWorkerScriptMsg {
88    fn pipeline_id(&self) -> Option<PipelineId> {
89        // Workers always return None, since the pipeline_id is only used to check for document activity,
90        // and this check does not apply to worker event-loops.
91        None
92    }
93
94    fn into_queued_task(self) -> Option<QueuedTask> {
95        let script_msg = match self {
96            ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
97            _ => return None,
98        };
99        let (event_category, task, pipeline_id, task_source) = match script_msg {
100            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
101                (category, boxed, pipeline_id, task_source)
102            },
103            _ => return None,
104        };
105        Some(QueuedTask {
106            worker: None,
107            event_category,
108            task,
109            pipeline_id,
110            task_source,
111        })
112    }
113
114    fn from_queued_task(queued_task: QueuedTask) -> Self {
115        let script_msg = CommonScriptMsg::Task(
116            queued_task.event_category,
117            queued_task.task,
118            queued_task.pipeline_id,
119            queued_task.task_source,
120        );
121        ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
122    }
123
124    fn inactive_msg() -> Self {
125        // Inactive is only relevant in the context of a browsing-context event-loop.
126        panic!("Workers should never receive messages marked as inactive");
127    }
128
129    fn wake_up_msg() -> Self {
130        ServiceWorkerScriptMsg::WakeUp
131    }
132
133    fn is_wake_up(&self) -> bool {
134        matches!(self, ServiceWorkerScriptMsg::WakeUp)
135    }
136}
137
138/// Messages sent from the owning registration.
139pub(crate) enum ServiceWorkerControlMsg {
140    /// Shutdown.
141    Exit,
142}
143
144pub(crate) enum MixedMessage {
145    ServiceWorker(ServiceWorkerScriptMsg),
146    #[cfg(feature = "devtools")]
147    Devtools(DevtoolScriptControlMsg),
148    Control(ServiceWorkerControlMsg),
149    Timer,
150}
151
152struct ServiceWorkerCspProcessor {}
153
154impl CspViolationsProcessor for ServiceWorkerCspProcessor {
155    fn process_csp_violations(&self, _cx: &mut JSContext, _violations: Vec<Violation>) {}
156}
157
158#[dom_struct]
159pub(crate) struct ServiceWorkerGlobalScope {
160    workerglobalscope: WorkerGlobalScope,
161
162    #[no_trace]
163    task_queue: TaskQueue<ServiceWorkerScriptMsg>,
164
165    own_sender: Sender<ServiceWorkerScriptMsg>,
166
167    /// A port on which a single "time-out" message can be received,
168    /// indicating the sw should stop running,
169    /// while still draining the task-queue
170    // and running all enqueued, and not cancelled, tasks.
171    #[no_trace]
172    time_out_port: Receiver<Instant>,
173
174    #[no_trace]
175    swmanager_sender: GenericSender<ServiceWorkerMsg>,
176
177    #[no_trace]
178    scope_url: ServoUrl,
179
180    /// A receiver of control messages,
181    /// currently only used to signal shutdown.
182    #[no_trace]
183    control_receiver: Receiver<ServiceWorkerControlMsg>,
184
185    #[no_trace]
186    worker_id: ServiceWorkerId,
187}
188
189impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
190    type WorkerMsg = ServiceWorkerScriptMsg;
191    type ControlMsg = ServiceWorkerControlMsg;
192    type Event = MixedMessage;
193
194    fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
195        &self.task_queue
196    }
197
198    fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
199        self.handle_mixed_message(event, cx)
200    }
201
202    fn handle_worker_post_event(
203        &self,
204        _worker: &TrustedWorkerAddress,
205    ) -> Option<AutoWorkerReset<'_>> {
206        None
207    }
208
209    fn from_control_msg(msg: ServiceWorkerControlMsg) -> MixedMessage {
210        MixedMessage::Control(msg)
211    }
212
213    fn from_worker_msg(msg: ServiceWorkerScriptMsg) -> MixedMessage {
214        MixedMessage::ServiceWorker(msg)
215    }
216
217    #[cfg(feature = "devtools")]
218    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
219        MixedMessage::Devtools(msg)
220    }
221
222    fn from_timer_msg() -> MixedMessage {
223        MixedMessage::Timer
224    }
225
226    fn control_receiver(&self) -> &Receiver<ServiceWorkerControlMsg> {
227        &self.control_receiver
228    }
229}
230
231impl ServiceWorkerGlobalScope {
232    #[allow(clippy::too_many_arguments)]
233    fn new_inherited(
234        init: WorkerGlobalScopeInit,
235        worker_url: ServoUrl,
236        from_devtools_receiver: Option<RoutedReceiver<WorkerDevtoolsControlMsg>>,
237        runtime: Runtime,
238        own_sender: Sender<ServiceWorkerScriptMsg>,
239        receiver: Receiver<ServiceWorkerScriptMsg>,
240        time_out_port: Receiver<Instant>,
241        swmanager_sender: GenericSender<ServiceWorkerMsg>,
242        scope_url: ServoUrl,
243        control_receiver: Receiver<ServiceWorkerControlMsg>,
244        closing: Arc<AtomicBool>,
245        font_context: Arc<FontContext>,
246        worker_id: ServiceWorkerId,
247    ) -> ServiceWorkerGlobalScope {
248        ServiceWorkerGlobalScope {
249            workerglobalscope: WorkerGlobalScope::new_inherited(
250                init,
251                DOMString::new(),
252                WorkerType::Classic, // FIXME(cybai): Should be provided from `Run Service Worker`
253                worker_url,
254                runtime,
255                from_devtools_receiver,
256                closing,
257                #[cfg(feature = "webgpu")]
258                Arc::new(IdentityHub::default()),
259                // FIXME: investigate what environment this value comes from for service workers.
260                InsecureRequestsPolicy::DoNotUpgrade,
261                font_context,
262                Some(ScriptEventLoopSender::ServiceWorker(own_sender.clone())),
263            ),
264            task_queue: TaskQueue::new(receiver),
265            own_sender,
266            time_out_port,
267            swmanager_sender,
268            scope_url,
269            control_receiver,
270            worker_id,
271        }
272    }
273
274    #[allow(clippy::too_many_arguments)]
275    pub(crate) fn new(
276        init: WorkerGlobalScopeInit,
277        worker_url: ServoUrl,
278        from_devtools_receiver: Option<RoutedReceiver<WorkerDevtoolsControlMsg>>,
279        runtime: Runtime,
280        own_sender: Sender<ServiceWorkerScriptMsg>,
281        receiver: Receiver<ServiceWorkerScriptMsg>,
282        time_out_port: Receiver<Instant>,
283        swmanager_sender: GenericSender<ServiceWorkerMsg>,
284        scope_url: ServoUrl,
285        control_receiver: Receiver<ServiceWorkerControlMsg>,
286        closing: Arc<AtomicBool>,
287        font_context: Arc<FontContext>,
288        debugger_global: Option<&WorkerDebuggerGlobalScope>,
289        worker_id: ServiceWorkerId,
290        cx: &mut JSContext,
291    ) -> DomRoot<ServiceWorkerGlobalScope> {
292        let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
293            init,
294            worker_url,
295            from_devtools_receiver,
296            runtime,
297            own_sender,
298            receiver,
299            time_out_port,
300            swmanager_sender,
301            scope_url,
302            control_receiver,
303            closing,
304            font_context,
305            worker_id,
306        ));
307        let scope = ServiceWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
308            cx,
309            &scope.origin(),
310            scope,
311        );
312        #[cfg(feature = "devtools")]
313        scope
314            .upcast::<WorkerGlobalScope>()
315            .init_debugger_global(debugger_global, cx);
316
317        scope
318    }
319
320    /// <https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm>
321    #[expect(unsafe_code)]
322    #[allow(clippy::too_many_arguments)]
323    pub(crate) fn run_serviceworker_scope(
324        scope_things: ScopeThings,
325        own_sender: Sender<ServiceWorkerScriptMsg>,
326        receiver: Receiver<ServiceWorkerScriptMsg>,
327        devtools_receiver: Option<GenericReceiver<WorkerDevtoolsControlMsg>>,
328        swmanager_sender: GenericSender<ServiceWorkerMsg>,
329        scope_url: ServoUrl,
330        control_receiver: Receiver<ServiceWorkerControlMsg>,
331        context_sender: Sender<ThreadSafeJSContext>,
332        closing: Arc<AtomicBool>,
333        font_context: Arc<FontContext>,
334        worker_id: ServiceWorkerId,
335    ) -> JoinHandle<()> {
336        let ScopeThings {
337            script_url,
338            init,
339            worker_load_origin,
340            ..
341        } = scope_things;
342
343        let serialized_worker_url = script_url.to_string();
344        let origin = scope_url.origin();
345        thread::Builder::new()
346            .name(format!("SW:{}", script_url.debug_compact()))
347            .spawn(move || {
348                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
349                let runtime = Runtime::new(None);
350                // SAFETY: We are in a new thread, so this first cx.
351                // It is OK to have it separated of runtime here,
352                // because it will never outlive it (runtime destruction happens at the end of this function
353                let mut cx = unsafe { runtime.cx() };
354                let cx = &mut cx;
355                let context_for_interrupt = runtime.thread_safe_js_context();
356                let _ = context_sender.send(context_for_interrupt);
357
358                let WorkerScriptLoadOrigin {
359                    referrer_url,
360                    referrer_policy,
361                    pipeline_id,
362                } = worker_load_origin;
363
364                #[cfg(feature = "devtools")]
365                let debugger_global = DebuggerGlobalScope::new(
366                    pipeline_id,
367                    init.to_devtools_sender.clone(),
368                    init.from_devtools_sender
369                        .clone()
370                        .expect("Guaranteed by update_serviceworker"),
371                    init.mem_profiler_chan.clone(),
372                    init.time_profiler_chan.clone(),
373                    init.script_to_constellation_chan.clone(),
374                    init.script_to_embedder_chan.clone(),
375                    init.resource_threads.clone(),
376                    init.storage_threads.clone(),
377                    #[cfg(feature = "webgpu")]
378                    Arc::new(IdentityHub::default()),
379                    cx,
380                );
381                #[cfg(feature = "devtools")]
382                debugger_global.execute(cx);
383
384                // Service workers are time limited
385                // https://w3c.github.io/ServiceWorker/#service-worker-lifetime
386                let sw_lifetime_timeout = pref!(dom_serviceworker_timeout_seconds) as u64;
387                let time_out_port = after(Duration::new(sw_lifetime_timeout, 0));
388
389                #[cfg(feature = "devtools")]
390                let devtools_mpsc_port =
391                    devtools_receiver.map(|receiver| receiver.route_preserving_errors());
392                #[cfg(not(feature = "devtools"))]
393                let devtools_mpsc_port = None;
394                #[cfg(feature = "devtools")]
395                let debugger_global_for_scope = Some(&*debugger_global);
396                #[cfg(not(feature = "devtools"))]
397                let debugger_global_for_scope: Option<&WorkerDebuggerGlobalScope> = None;
398
399                let resource_threads_sender = init.resource_threads.sender();
400                #[cfg(feature = "devtools")]
401                let devtools_enabled = init.to_devtools_sender.is_some();
402                let service_worker_global_scope = ServiceWorkerGlobalScope::new(
403                    init,
404                    script_url.clone(),
405                    devtools_mpsc_port,
406                    runtime,
407                    own_sender,
408                    receiver,
409                    time_out_port,
410                    swmanager_sender,
411                    scope_url,
412                    control_receiver,
413                    closing,
414                    font_context,
415                    debugger_global_for_scope,
416                    worker_id,
417                    cx,
418                );
419
420                let worker_global_scope = service_worker_global_scope.upcast::<WorkerGlobalScope>();
421                let global_scope = service_worker_global_scope.upcast::<GlobalScope>();
422
423                #[cfg(feature = "devtools")]
424                if devtools_enabled {
425                    debugger_global.fire_add_debuggee(
426                        cx,
427                        global_scope,
428                        pipeline_id,
429                        Some(worker_global_scope.worker_id()),
430                    );
431                }
432
433                let referrer = referrer_url
434                    .map(Referrer::ReferrerUrl)
435                    .unwrap_or_else(|| global_scope.get_referrer());
436
437                let request = RequestBuilder::new(
438                    None,
439                    UrlWithBlobClaim::from_url_without_having_claimed_blob(script_url),
440                    referrer,
441                )
442                .destination(Destination::ServiceWorker)
443                .credentials_mode(CredentialsMode::Include)
444                .parser_metadata(ParserMetadata::NotParserInserted)
445                .use_url_credentials(true)
446                .pipeline_id(Some(pipeline_id))
447                .referrer_policy(referrer_policy)
448                // TODO: Use policy container from ScopeThings
449                .policy_container(global_scope.policy_container())
450                .origin(origin);
451
452                let (url, source) = match load_whole_resource(
453                    request,
454                    &resource_threads_sender,
455                    service_worker_global_scope.upcast(),
456                    &ServiceWorkerCspProcessor {},
457                    cx,
458                ) {
459                    Err(_) => {
460                        error!("error loading script {}", serialized_worker_url);
461                        worker_global_scope.clear_js_runtime();
462                        return;
463                    },
464                    Ok((metadata, bytes, _)) => (metadata.final_url, bytes),
465                };
466
467                unsafe {
468                    // Handle interrupt requests
469                    JS_AddInterruptCallback(cx.raw_cx(), Some(interrupt_callback));
470                }
471
472                {
473                    // TODO: use AutoWorkerReset as in dedicated worker?
474                    let mut realm = enter_auto_realm(cx, worker_global_scope);
475                    let mut realm = realm.current_realm();
476                    define_all_exposed_interfaces(&mut realm, global_scope);
477
478                    let script = global_scope.create_a_classic_script(
479                        &mut realm,
480                        String::from_utf8_lossy(&source),
481                        url,
482                        ScriptOptions::External,
483                        ScriptFetchOptions::default_classic_script(),
484                        Some(IntroductionType::WORKER),
485                        1,
486                    );
487                    _ = global_scope.run_a_classic_script(
488                        &mut realm,
489                        script,
490                        RethrowErrors::No,
491                        None, // return_value
492                    );
493                    service_worker_global_scope.dispatch_activate(&mut realm);
494                }
495
496                let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
497                global_scope.mem_profiler_chan().run_with_memory_reporting(
498                    || {
499                        // Step 18, Run the responsible event loop specified
500                        // by inside settings until it is destroyed.
501                        // The worker processing model remains on this step
502                        // until the event loop is destroyed,
503                        // which happens after the closing flag is set to true,
504                        // or until the worker has run beyond its allocated time.
505                        while !worker_global_scope.is_closing() &&
506                            !service_worker_global_scope.has_timed_out()
507                        {
508                            run_worker_event_loop(&*service_worker_global_scope, None, cx);
509                        }
510                    },
511                    reporter_name,
512                    service_worker_global_scope.event_loop_sender(),
513                    CommonScriptMsg::CollectReports,
514                );
515
516                global_scope.tear_down();
517                worker_global_scope.clear_js_runtime();
518            })
519            .expect("Thread spawning failed")
520    }
521
522    fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
523        match msg {
524            #[cfg(feature = "devtools")]
525            MixedMessage::Devtools(msg) => self
526                .upcast::<WorkerGlobalScope>()
527                .handle_devtools_message(msg, cx),
528            MixedMessage::ServiceWorker(msg) => {
529                self.handle_script_event(msg, cx);
530            },
531            MixedMessage::Control(ServiceWorkerControlMsg::Exit) => {
532                return false;
533            },
534            MixedMessage::Timer => {},
535        }
536        true
537    }
538
539    fn has_timed_out(&self) -> bool {
540        // TODO: https://w3c.github.io/ServiceWorker/#service-worker-lifetime
541        false
542    }
543
544    fn handle_script_event(&self, msg: ServiceWorkerScriptMsg, cx: &mut JSContext) {
545        use self::ServiceWorkerScriptMsg::*;
546
547        match msg {
548            CommonWorker(WorkerScriptMsg::DOMMessage(msg)) => {
549                let scope = self.upcast::<WorkerGlobalScope>();
550                let target = self.upcast();
551
552                let mut realm = enter_auto_realm(cx, scope);
553                let cx = &mut realm.current_realm();
554
555                rooted!(&in(cx) let mut message = UndefinedValue());
556                let client = Client::new(
557                    cx,
558                    scope.upcast(),
559                    self.swmanager_sender.clone(),
560                    self.scope_url.clone(),
561                    FrameType::None,
562                    self.worker_id,
563                );
564                if let Ok(ports) =
565                    structuredclone::read(cx, scope.upcast(), *msg.data, message.handle_mut())
566                {
567                    ExtendableMessageEvent::dispatch_jsval(
568                        cx,
569                        target,
570                        scope.upcast(),
571                        message.handle(),
572                        Some(&ClientOrServiceWorkerOrMessagePort::Client(client)),
573                        ports,
574                    );
575                } else {
576                    ExtendableMessageEvent::dispatch_error(cx, target, scope.upcast());
577                }
578            },
579            CommonWorker(WorkerScriptMsg::Common(msg)) => {
580                self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
581            },
582            Response(mediator) => {
583                // TODO XXXcreativcoder This will eventually use a FetchEvent interface to fire event
584                // when we have the Request and Response dom api's implemented
585                // https://w3c.github.io/ServiceWorker/#fetchevent-interface
586                self.upcast::<EventTarget>().fire_event(cx, atom!("fetch"));
587                let _ = mediator.response_chan.send(None);
588            },
589            WakeUp => {},
590            SetNetworkOnlineState(online) => {
591                self.upcast::<WorkerGlobalScope>()
592                    .set_network_online_state(online);
593            },
594        }
595    }
596
597    pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
598        ScriptEventLoopSender::ServiceWorker(self.own_sender.clone())
599    }
600
601    fn dispatch_activate(&self, cx: &mut CurrentRealm) {
602        let event = ExtendableEvent::new(cx, self, atom!("activate"), false, false);
603        let event = (*event).upcast::<Event>();
604        event.dispatch(cx, self.upcast(), false);
605    }
606}
607
608#[expect(unsafe_code)]
609unsafe extern "C" fn interrupt_callback(cx: *mut RawJSContext) -> bool {
610    // SAFETY: it is safe to construct a JSContext from engine hook.
611    let mut cx = unsafe { JSContext::from_ptr(std::ptr::NonNull::new(cx).unwrap()) };
612    let mut realm = CurrentRealm::assert(&mut cx);
613
614    let global = GlobalScope::from_current_realm(&mut realm);
615    let worker =
616        DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope");
617    assert!(worker.is::<ServiceWorkerGlobalScope>());
618
619    // A false response causes the script to terminate
620    !worker.is_closing()
621}
622
623impl ServiceWorkerGlobalScopeMethods<crate::DomTypeHolder> for ServiceWorkerGlobalScope {
624    // https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-onmessage
625    event_handler!(message, GetOnmessage, SetOnmessage);
626
627    // https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-onmessageerror
628    event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
629}
630
631impl HasOrigin for ServiceWorkerGlobalScope {
632    fn origin(&self) -> MutableOrigin {
633        self.upcast::<WorkerGlobalScope>().origin()
634    }
635}