Skip to main content

script/dom/
eventsource.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
5use std::cell::Cell;
6use std::mem;
7use std::rc::Rc;
8use std::str::{Chars, FromStr};
9use std::time::Duration;
10
11use bytes::Bytes;
12use dom_struct::dom_struct;
13use encoding_rs::{Decoder, UTF_8};
14use headers::ContentType;
15use http::StatusCode;
16use http::header::{self, HeaderName, HeaderValue};
17use js::context::JSContext;
18use js::conversions::ToJSValConvertible;
19use js::jsval::UndefinedValue;
20use js::rust::HandleObject;
21use mime::{self, Mime};
22use net_traits::fetch::fetch_controller::FetchController;
23use net_traits::request::{CacheMode, CorsSettings, Destination, RequestBuilder, RequestId};
24use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
25use script_bindings::cell::DomRefCell;
26use script_bindings::reflector::reflect_weak_referenceable_dom_object_with_proto;
27use servo_url::ServoUrl;
28use stylo_atoms::Atom;
29
30use crate::dom::bindings::codegen::Bindings::EventSourceBinding::{
31    EventSourceInit, EventSourceMethods,
32};
33use crate::dom::bindings::error::{Error, Fallible};
34use crate::dom::bindings::inheritance::Castable;
35use crate::dom::bindings::refcounted::Trusted;
36use crate::dom::bindings::reflector::DomGlobal;
37use crate::dom::bindings::root::DomRoot;
38use crate::dom::bindings::str::DOMString;
39use crate::dom::csp::{GlobalCspReporting, Violation};
40use crate::dom::event::Event;
41use crate::dom::eventtarget::EventTarget;
42use crate::dom::globalscope::GlobalScope;
43use crate::dom::messageevent::MessageEvent;
44use crate::dom::performance::performanceresourcetiming::InitiatorType;
45use crate::event_loop::timers::OneshotTimerCallback;
46use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
47use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
48use crate::realms::enter_auto_realm;
49
50const DEFAULT_RECONNECTION_TIME: Duration = Duration::from_millis(5000);
51
52#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
53struct GenerationId(u32);
54
55#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
56/// <https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate>
57enum ReadyState {
58    Connecting = 0,
59    Open = 1,
60    Closed = 2,
61}
62
63#[derive(JSTraceable, MallocSizeOf)]
64struct DroppableEventSource {
65    #[no_trace]
66    controller: DomRefCell<FetchController>,
67}
68
69impl DroppableEventSource {
70    pub(crate) fn new(controller: DomRefCell<FetchController>) -> Self {
71        DroppableEventSource { controller }
72    }
73
74    pub(crate) fn cancel(&self) {
75        self.controller.borrow_mut().abort();
76    }
77
78    pub(crate) fn set_controller(&self, controller: FetchController) {
79        *self.controller.borrow_mut() = controller;
80    }
81}
82
83// https://html.spec.whatwg.org/multipage/#garbage-collection-2
84impl Drop for DroppableEventSource {
85    fn drop(&mut self) {
86        // If an EventSource object is garbage collected while its connection is still open,
87        // the user agent must abort any instance of the fetch algorithm opened by this EventSource.
88        self.cancel();
89    }
90}
91
92#[dom_struct]
93pub(crate) struct EventSource {
94    eventtarget: EventTarget,
95    #[no_trace]
96    url: ServoUrl,
97    #[no_trace]
98    request: DomRefCell<Option<RequestBuilder>>,
99    last_event_id: DomRefCell<DOMString>,
100    reconnection_time: Cell<Duration>,
101    generation_id: Cell<GenerationId>,
102
103    ready_state: Cell<ReadyState>,
104    with_credentials: bool,
105    droppable: DroppableEventSource,
106}
107
108#[derive(Clone, MallocSizeOf)]
109enum ParserState {
110    Field,
111    Comment,
112    Value,
113    Eol,
114}
115
116#[derive(MallocSizeOf)]
117struct EventSourceContext {
118    decoder: Decoder,
119    event_source: Trusted<EventSource>,
120    gen_id: GenerationId,
121    parser_state: ParserState,
122    field: String,
123    value: String,
124    origin: String,
125    event_type: String,
126    data: String,
127    last_event_id: String,
128}
129
130impl Clone for EventSourceContext {
131    fn clone(&self) -> Self {
132        EventSourceContext {
133            decoder: UTF_8.new_decoder_with_bom_removal(),
134            event_source: self.event_source.clone(),
135            gen_id: self.gen_id,
136            parser_state: self.parser_state.clone(),
137            field: self.field.clone(),
138            value: self.value.clone(),
139            origin: self.origin.clone(),
140            event_type: self.event_type.clone(),
141            data: self.data.clone(),
142            last_event_id: self.last_event_id.clone(),
143        }
144    }
145}
146
147impl EventSourceContext {
148    /// <https://html.spec.whatwg.org/multipage/#announce-the-connection>
149    fn announce_the_connection(&self) {
150        let event_source = self.event_source.root();
151        if self.gen_id != event_source.generation_id.get() {
152            return;
153        }
154        let global = event_source.global();
155        let event_source = self.event_source.clone();
156        global.task_manager().remote_event_task_source().queue(
157            task!(announce_the_event_source_connection: move |cx| {
158                let event_source = event_source.root();
159                if event_source.ready_state.get() != ReadyState::Closed {
160                    event_source.ready_state.set(ReadyState::Open);
161                    event_source.upcast::<EventTarget>().fire_event(cx, atom!("open"));
162                }
163            }),
164        );
165    }
166
167    /// <https://html.spec.whatwg.org/multipage/#fail-the-connection>
168    fn fail_the_connection(&self) {
169        let event_source = self.event_source.root();
170        if self.gen_id != event_source.generation_id.get() {
171            return;
172        }
173        event_source.fail_the_connection();
174    }
175
176    /// <https://html.spec.whatwg.org/multipage/#reestablish-the-connection>
177    fn reestablish_the_connection(&self) {
178        let event_source = self.event_source.root();
179
180        if self.gen_id != event_source.generation_id.get() {
181            return;
182        }
183
184        let trusted_event_source = self.event_source.clone();
185        let global = event_source.global();
186        let event_source_context = EventSourceContext {
187            decoder: UTF_8.new_decoder_with_bom_removal(),
188            event_source: self.event_source.clone(),
189            gen_id: self.gen_id,
190            parser_state: ParserState::Eol,
191            field: String::new(),
192            value: String::new(),
193            origin: self.origin.clone(),
194            event_type: String::new(),
195            data: String::new(),
196            last_event_id: String::from(event_source.last_event_id.borrow().clone()),
197        };
198        global.task_manager().remote_event_task_source().queue(
199            task!(reestablish_the_event_source_onnection: move |cx| {
200                let event_source = trusted_event_source.root();
201
202                // Step 1.1.
203                if event_source.ready_state.get() == ReadyState::Closed {
204                    return;
205                }
206
207                // Step 1.2.
208                event_source.ready_state.set(ReadyState::Connecting);
209
210                // Step 1.3.
211                event_source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
212
213                // Step 2.
214                let duration = event_source.reconnection_time.get();
215
216                // Step 3.
217                // TODO: Optionally wait some more.
218
219                // Steps 4-5.
220                let callback = OneshotTimerCallback::EventSourceTimeout(
221                    EventSourceTimeoutCallback {
222                        event_source: trusted_event_source,
223                        event_source_context,
224                    }
225                );
226                event_source.global().schedule_callback(callback, duration);
227            }),
228        );
229    }
230
231    /// <https://html.spec.whatwg.org/multipage/#processField>
232    fn process_field(&mut self) {
233        match &*self.field {
234            "event" => mem::swap(&mut self.event_type, &mut self.value),
235            "data" => {
236                self.data.push_str(&self.value);
237                self.data.push('\n');
238            },
239            "id" if !self.value.contains('\0') => {
240                mem::swap(&mut self.last_event_id, &mut self.value);
241            },
242            "retry" => {
243                if let Ok(time) = u64::from_str(&self.value) {
244                    self.event_source
245                        .root()
246                        .reconnection_time
247                        .set(Duration::from_millis(time));
248                }
249            },
250            _ => (),
251        }
252
253        self.field.clear();
254        self.value.clear();
255    }
256
257    /// <https://html.spec.whatwg.org/multipage/#dispatchMessage>
258    fn dispatch_event(&mut self, cx: &mut JSContext) {
259        let event_source = self.event_source.root();
260        // Step 1
261        *event_source.last_event_id.safe_borrow_mut(cx.no_gc()) =
262            DOMString::from(self.last_event_id.clone());
263        // Step 2
264        if self.data.is_empty() {
265            self.data.clear();
266            self.event_type.clear();
267            return;
268        }
269        // Step 3
270        if let Some(last) = self.data.pop() &&
271            last != '\n'
272        {
273            self.data.push(last);
274        }
275        // Step 6
276        let type_ = if !self.event_type.is_empty() {
277            Atom::from(self.event_type.clone())
278        } else {
279            atom!("message")
280        };
281        // Steps 4-5
282        let event = {
283            let mut realm = enter_auto_realm(cx, &*event_source);
284            let cx = &mut realm.current_realm();
285            rooted!(&in(cx) let mut data = UndefinedValue());
286            self.data.to_jsval(cx, data.handle_mut());
287            MessageEvent::new(
288                cx,
289                &event_source.global(),
290                type_,
291                false,
292                false,
293                data.handle(),
294                DOMString::from(self.origin.clone()),
295                None,
296                event_source.last_event_id.borrow().clone(),
297                Vec::with_capacity(0),
298            )
299        };
300        // Step 7
301        self.event_type.clear();
302        self.data.clear();
303
304        // Step 8.
305        let global = event_source.global();
306        let event_source = self.event_source.clone();
307        let event = Trusted::new(&*event);
308        global.task_manager().remote_event_task_source().queue(
309            task!(dispatch_the_event_source_event: move |cx| {
310                let event_source = event_source.root();
311                if event_source.ready_state.get() != ReadyState::Closed {
312                    event.root().upcast::<Event>().fire(cx, event_source.upcast());
313                }
314            }),
315        );
316    }
317
318    /// <https://html.spec.whatwg.org/multipage/#event-stream-interpretation>
319    fn parse(&mut self, cx: &mut JSContext, stream: Chars) {
320        let mut stream = stream.peekable();
321
322        while let Some(ch) = stream.next() {
323            match (ch, &self.parser_state) {
324                (':', &ParserState::Eol) => self.parser_state = ParserState::Comment,
325                (':', &ParserState::Field) => {
326                    self.parser_state = ParserState::Value;
327                    if let Some(&' ') = stream.peek() {
328                        stream.next();
329                    }
330                },
331
332                ('\n', &ParserState::Value) => {
333                    self.parser_state = ParserState::Eol;
334                    self.process_field();
335                },
336                ('\r', &ParserState::Value) => {
337                    if let Some(&'\n') = stream.peek() {
338                        continue;
339                    }
340                    self.parser_state = ParserState::Eol;
341                    self.process_field();
342                },
343
344                ('\n', &ParserState::Field) => {
345                    self.parser_state = ParserState::Eol;
346                    self.process_field();
347                },
348                ('\r', &ParserState::Field) => {
349                    if let Some(&'\n') = stream.peek() {
350                        continue;
351                    }
352                    self.parser_state = ParserState::Eol;
353                    self.process_field();
354                },
355
356                ('\n', &ParserState::Eol) => self.dispatch_event(cx),
357                ('\r', &ParserState::Eol) => {
358                    if let Some(&'\n') = stream.peek() {
359                        continue;
360                    }
361                    self.dispatch_event(cx);
362                },
363
364                ('\n', &ParserState::Comment) => self.parser_state = ParserState::Eol,
365                ('\r', &ParserState::Comment) => {
366                    if let Some(&'\n') = stream.peek() {
367                        continue;
368                    }
369                    self.parser_state = ParserState::Eol;
370                },
371
372                (_, &ParserState::Field) => self.field.push(ch),
373                (_, &ParserState::Value) => self.value.push(ch),
374                (_, &ParserState::Eol) => {
375                    self.parser_state = ParserState::Field;
376                    self.field.push(ch);
377                },
378                (_, &ParserState::Comment) => (),
379            }
380        }
381    }
382}
383
384impl FetchResponseListener for EventSourceContext {
385    fn should_invoke(&self) -> bool {
386        self.event_source.root().generation_id.get() == self.gen_id
387    }
388
389    fn process_request_body(&mut self, _: RequestId) {
390        // TODO
391    }
392
393    fn process_response(
394        &mut self,
395        _: &mut JSContext,
396        _: RequestId,
397        metadata: Result<FetchMetadata, NetworkError>,
398    ) {
399        match metadata {
400            Ok(fetch_metadata) => {
401                if fetch_metadata.is_cors_cross_origin() {
402                    return self.fail_the_connection();
403                }
404                let metadata: Metadata = fetch_metadata.into();
405                // Step 15.3 if res's status is not 200, or if res's `Content-Type` is not
406                // `text/event-stream`, then fail the connection.
407                if metadata.status.code() != StatusCode::OK {
408                    return self.fail_the_connection();
409                }
410                let mime = match metadata.content_type {
411                    None => return self.fail_the_connection(),
412                    Some(ct) => <ContentType as Into<Mime>>::into(ct.into_inner()),
413                };
414                if (mime.type_(), mime.subtype()) != (mime::TEXT, mime::EVENT_STREAM) {
415                    return self.fail_the_connection();
416                }
417                self.origin = metadata
418                    .final_url
419                    .origin()
420                    .ascii_serialization()
421                    .into_owned();
422                // Step 15.4 announce the connection and interpret res's body line by line.
423                self.announce_the_connection();
424            },
425            Err(error) => {
426                // Step 15.2 if res is a network error, then reestablish the connection, unless
427                // the user agent knows that to be futile, in which case the user agent may
428                // fail the connection.
429                if error.is_permanent_failure() {
430                    self.fail_the_connection()
431                } else {
432                    self.reestablish_the_connection()
433                }
434            },
435        }
436    }
437
438    fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Bytes) {
439        let mut output = String::with_capacity(chunk.len());
440        let mut input = &chunk[..];
441
442        loop {
443            if input.is_empty() {
444                return;
445            }
446            let (result, bytes_read) =
447                self.decoder
448                    .decode_to_string_without_replacement(input, &mut output, false);
449            match result {
450                encoding_rs::DecoderResult::InputEmpty => {
451                    self.parse(cx, output.chars());
452                    return;
453                },
454                encoding_rs::DecoderResult::Malformed(_, _) => {
455                    self.parse(cx, output.chars());
456                    self.parse(cx, "\u{FFFD}".chars());
457                    output.clear();
458                    input = &input[bytes_read..];
459                },
460                encoding_rs::DecoderResult::OutputFull => {
461                    self.parse(cx, output.chars());
462                    output.clear();
463                    input = &input[bytes_read..];
464                },
465            }
466        }
467    }
468
469    fn process_response_eof(
470        mut self,
471        cx: &mut JSContext,
472        _: RequestId,
473        response: Result<(), NetworkError>,
474        timing: ResourceFetchTiming,
475    ) {
476        let mut output = String::new();
477        let (result, _) = self
478            .decoder
479            .decode_to_string_without_replacement(&[], &mut output, true);
480        if !output.is_empty() {
481            self.parse(cx, "\u{FFFD}".chars());
482        }
483        if matches!(result, encoding_rs::DecoderResult::Malformed(_, _)) {
484            self.parse(cx, "\u{FFFD}".chars());
485        }
486        if response.is_ok() {
487            self.reestablish_the_connection();
488        }
489
490        network_listener::submit_timing(cx, &self, &response, &timing);
491    }
492
493    fn process_csp_violations(
494        &mut self,
495        cx: &mut JSContext,
496        _request_id: RequestId,
497        violations: Vec<Violation>,
498    ) {
499        let global = &self.resource_timing_global();
500        global.report_csp_violations(cx, violations, None, None);
501    }
502}
503
504impl ResourceTimingListener for EventSourceContext {
505    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
506        (InitiatorType::Other, self.event_source.root().url().clone())
507    }
508
509    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
510        self.event_source.root().global()
511    }
512}
513
514impl EventSource {
515    fn new_inherited(url: ServoUrl, with_credentials: bool) -> EventSource {
516        EventSource {
517            eventtarget: EventTarget::new_inherited(),
518            url,
519            request: DomRefCell::new(None),
520            last_event_id: DomRefCell::new(DOMString::new()),
521            reconnection_time: Cell::new(DEFAULT_RECONNECTION_TIME),
522            generation_id: Cell::new(GenerationId(0)),
523
524            ready_state: Cell::new(ReadyState::Connecting),
525            with_credentials,
526            droppable: DroppableEventSource::new(DomRefCell::new(Default::default())),
527        }
528    }
529
530    fn new(
531        cx: &mut JSContext,
532        global: &GlobalScope,
533        proto: Option<HandleObject>,
534        url: ServoUrl,
535        with_credentials: bool,
536    ) -> DomRoot<EventSource> {
537        reflect_weak_referenceable_dom_object_with_proto(
538            cx,
539            Rc::new(EventSource::new_inherited(url, with_credentials)),
540            global,
541            proto,
542        )
543    }
544
545    /// <https://html.spec.whatwg.org/multipage/#sse-processing-model:fail-the-connection-3>
546    pub(crate) fn cancel(&self) {
547        self.droppable.cancel();
548        self.fail_the_connection();
549    }
550
551    /// <https://html.spec.whatwg.org/multipage/#fail-the-connection>
552    pub(crate) fn fail_the_connection(&self) {
553        let global = self.global();
554        let event_source = Trusted::new(self);
555        global.task_manager().remote_event_task_source().queue(
556            task!(fail_the_event_source_connection: move |cx| {
557                let event_source = event_source.root();
558                if event_source.ready_state.get() != ReadyState::Closed {
559                    event_source.ready_state.set(ReadyState::Closed);
560                    event_source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
561                }
562            }),
563        );
564    }
565
566    pub(crate) fn request(&self) -> RequestBuilder {
567        self.request.borrow().clone().unwrap()
568    }
569
570    pub(crate) fn url(&self) -> &ServoUrl {
571        &self.url
572    }
573}
574
575impl EventSourceMethods<crate::DomTypeHolder> for EventSource {
576    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource>
577    fn Constructor(
578        cx: &mut JSContext,
579        global: &GlobalScope,
580        proto: Option<HandleObject>,
581        url: DOMString,
582        event_source_init: &EventSourceInit,
583    ) -> Fallible<DomRoot<EventSource>> {
584        // Step 2. Let settings be the relevant settings object for the `EventSource` constructor.
585        // Bindings pass that environment as `global`.
586        // Step 3. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings.
587        let url_record = match global.encoding_parse_a_url(&url.str()) {
588            Ok(u) => u,
589            // Step 4 If urlRecord is failure, then throw a "SyntaxError" DOMException.
590            Err(_) => return Err(Error::Syntax(None)),
591        };
592        // Step 1 Let ev be a new EventSource object.
593        let event_source = EventSource::new(
594            cx,
595            global,
596            proto,
597            // Step 5 Set ev's url to urlRecord.
598            url_record.clone(),
599            event_source_init.withCredentials,
600        );
601        global.track_event_source(&event_source);
602        let cors_attribute_state = if event_source_init.withCredentials {
603            // Step 7 If the value of eventSourceInitDict's withCredentials member is true,
604            // then set corsAttributeState to Use Credentials and set ev's withCredentials
605            // attribute to true.
606            CorsSettings::UseCredentials
607        } else {
608            // Step 6 Let corsAttributeState be Anonymous.
609            CorsSettings::Anonymous
610        };
611        // Step 8 Let request be the result of creating a potential-CORS request
612        // given urlRecord, the empty string, and corsAttributeState.
613        // Step 9. Set request's client to the environment settings object and other state;
614        // `with_global_scope` supplies client, origin, policy container, etc.
615        let mut request = create_a_potential_cors_request(
616            global.webview_id(),
617            url_record,
618            Destination::None,
619            Some(cors_attribute_state),
620            Some(true),
621            global.get_referrer(),
622        )
623        .with_global_scope(global);
624
625        // Step 10 User agents may set (`Accept`, `text/event-stream`) in request's header list.
626        // TODO(eijebong): Replace once typed headers allow it
627        request.headers.insert(
628            header::ACCEPT,
629            HeaderValue::from_static("text/event-stream"),
630        );
631        // Step 11 Set request's cache mode to "no-store".
632        request.cache_mode = CacheMode::NoStore;
633        // Step 13 Set ev's request to request.
634        *event_source.request.safe_borrow_mut(cx.no_gc()) = Some(request.clone());
635        // Step 14 Let processEventSourceEndOfBody given response res be the following step:
636        // if res is not a network error, then reestablish the connection.
637
638        event_source.droppable.set_controller(FetchController::new(
639            request.id,
640            false,
641            global.core_resource_thread(),
642        ));
643
644        let context = EventSourceContext {
645            decoder: UTF_8.new_decoder_with_bom_removal(),
646            event_source: Trusted::new(&event_source),
647            gen_id: event_source.generation_id.get(),
648            parser_state: ParserState::Eol,
649            field: String::new(),
650            value: String::new(),
651            origin: String::new(),
652
653            event_type: String::new(),
654            data: String::new(),
655            last_event_id: String::new(),
656        };
657
658        let task_source = global.task_manager().networking_task_source().into();
659        global.fetch(request, context, task_source);
660
661        // Step 16 Return ev.
662        Ok(event_source)
663    }
664
665    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onopen
666    event_handler!(open, GetOnopen, SetOnopen);
667
668    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onmessage
669    event_handler!(message, GetOnmessage, SetOnmessage);
670
671    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onerror
672    event_handler!(error, GetOnerror, SetOnerror);
673
674    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-url>
675    fn Url(&self) -> DOMString {
676        DOMString::from(self.url.as_str())
677    }
678
679    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-withcredentials>
680    fn WithCredentials(&self) -> bool {
681        self.with_credentials
682    }
683
684    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate>
685    fn ReadyState(&self) -> u16 {
686        self.ready_state.get() as u16
687    }
688
689    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-close>
690    fn Close(&self) {
691        let GenerationId(prev_id) = self.generation_id.get();
692        self.generation_id.set(GenerationId(prev_id + 1));
693        self.droppable.cancel();
694        self.ready_state.set(ReadyState::Closed);
695    }
696}
697
698#[derive(JSTraceable, MallocSizeOf)]
699pub(crate) struct EventSourceTimeoutCallback {
700    #[ignore_malloc_size_of = "Because it is non-owning"]
701    event_source: Trusted<EventSource>,
702    #[no_trace]
703    event_source_context: EventSourceContext,
704}
705
706impl EventSourceTimeoutCallback {
707    /// <https://html.spec.whatwg.org/multipage/#reestablish-the-connection>
708    pub(crate) fn invoke(self) {
709        let event_source = self.event_source.root();
710        let global = event_source.global();
711
712        // Step 5.1: If the EventSource object's readyState attribute is not set to CONNECTING, then return.
713        if event_source.ready_state.get() != ReadyState::Connecting {
714            return;
715        }
716
717        // Step 5.2: Let request be the EventSource object's request.
718        let mut request = event_source.request();
719
720        // Step 5.3: If the EventSource object's last event ID string is not the empty string, then:
721        //  - Let lastEventIDValue be the EventSource object's last event ID string, encoded as UTF-8.
722        //  - Set (`Last-Event-ID`, lastEventIDValue) in request's header list.
723        if !event_source.last_event_id.borrow().is_empty() &&
724            let Ok(header_value) =
725                HeaderValue::from_str(&String::from(event_source.last_event_id.borrow().clone()))
726        {
727            // TODO(eijebong): Change this once typed header support custom values
728            request
729                .headers
730                .insert(HeaderName::from_static("last-event-id"), header_value);
731        }
732
733        // Step 5.4: Fetch request and process the response obtained in this fashion, if
734        // any, as described earlier in this section.
735        let task_source = global.task_manager().networking_task_source().into();
736        global.fetch(request, self.event_source_context, task_source);
737    }
738}