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