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::request::{CacheMode, CorsSettings, Destination, RequestBuilder, RequestId};
23use net_traits::{FetchMetadata, FilteredMetadata, NetworkError, ResourceFetchTiming};
24use script_bindings::cell::DomRefCell;
25use script_bindings::reflector::reflect_weak_referenceable_dom_object_with_proto;
26use servo_url::ServoUrl;
27use stylo_atoms::Atom;
28
29use crate::dom::bindings::codegen::Bindings::EventSourceBinding::{
30    EventSourceInit, EventSourceMethods,
31};
32use crate::dom::bindings::error::{Error, Fallible};
33use crate::dom::bindings::inheritance::Castable;
34use crate::dom::bindings::refcounted::Trusted;
35use crate::dom::bindings::reflector::DomGlobal;
36use crate::dom::bindings::root::DomRoot;
37use crate::dom::bindings::str::DOMString;
38use crate::dom::csp::{GlobalCspReporting, Violation};
39use crate::dom::event::Event;
40use crate::dom::eventtarget::EventTarget;
41use crate::dom::globalscope::GlobalScope;
42use crate::dom::messageevent::MessageEvent;
43use crate::dom::performance::performanceresourcetiming::InitiatorType;
44use crate::event_loop::timers::OneshotTimerCallback;
45use crate::fetch::fetch::{
46    FetchCanceller, RequestWithGlobalScope, create_a_potential_cors_request,
47};
48use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
49use crate::realms::enter_auto_realm;
50
51const DEFAULT_RECONNECTION_TIME: Duration = Duration::from_millis(5000);
52
53#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
54struct GenerationId(u32);
55
56#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
57/// <https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate>
58enum ReadyState {
59    Connecting = 0,
60    Open = 1,
61    Closed = 2,
62}
63
64#[derive(JSTraceable, MallocSizeOf)]
65struct DroppableEventSource {
66    canceller: DomRefCell<FetchCanceller>,
67}
68
69impl DroppableEventSource {
70    pub(crate) fn new(canceller: DomRefCell<FetchCanceller>) -> Self {
71        DroppableEventSource { canceller }
72    }
73
74    pub(crate) fn cancel(&self) {
75        self.canceller.borrow_mut().abort();
76    }
77
78    pub(crate) fn set_canceller(&self, data: FetchCanceller) {
79        *self.canceller.borrow_mut() = data;
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(fm) => {
401                let meta = match fm {
402                    FetchMetadata::Unfiltered(m) => m,
403                    FetchMetadata::Filtered { unsafe_, filtered } => match filtered {
404                        FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_) => {
405                            return self.fail_the_connection();
406                        },
407                        _ => unsafe_,
408                    },
409                };
410                // Step 15.3 if res's status is not 200, or if res's `Content-Type` is not
411                // `text/event-stream`, then fail the connection.
412                if meta.status.code() != StatusCode::OK {
413                    return self.fail_the_connection();
414                }
415                let mime = match meta.content_type {
416                    None => return self.fail_the_connection(),
417                    Some(ct) => <ContentType as Into<Mime>>::into(ct.into_inner()),
418                };
419                if (mime.type_(), mime.subtype()) != (mime::TEXT, mime::EVENT_STREAM) {
420                    return self.fail_the_connection();
421                }
422                self.origin = meta.final_url.origin().ascii_serialization().into_owned();
423                // Step 15.4 announce the connection and interpret res's body line by line.
424                self.announce_the_connection();
425            },
426            Err(error) => {
427                // Step 15.2 if res is a network error, then reestablish the connection, unless
428                // the user agent knows that to be futile, in which case the user agent may
429                // fail the connection.
430                if error.is_permanent_failure() {
431                    self.fail_the_connection()
432                } else {
433                    self.reestablish_the_connection()
434                }
435            },
436        }
437    }
438
439    fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Bytes) {
440        let mut output = String::with_capacity(chunk.len());
441        let mut input = &chunk[..];
442
443        loop {
444            if input.is_empty() {
445                return;
446            }
447            let (result, bytes_read) =
448                self.decoder
449                    .decode_to_string_without_replacement(input, &mut output, false);
450            match result {
451                encoding_rs::DecoderResult::InputEmpty => {
452                    self.parse(cx, output.chars());
453                    return;
454                },
455                encoding_rs::DecoderResult::Malformed(_, _) => {
456                    self.parse(cx, output.chars());
457                    self.parse(cx, "\u{FFFD}".chars());
458                    output.clear();
459                    input = &input[bytes_read..];
460                },
461                encoding_rs::DecoderResult::OutputFull => {
462                    self.parse(cx, output.chars());
463                    output.clear();
464                    input = &input[bytes_read..];
465                },
466            }
467        }
468    }
469
470    fn process_response_eof(
471        mut self,
472        cx: &mut JSContext,
473        _: RequestId,
474        response: Result<(), NetworkError>,
475        timing: ResourceFetchTiming,
476    ) {
477        let mut output = String::new();
478        let (result, _) = self
479            .decoder
480            .decode_to_string_without_replacement(&[], &mut output, true);
481        if !output.is_empty() {
482            self.parse(cx, "\u{FFFD}".chars());
483        }
484        if matches!(result, encoding_rs::DecoderResult::Malformed(_, _)) {
485            self.parse(cx, "\u{FFFD}".chars());
486        }
487        if response.is_ok() {
488            self.reestablish_the_connection();
489        }
490
491        network_listener::submit_timing(cx, &self, &response, &timing);
492    }
493
494    fn process_csp_violations(
495        &mut self,
496        cx: &mut JSContext,
497        _request_id: RequestId,
498        violations: Vec<Violation>,
499    ) {
500        let global = &self.resource_timing_global();
501        global.report_csp_violations(cx, violations, None, None);
502    }
503}
504
505impl ResourceTimingListener for EventSourceContext {
506    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
507        (InitiatorType::Other, self.event_source.root().url().clone())
508    }
509
510    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
511        self.event_source.root().global()
512    }
513}
514
515impl EventSource {
516    fn new_inherited(url: ServoUrl, with_credentials: bool) -> EventSource {
517        EventSource {
518            eventtarget: EventTarget::new_inherited(),
519            url,
520            request: DomRefCell::new(None),
521            last_event_id: DomRefCell::new(DOMString::new()),
522            reconnection_time: Cell::new(DEFAULT_RECONNECTION_TIME),
523            generation_id: Cell::new(GenerationId(0)),
524
525            ready_state: Cell::new(ReadyState::Connecting),
526            with_credentials,
527            droppable: DroppableEventSource::new(DomRefCell::new(Default::default())),
528        }
529    }
530
531    fn new(
532        cx: &mut JSContext,
533        global: &GlobalScope,
534        proto: Option<HandleObject>,
535        url: ServoUrl,
536        with_credentials: bool,
537    ) -> DomRoot<EventSource> {
538        reflect_weak_referenceable_dom_object_with_proto(
539            cx,
540            Rc::new(EventSource::new_inherited(url, with_credentials)),
541            global,
542            proto,
543        )
544    }
545
546    // https://html.spec.whatwg.org/multipage/#sse-processing-model:fail-the-connection-3
547    pub(crate) fn cancel(&self) {
548        self.droppable.cancel();
549        self.fail_the_connection();
550    }
551
552    /// <https://html.spec.whatwg.org/multipage/#fail-the-connection>
553    pub(crate) fn fail_the_connection(&self) {
554        let global = self.global();
555        let event_source = Trusted::new(self);
556        global.task_manager().remote_event_task_source().queue(
557            task!(fail_the_event_source_connection: move |cx| {
558                let event_source = event_source.root();
559                if event_source.ready_state.get() != ReadyState::Closed {
560                    event_source.ready_state.set(ReadyState::Closed);
561                    event_source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
562                }
563            }),
564        );
565    }
566
567    pub(crate) fn request(&self) -> RequestBuilder {
568        self.request.borrow().clone().unwrap()
569    }
570
571    pub(crate) fn url(&self) -> &ServoUrl {
572        &self.url
573    }
574}
575
576impl EventSourceMethods<crate::DomTypeHolder> for EventSource {
577    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource>
578    fn Constructor(
579        cx: &mut JSContext,
580        global: &GlobalScope,
581        proto: Option<HandleObject>,
582        url: DOMString,
583        event_source_init: &EventSourceInit,
584    ) -> Fallible<DomRoot<EventSource>> {
585        // Step 2. Let settings be the relevant settings object for the `EventSource` constructor.
586        // Bindings pass that environment as `global`.
587        // Step 3. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings.
588        let url_record = match global.encoding_parse_a_url(&url.str()) {
589            Ok(u) => u,
590            // Step 4 If urlRecord is failure, then throw a "SyntaxError" DOMException.
591            Err(_) => return Err(Error::Syntax(None)),
592        };
593        // Step 1 Let ev be a new EventSource object.
594        let event_source = EventSource::new(
595            cx,
596            global,
597            proto,
598            // Step 5 Set ev's url to urlRecord.
599            url_record.clone(),
600            event_source_init.withCredentials,
601        );
602        global.track_event_source(&event_source);
603        let cors_attribute_state = if event_source_init.withCredentials {
604            // Step 7 If the value of eventSourceInitDict's withCredentials member is true,
605            // then set corsAttributeState to Use Credentials and set ev's withCredentials
606            // attribute to true.
607            CorsSettings::UseCredentials
608        } else {
609            // Step 6 Let corsAttributeState be Anonymous.
610            CorsSettings::Anonymous
611        };
612        // Step 8 Let request be the result of creating a potential-CORS request
613        // given urlRecord, the empty string, and corsAttributeState.
614        // Step 9. Set request's client to the environment settings object and other state;
615        // `with_global_scope` supplies client, origin, policy container, etc.
616        let mut request = create_a_potential_cors_request(
617            global.webview_id(),
618            url_record,
619            Destination::None,
620            Some(cors_attribute_state),
621            Some(true),
622            global.get_referrer(),
623        )
624        .with_global_scope(global);
625
626        // Step 10 User agents may set (`Accept`, `text/event-stream`) in request's header list.
627        // TODO(eijebong): Replace once typed headers allow it
628        request.headers.insert(
629            header::ACCEPT,
630            HeaderValue::from_static("text/event-stream"),
631        );
632        // Step 11 Set request's cache mode to "no-store".
633        request.cache_mode = CacheMode::NoStore;
634        // Step 13 Set ev's request to request.
635        *event_source.request.safe_borrow_mut(cx.no_gc()) = Some(request.clone());
636        // Step 14 Let processEventSourceEndOfBody given response res be the following step:
637        // if res is not a network error, then reestablish the connection.
638
639        event_source.droppable.set_canceller(FetchCanceller::new(
640            request.id,
641            false,
642            global.core_resource_thread(),
643        ));
644
645        let context = EventSourceContext {
646            decoder: UTF_8.new_decoder_with_bom_removal(),
647            event_source: Trusted::new(&event_source),
648            gen_id: event_source.generation_id.get(),
649            parser_state: ParserState::Eol,
650            field: String::new(),
651            value: String::new(),
652            origin: String::new(),
653
654            event_type: String::new(),
655            data: String::new(),
656            last_event_id: String::new(),
657        };
658
659        let task_source = global.task_manager().networking_task_source().into();
660        global.fetch(request, context, task_source);
661
662        // Step 16 Return ev.
663        Ok(event_source)
664    }
665
666    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onopen
667    event_handler!(open, GetOnopen, SetOnopen);
668
669    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onmessage
670    event_handler!(message, GetOnmessage, SetOnmessage);
671
672    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onerror
673    event_handler!(error, GetOnerror, SetOnerror);
674
675    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-url>
676    fn Url(&self) -> DOMString {
677        DOMString::from(self.url.as_str())
678    }
679
680    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-withcredentials>
681    fn WithCredentials(&self) -> bool {
682        self.with_credentials
683    }
684
685    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate>
686    fn ReadyState(&self) -> u16 {
687        self.ready_state.get() as u16
688    }
689
690    /// <https://html.spec.whatwg.org/multipage/#dom-eventsource-close>
691    fn Close(&self) {
692        let GenerationId(prev_id) = self.generation_id.get();
693        self.generation_id.set(GenerationId(prev_id + 1));
694        self.droppable.cancel();
695        self.ready_state.set(ReadyState::Closed);
696    }
697}
698
699#[derive(JSTraceable, MallocSizeOf)]
700pub(crate) struct EventSourceTimeoutCallback {
701    #[ignore_malloc_size_of = "Because it is non-owning"]
702    event_source: Trusted<EventSource>,
703    #[no_trace]
704    event_source_context: EventSourceContext,
705}
706
707impl EventSourceTimeoutCallback {
708    /// <https://html.spec.whatwg.org/multipage/#reestablish-the-connection>
709    pub(crate) fn invoke(self) {
710        let event_source = self.event_source.root();
711        let global = event_source.global();
712
713        // Step 5.1: If the EventSource object's readyState attribute is not set to CONNECTING, then return.
714        if event_source.ready_state.get() != ReadyState::Connecting {
715            return;
716        }
717
718        // Step 5.2: Let request be the EventSource object's request.
719        let mut request = event_source.request();
720
721        // Step 5.3: If the EventSource object's last event ID string is not the empty string, then:
722        //  - Let lastEventIDValue be the EventSource object's last event ID string, encoded as UTF-8.
723        //  - Set (`Last-Event-ID`, lastEventIDValue) in request's header list.
724        if !event_source.last_event_id.borrow().is_empty() &&
725            let Ok(header_value) =
726                HeaderValue::from_str(&String::from(event_source.last_event_id.borrow().clone()))
727        {
728            // TODO(eijebong): Change this once typed header support custom values
729            request
730                .headers
731                .insert(HeaderName::from_static("last-event-id"), header_value);
732        }
733
734        // Step 5.4: Fetch request and process the response obtained in this fashion, if
735        // any, as described earlier in this section.
736        let task_source = global.task_manager().networking_task_source().into();
737        global.fetch(request, self.event_source_context, task_source);
738    }
739}