use silex_dom::prelude::*;
use std::cell::RefCell;
use std::collections::HashSet;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::CssStyleSheet;
thread_local! {
pub(crate) static DOCUMENT_REGISTRY: RefCell<DocumentStyleRegistry> = RefCell::new(DocumentStyleRegistry::new());
}
pub struct StaticStyleRegistry {
injected_ids: HashSet<String>,
shared_sheet: Option<CssStyleSheet>,
}
impl StaticStyleRegistry {
pub(crate) fn with<R>(f: impl FnOnce(&mut Self) -> R) -> R {
thread_local! {
static INSTANCE: RefCell<StaticStyleRegistry> = RefCell::new(StaticStyleRegistry {
injected_ids: HashSet::new(),
shared_sheet: None,
});
}
INSTANCE.with(|i| f(&mut i.borrow_mut()))
}
pub fn inject(&mut self, id: &str, content: &str) {
if self.injected_ids.contains(id) {
return;
}
self.injected_ids.insert(id.to_string());
if let Some(sheet) = &self.shared_sheet {
let rules = split_rules(content);
for rule in rules {
if let Ok(rule_list) = sheet.css_rules() {
let _ = sheet.insert_rule_with_index(rule, rule_list.length());
}
}
} else {
let sheet = CssStyleSheet::new().expect("Failed to create CssStyleSheet");
let _ = sheet.replace_sync(content);
DOCUMENT_REGISTRY.with(|dr| dr.borrow_mut().set_static_sheet(sheet.clone()));
self.shared_sheet = Some(sheet);
}
}
}
pub fn split_rules(css: &str) -> Vec<&str> {
let mut rules = Vec::new();
let mut start = 0;
let mut depth = 0;
let mut in_quote = None;
let bytes = css.as_bytes();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'\\' => {
i += 1;
}
b'"' | b'\'' => {
let q = bytes[i];
if in_quote == Some(q) {
in_quote = None;
} else if in_quote.is_none() {
in_quote = Some(q);
}
}
b'{' if in_quote.is_none() => depth += 1,
b'}' if in_quote.is_none() && depth > 0 => {
depth -= 1;
if depth == 0 {
let rule = css[start..i + 1].trim();
if !rule.is_empty() {
rules.push(rule);
}
start = i + 1;
}
}
b';' if depth == 0 && in_quote.is_none() => {
let rule = css[start..i + 1].trim();
if !rule.is_empty() {
rules.push(rule);
}
start = i + 1;
}
_ => {}
}
i += 1;
}
let tail = css[start..].trim();
if !tail.is_empty() {
rules.push(tail);
}
rules
}
pub fn inject_style(id: &str, content: &str) {
StaticStyleRegistry::with(|r| r.inject(id, content));
}
pub(crate) struct DocumentStyleRegistry {
static_sheet: Option<CssStyleSheet>,
dynamic_sheets: Vec<CssStyleSheet>,
last_sync_ids: Vec<usize>,
is_pending: bool,
}
impl DocumentStyleRegistry {
fn new() -> Self {
Self {
static_sheet: None,
dynamic_sheets: Vec::new(),
last_sync_ids: Vec::new(),
is_pending: false,
}
}
pub fn set_static_sheet(&mut self, sheet: CssStyleSheet) {
self.static_sheet = Some(sheet);
self.sync();
}
pub fn add_sheet(&mut self, sheet: CssStyleSheet) {
self.dynamic_sheets.push(sheet);
self.sync();
}
pub fn remove_sheet(&mut self, sheet: &CssStyleSheet) {
let sheet_val: &JsValue = sheet.unchecked_ref();
self.dynamic_sheets.retain(|s| {
let s_val: &JsValue = s.unchecked_ref();
s_val != sheet_val
});
self.sync();
}
fn sync(&mut self) {
if self.is_pending {
return;
}
self.is_pending = true;
wasm_bindgen_futures::spawn_local(async {
DOCUMENT_REGISTRY.with(|dr| {
if let Ok(mut dr) = dr.try_borrow_mut() {
dr.perform_sync();
}
});
});
}
fn perform_sync(&mut self) {
self.is_pending = false;
let num_sheets = (self.static_sheet.is_some() as usize) + self.dynamic_sheets.len();
let mut current_ids = Vec::with_capacity(num_sheets);
if let Some(sheet) = &self.static_sheet {
current_ids.push(sheet.unchecked_ref::<JsValue>() as *const _ as usize);
}
for sheet in &self.dynamic_sheets {
current_ids.push(sheet.unchecked_ref::<JsValue>() as *const _ as usize);
}
if self.last_sync_ids == current_ids {
return;
}
let doc = document();
let mut new_list: Vec<JsValue> = Vec::with_capacity(num_sheets);
if let Some(sheet) = &self.static_sheet {
new_list.push(sheet.clone().unchecked_into());
}
for sheet in &self.dynamic_sheets {
new_list.push(sheet.clone().unchecked_into());
}
let arr: js_sys::Array = new_list.into_iter().collect();
doc.set_adopted_style_sheets(&arr);
self.last_sync_ids = current_ids;
}
}