Skip to main content

script/css/
stylesheet_loader.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, expect(crown::jscontext_first_arg))]
6
7use std::io::{Read, Seek, Write};
8use std::sync::atomic::{AtomicUsize, Ordering};
9
10use bytes::{Bytes, BytesMut};
11use cssparser::SourceLocation;
12use encoding_rs::UTF_8;
13use js::context::JSContext;
14use net_traits::mime_classifier::MimeClassifier;
15use net_traits::request::{CorsSettings, Destination, RequestId};
16use net_traits::{
17    FetchMetadata, FilteredMetadata, LoadContext, Metadata, NetworkError, ReferrerPolicy,
18    ResourceFetchTiming,
19};
20use servo_arc::Arc;
21use servo_config::pref;
22use servo_url::ServoUrl;
23use style::context::QuirksMode;
24use style::global_style_data::STYLE_THREAD_POOL;
25use style::media_queries::MediaList;
26use style::shared_lock::{Locked, SharedRwLock};
27use style::stylesheets::import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition};
28use style::stylesheets::{
29    ImportRule, Origin, Stylesheet, StylesheetLoader as StyleStylesheetLoader, UrlExtraData,
30};
31use style::values::CssUrl;
32
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::csp::{GlobalCspReporting, Violation};
38use crate::dom::document::Document;
39use crate::dom::element::Element;
40use crate::dom::eventtarget::EventTarget;
41use crate::dom::globalscope::GlobalScope;
42use crate::dom::html::htmlelement::HTMLElement;
43use crate::dom::html::htmllinkelement::{HTMLLinkElement, RequestGenerationId};
44use crate::dom::node::NodeTraits;
45use crate::dom::performance::performanceresourcetiming::InitiatorType;
46use crate::dom::shadowroot::shadowroot::ShadowRoot;
47use crate::dom::window::CSSErrorReporter;
48use crate::event_loop::document_loader::LoadType;
49use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
50use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
51use crate::tasks::task_source::SendableTaskSource;
52use crate::unminify::{
53    BeautifyFileType, create_output_file, create_temp_files, execute_js_beautify,
54};
55
56/// An struct which is used to uniquely identify a [`StylesheetContext`] for
57/// tracking the set of script-blocking stylesheets.
58#[derive(Clone, Copy, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
59pub(crate) struct StylesheetContextId(usize);
60
61impl StylesheetContextId {
62    fn next() -> Self {
63        static NEXT_STYLESHEET_CONTEXT_INDEX: AtomicUsize = AtomicUsize::new(0);
64        Self(NEXT_STYLESHEET_CONTEXT_INDEX.fetch_add(1, Ordering::Relaxed))
65    }
66}
67
68pub(crate) trait StylesheetOwner {
69    /// Returns whether this element was inserted by the parser (i.e., it should
70    /// trigger a document-load-blocking load).
71    fn parser_inserted(&self) -> bool;
72
73    /// <https://html.spec.whatwg.org/multipage/#potentially-render-blocking>
74    fn potentially_render_blocking(&self) -> bool;
75
76    /// Which referrer policy should loads triggered by this owner follow
77    fn referrer_policy(&self, cx: &mut JSContext) -> ReferrerPolicy;
78
79    /// Notes that a new load is pending to finish.
80    fn increment_pending_loads_count(&self);
81
82    /// Returns None if there are still pending loads, or whether any load has
83    /// failed since the loads started.
84    fn load_finished(&self, successful: bool) -> Option<bool>;
85
86    /// Sets origin_clean flag.
87    fn set_origin_clean(&self, cx: &mut JSContext, origin_clean: bool);
88}
89
90pub(crate) enum StylesheetContextSource {
91    LinkElement,
92    Import(Arc<Locked<ImportRule>>),
93}
94
95/// The context required for asynchronously loading an external stylesheet.
96struct StylesheetContext {
97    /// The id associated with this [`StylesheetContext`]. This is used to uniquely identify
98    /// it in the `Document`'s script-blocking stylesheet set.
99    id: StylesheetContextId,
100    /// The element that initiated the request.
101    element: Trusted<HTMLElement>,
102    source: StylesheetContextSource,
103    media: Arc<Locked<MediaList>>,
104    url: ServoUrl,
105    metadata: Option<Metadata>,
106    /// The response body received to date.
107    data: BytesMut,
108    /// The node document for elem when the load was initiated.
109    document: Trusted<Document>,
110    shadow_root: Option<Trusted<ShadowRoot>>,
111    origin_clean: bool,
112    /// A token which must match the generation id of the `HTMLLinkElement` for it to load the stylesheet.
113    /// This is ignored for `HTMLStyleElement` and imports.
114    request_generation_id: Option<RequestGenerationId>,
115    /// <https://html.spec.whatwg.org/multipage/#contributes-a-script-blocking-style-sheet>
116    is_script_blocking: bool,
117    /// <https://html.spec.whatwg.org/multipage/#render-blocking>
118    is_render_blocking: bool,
119}
120
121impl StylesheetContext {
122    fn unminify_css(&mut self, file_url: ServoUrl) {
123        let Some(unminified_dir) = self.document.root().window().unminified_css_dir() else {
124            return;
125        };
126
127        let mut style_content = std::mem::take(&mut self.data).to_vec();
128        if let Some((input, mut output)) = create_temp_files() &&
129            execute_js_beautify(
130                input.path(),
131                output.try_clone().unwrap(),
132                BeautifyFileType::Css,
133            )
134        {
135            output.seek(std::io::SeekFrom::Start(0)).unwrap();
136            output.read_to_end(&mut style_content).unwrap();
137        }
138        match create_output_file(unminified_dir, &file_url, None) {
139            Ok(mut file) => {
140                file.write_all(&style_content).unwrap();
141            },
142            Err(why) => {
143                log::warn!("Could not store script {:?}", why);
144            },
145        }
146
147        self.data = Bytes::copy_from_slice(&style_content)
148            .try_into_mut()
149            .unwrap();
150    }
151
152    fn empty_stylesheet(&self, document: &Document) -> Arc<Stylesheet> {
153        let shared_lock = document.style_shared_author_lock().clone();
154        let quirks_mode = document.quirks_mode();
155
156        Arc::new(Stylesheet::from_bytes(
157            &[],
158            UrlExtraData(self.url.get_arc()),
159            None,
160            None,
161            Origin::Author,
162            self.media.clone(),
163            shared_lock,
164            None,
165            None,
166            quirks_mode,
167        ))
168    }
169
170    fn parse(
171        &self,
172        quirks_mode: QuirksMode,
173        shared_lock: SharedRwLock,
174        css_error_reporter: &CSSErrorReporter,
175        loader: ElementStylesheetLoader<'_>,
176    ) -> Arc<Stylesheet> {
177        let metadata = self
178            .metadata
179            .as_ref()
180            .expect("Should never call parse without metadata.");
181
182        let _span = profile_traits::trace_span!("ParseStylesheet").entered();
183        Arc::new(Stylesheet::from_bytes(
184            &self.data,
185            UrlExtraData(metadata.final_url.get_arc()),
186            metadata.charset.as_deref(),
187            // The CSS environment encoding is the result of running the following steps: [CSSSYNTAX]
188            // If el has a charset attribute, get an encoding from that attribute's value. If that succeeds, return the resulting encoding. [ENCODING]
189            // Otherwise, return the document's character encoding. [DOM]
190            //
191            // TODO: Need to implement encoding http://dev.w3.org/csswg/css-syntax/#environment-encoding
192            Some(UTF_8),
193            Origin::Author,
194            self.media.clone(),
195            shared_lock,
196            Some(&loader),
197            Some(css_error_reporter),
198            quirks_mode,
199        ))
200    }
201
202    fn contributes_to_the_styling_processing_model(&self, element: &HTMLElement) -> bool {
203        if !element.upcast::<Element>().is_connected() {
204            return false;
205        }
206
207        // Whether or not this `StylesheetContext` is for a `<link>` element that comes
208        // from a previous generation. This prevents processing of earlier stylsheet URLs
209        // when the URL has changed.
210        //
211        // TODO(mrobinson): Shouldn't we also exit early if this is an import that was originally
212        // imported from a `<link>` element that has advanced a generation as well?
213        if !matches!(&self.source, StylesheetContextSource::LinkElement) {
214            return true;
215        }
216        let link = element.downcast::<HTMLLinkElement>().unwrap();
217        self.request_generation_id
218            .is_none_or(|generation| generation == link.get_request_generation_id())
219    }
220
221    /// <https://html.spec.whatwg.org/multipage/#contributes-a-script-blocking-style-sheet>
222    fn contributes_a_script_blocking_style_sheet(
223        &self,
224        element: &HTMLElement,
225        owner: &dyn StylesheetOwner,
226        document: &Document,
227    ) -> bool {
228        // el was created by that Document's parser.
229        owner.parser_inserted()
230        // el is either a style element or a link element that was an external resource link that
231        // contributes to the styling processing model when the el was created by the parser.
232        && element.downcast::<HTMLLinkElement>().is_none_or(|link|
233            self.contributes_to_the_styling_processing_model(element)
234            // el's style sheet was enabled when the element was created by the parser.
235            && !link.is_effectively_disabled()
236        )
237        // el's media attribute's value matches the environment.
238        && element.media_attribute_matches_media_environment()
239        // The last time the event loop reached step 1, el's root was that Document.
240        && *element.owner_document() == *document
241        // The user agent hasn't given up on loading that particular style sheet yet.
242        // A user agent may give up on loading a style sheet at any time.
243        //
244        // This might happen when we time out a resource, but that happens in `fetch` instead
245    }
246
247    fn decrement_blockers_and_finish_load(
248        self,
249        document: &Document,
250        cx: &mut js::context::JSContext,
251    ) {
252        if self.is_script_blocking {
253            document.remove_script_blocking_stylesheet(self.id);
254        }
255
256        if self.is_render_blocking {
257            document.decrement_render_blocking_element_count();
258        }
259
260        document.finish_load(LoadType::Stylesheet(self.url), cx);
261    }
262
263    fn do_post_parse_tasks(
264        self,
265        success: bool,
266        stylesheet: Arc<Stylesheet>,
267        cx: &mut js::context::JSContext,
268    ) {
269        let element = self.element.root();
270        let document = self.document.root();
271        let owner = element
272            .upcast::<Element>()
273            .as_stylesheet_owner()
274            .expect("Stylesheet not loaded by <style> or <link> element!");
275
276        match &self.source {
277            // https://html.spec.whatwg.org/multipage/#link-type-stylesheet%3Aprocess-the-linked-resource
278            StylesheetContextSource::LinkElement => {
279                let link = element
280                    .downcast::<HTMLLinkElement>()
281                    .expect("Should be HTMLinkElement due to StylesheetContextSource");
282                // For failed requests, we should bail out if it is from a previous generation.
283                // Since we can reissue another failed request, which resets the pending load counter
284                // in a link element.
285                if self
286                    .request_generation_id
287                    .is_some_and(|generation| generation != link.get_request_generation_id())
288                {
289                    self.decrement_blockers_and_finish_load(&document, cx);
290                    return;
291                }
292                // https://html.spec.whatwg.org/multipage/#link-type-stylesheet
293                // > When the disabled attribute of a link element with a stylesheet keyword is set,
294                // > disable the associated CSS style sheet.
295                if link.is_effectively_disabled() {
296                    stylesheet.set_disabled(true);
297                }
298                // Step 3. If el has an associated CSS style sheet, remove the CSS style sheet.
299                // Step 4. If success is true, then:
300                // Step 4.1. Create a CSS style sheet with the following properties:
301                //
302                // Note that even in the failure case, we should create an empty stylesheet.
303                // That's why `set_stylesheet` also removes the previous stylesheet
304                link.set_stylesheet(cx.no_gc(), stylesheet);
305            },
306            StylesheetContextSource::Import(import_rule) => {
307                let mut guard = document.style_shared_author_lock().write();
308                import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
309            },
310        }
311
312        if let Some(ref shadow_root) = self.shadow_root {
313            shadow_root.root().invalidate_stylesheets(cx.no_gc());
314        } else {
315            document.invalidate_stylesheets(cx.no_gc());
316        }
317        owner.set_origin_clean(cx, self.origin_clean);
318
319        // Remaining steps are a combination of
320        // https://html.spec.whatwg.org/multipage/#link-type-stylesheet%3Aprocess-the-linked-resource
321        // and https://html.spec.whatwg.org/multipage/#the-style-element%3Acritical-subresources
322
323        // Step 4.2. Fire an event named load at el.
324        // Step 5. Otherwise, fire an event named error at el.
325        if let Some(any_failed) = owner.load_finished(success) {
326            // Only fire an event if we have no more pending events
327            // (in which case `owner.load_finished` would return None)
328            let event = match any_failed {
329                true => atom!("error"),
330                false => atom!("load"),
331            };
332            element.upcast::<EventTarget>().fire_event(cx, event);
333        }
334        // Regardless if there are other pending events, we need to unblock
335        // rendering for this particular request and signal that the load has finished
336
337        // Step 6. If el contributes a script-blocking style sheet, then:
338        // Step 7. Unblock rendering on el.
339        self.decrement_blockers_and_finish_load(&document, cx);
340    }
341}
342
343impl FetchResponseListener for StylesheetContext {
344    fn process_request_body(&mut self, _: RequestId) {}
345
346    fn process_response(
347        &mut self,
348        _: &mut js::context::JSContext,
349        _: RequestId,
350        metadata: Result<FetchMetadata, NetworkError>,
351    ) {
352        if let Ok(FetchMetadata::Filtered {
353            filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
354            ..
355        }) = metadata
356        {
357            self.origin_clean = false;
358        }
359
360        self.metadata = metadata.ok().map(Into::into);
361    }
362
363    fn process_response_chunk(
364        &mut self,
365        _: &mut js::context::JSContext,
366        _: RequestId,
367        payload: Bytes,
368    ) {
369        self.data.extend_from_slice(&payload);
370    }
371
372    fn process_response_eof(
373        mut self,
374        cx: &mut js::context::JSContext,
375        _: RequestId,
376        status: Result<(), NetworkError>,
377        timing: ResourceFetchTiming,
378    ) {
379        network_listener::submit_timing(cx, &self, &status, &timing);
380
381        let document = self.document.root();
382        let Some(metadata) = self.metadata.as_ref() else {
383            let empty_stylesheet = self.empty_stylesheet(&document);
384            self.do_post_parse_tasks(false, empty_stylesheet, cx);
385            return;
386        };
387
388        let element = self.element.root();
389
390        // https://html.spec.whatwg.org/multipage/#link-type-stylesheet:process-the-linked-resource
391        if element.is::<HTMLLinkElement>() {
392            // Step 1. If the resource's Content-Type metadata is not text/css, then set success to false.
393            let is_css = MimeClassifier::is_css(
394                &metadata.resource_content_type_metadata(LoadContext::Style, &self.data),
395            ) || (
396                // From <https://html.spec.whatwg.org/multipage/#link-type-stylesheet>:
397                // > Quirk: If the document has been set to quirks mode, has the same origin as
398                // > the URL of the external resource, and the Content-Type metadata of the
399                // > external resource is not a supported style sheet type, the user agent must
400                // > instead assume it to be text/css.
401                document.quirks_mode() == QuirksMode::Quirks &&
402                    document.origin().immutable().clone() == metadata.final_url.origin()
403            );
404
405            if !is_css {
406                let empty_stylesheet = self.empty_stylesheet(&document);
407                self.do_post_parse_tasks(false, empty_stylesheet, cx);
408                return;
409            }
410
411            // Step 2. If el no longer creates an external resource link that contributes to the styling processing model,
412            // or if, since the resource in question was fetched, it has become appropriate to fetch it again, then:
413            if !self.contributes_to_the_styling_processing_model(&element) {
414                // Step 2.1. Remove el from el's node document's script-blocking style sheet set.
415                self.decrement_blockers_and_finish_load(&document, cx);
416                // Step 2.2. Return.
417                return;
418            }
419        }
420
421        if metadata.status != http::StatusCode::OK {
422            let empty_stylesheet = self.empty_stylesheet(&document);
423            self.do_post_parse_tasks(false, empty_stylesheet, cx);
424            return;
425        }
426
427        self.unminify_css(metadata.final_url.clone());
428
429        let loader = if pref!(dom_parallel_css_parsing_enabled) {
430            ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader::new(&element))
431        } else {
432            ElementStylesheetLoader::Synchronous { element: &element }
433        };
434        loader.parse(self, &element, &document, cx);
435    }
436
437    fn process_csp_violations(
438        &mut self,
439        cx: &mut js::context::JSContext,
440        _request_id: RequestId,
441        violations: Vec<Violation>,
442    ) {
443        let global = &self.resource_timing_global();
444        global.report_csp_violations(cx, violations, None, None);
445    }
446
447    fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
448        self.data.reserve(size.saturating_sub(self.data.len()));
449    }
450}
451
452impl ResourceTimingListener for StylesheetContext {
453    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
454        let initiator_type = InitiatorType::LocalName(
455            self.element
456                .root()
457                .upcast::<Element>()
458                .local_name()
459                .to_string(),
460        );
461        (initiator_type, self.url.clone())
462    }
463
464    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
465        self.element.root().owner_document().global()
466    }
467}
468
469pub(crate) enum ElementStylesheetLoader<'a> {
470    Synchronous { element: &'a HTMLElement },
471    Asynchronous(AsynchronousStylesheetLoader),
472}
473
474impl<'a> ElementStylesheetLoader<'a> {
475    pub(crate) fn new(element: &'a HTMLElement) -> Self {
476        ElementStylesheetLoader::Synchronous { element }
477    }
478}
479
480impl ElementStylesheetLoader<'_> {
481    pub(crate) fn load_with_element(
482        cx: &mut JSContext,
483        element: &HTMLElement,
484        source: StylesheetContextSource,
485        media: Arc<Locked<MediaList>>,
486        url: ServoUrl,
487        cors_setting: Option<CorsSettings>,
488        integrity_metadata: String,
489    ) {
490        let document = element.owner_document();
491        let shadow_root = element
492            .containing_shadow_root()
493            .map(|shadow_root| Trusted::new(&*shadow_root));
494        let generation = element
495            .downcast::<HTMLLinkElement>()
496            .map(HTMLLinkElement::get_request_generation_id);
497        let mut context = StylesheetContext {
498            id: StylesheetContextId::next(),
499            element: Trusted::new(element),
500            source,
501            media,
502            url: url.clone(),
503            metadata: None,
504            data: BytesMut::new(),
505            document: Trusted::new(&*document),
506            shadow_root,
507            origin_clean: true,
508            request_generation_id: generation,
509            is_script_blocking: false,
510            is_render_blocking: false,
511        };
512
513        let owner = element
514            .upcast::<Element>()
515            .as_stylesheet_owner()
516            .expect("Stylesheet not loaded by <style> or <link> element!");
517        let referrer_policy = owner.referrer_policy(cx);
518        owner.increment_pending_loads_count();
519
520        // Final steps of https://html.spec.whatwg.org/multipage/#update-a-style-block
521        // and part of https://html.spec.whatwg.org/multipage/#link-type-stylesheet:linked-resource-fetch-setup-steps
522
523        // If element contributes a script-blocking style sheet, append element to its node document's script-blocking style sheet set.
524        context.is_script_blocking =
525            context.contributes_a_script_blocking_style_sheet(element, owner, &document);
526        if context.is_script_blocking {
527            document.add_script_blocking_stylesheet(context.id);
528        }
529
530        // If element's media attribute's value matches the environment and
531        // element is potentially render-blocking, then block rendering on element.
532        context.is_render_blocking = element.media_attribute_matches_media_environment() &&
533            owner.potentially_render_blocking() &&
534            document.allows_adding_render_blocking_elements();
535        if context.is_render_blocking {
536            document.increment_render_blocking_element_count();
537        }
538
539        // https://html.spec.whatwg.org/multipage/#default-fetch-and-process-the-linked-resource
540        let global = element.global();
541        let request = create_a_potential_cors_request(
542            Some(document.webview_id()),
543            url.clone(),
544            Destination::Style,
545            cors_setting,
546            None,
547            global.get_referrer(),
548        )
549        .with_global_scope(&global)
550        .referrer_policy(referrer_policy)
551        .integrity_metadata(integrity_metadata);
552
553        document.fetch_blocking(LoadType::Stylesheet(url), request, context);
554    }
555
556    fn parse(
557        self,
558        listener: StylesheetContext,
559        element: &HTMLElement,
560        document: &Document,
561        cx: &mut js::context::JSContext,
562    ) {
563        let shared_lock = document.style_shared_author_lock().clone();
564        let quirks_mode = document.quirks_mode();
565        let window = element.owner_window();
566
567        match self {
568            ElementStylesheetLoader::Synchronous { .. } => {
569                let stylesheet =
570                    listener.parse(quirks_mode, shared_lock, window.css_error_reporter(), self);
571                listener.do_post_parse_tasks(true, stylesheet, cx);
572            },
573            ElementStylesheetLoader::Asynchronous(asynchronous_loader) => {
574                let css_error_reporter = window.css_error_reporter().clone();
575                let task_source = window
576                    .as_global_scope()
577                    .task_manager()
578                    .networking_task_source()
579                    .to_sendable();
580
581                let parse_stylesheet = move || {
582                    let loader = ElementStylesheetLoader::Asynchronous(asynchronous_loader);
583                    let stylesheet =
584                        listener.parse(quirks_mode, shared_lock, &css_error_reporter, loader);
585
586                    task_source.queue(
587                        task!(finish_parsing_of_stylesheet_on_main_thread: move |cx| {
588                            listener.do_post_parse_tasks(true, stylesheet, cx);
589                        }),
590                    );
591                };
592
593                let thread_pool = STYLE_THREAD_POOL.pool();
594                if let Some(thread_pool) = thread_pool.as_ref() {
595                    thread_pool.spawn(parse_stylesheet);
596                } else {
597                    parse_stylesheet();
598                }
599            },
600        };
601    }
602}
603
604impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
605    /// Request a stylesheet after parsing a given `@import` rule, and return
606    /// the constructed `@import` rule.
607    fn request_stylesheet(
608        &self,
609        url: CssUrl,
610        source_location: SourceLocation,
611        lock: &SharedRwLock,
612        media: Arc<Locked<MediaList>>,
613        supports: Option<ImportSupportsCondition>,
614        layer: ImportLayer,
615    ) -> Arc<Locked<ImportRule>> {
616        // Ensure the supports conditions for this @import are true, if not, refuse to load
617        if supports.as_ref().is_some_and(|s| !s.enabled) {
618            return Arc::new(lock.wrap(ImportRule {
619                url,
620                stylesheet: ImportSheet::new_refused(),
621                supports,
622                layer,
623                source_location,
624            }));
625        }
626
627        let resolved_url = match url.url().cloned() {
628            Some(url) => url,
629            None => {
630                return Arc::new(lock.wrap(ImportRule {
631                    url,
632                    stylesheet: ImportSheet::new_refused(),
633                    supports,
634                    layer,
635                    source_location,
636                }));
637            },
638        };
639
640        let import_rule = Arc::new(lock.wrap(ImportRule {
641            url,
642            stylesheet: ImportSheet::new_pending(),
643            supports,
644            layer,
645            source_location,
646        }));
647
648        // TODO (mrnayak) : Whether we should use the original loader's CORS
649        // setting? Fix this when spec has more details.
650        let source = StylesheetContextSource::Import(import_rule.clone());
651
652        match self {
653            ElementStylesheetLoader::Synchronous { element } => {
654                // TODO: https://github.com/servo/servo/issues/44685
655                #[expect(unsafe_code)]
656                let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
657                Self::load_with_element(
658                    &mut cx,
659                    element,
660                    source,
661                    media,
662                    resolved_url.into(),
663                    None,
664                    String::new(),
665                );
666            },
667            ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader {
668                element,
669                task_source,
670            }) => {
671                let element = element.clone();
672                task_source.queue(task!(load_import_stylesheet_on_main_thread: move |cx| {
673                    Self::load_with_element(
674                        cx,
675                        &element.root(),
676                        source,
677                        media,
678                        resolved_url.into(),
679                        None,
680                        String::new(),
681                    );
682                }));
683            },
684        }
685
686        import_rule
687    }
688}
689
690pub(crate) struct AsynchronousStylesheetLoader {
691    element: Trusted<HTMLElement>,
692    task_source: SendableTaskSource,
693}
694
695impl AsynchronousStylesheetLoader {
696    pub(crate) fn new(element: &HTMLElement) -> Self {
697        let window = element.owner_window();
698        Self {
699            element: Trusted::new(element),
700            task_source: window
701                .as_global_scope()
702                .task_manager()
703                .networking_task_source()
704                .into(),
705        }
706    }
707}