futursolo / stylist-rs

A CSS-in-Rust styling solution for WebAssembly Applications
https://crates.io/crates/stylist
MIT License
370 stars 22 forks source link

Mount is wrong when used in iframes #11

Closed WorldSEnder closed 3 years ago

WorldSEnder commented 3 years ago

I want to use stylist for elements that are quarantined in an iframe. The problem with the approach taken with the current mount is then that the wrong document is used, i.e. the top-level document instead of the one of the nested iframe.

Further in the future, when server-side rendering is available in yew, yet another context - a server-side stylesheet container attached to the request - should be used when mounting an element.

Somehow, a Style must thus have a sense of in which context it is used and do the correct thing in each case. In material-ui this is done using a StyleSheetManager which is obtained from the context of a component, I believe falling back to document->head if none is found.

futursolo commented 3 years ago

There're a couple issues that prevents a Context or StyleSheetManager approach:

  1. There's no Context in Yew 0.18 (only agents).
  2. To use a Context, the API needs to be either a Component or a hook.

I think there're a couple potential solutions:

  1. StyleOptions / StyleBuilder (Works universally, but difficult / unergonomic to use)
  2. Set a global before your App renders (Either yew::agent or Arc<Mutex<>>, might need unsafe if storing web_sys::Element).
  3. Trying to detect if inside an iframe.
WorldSEnder commented 3 years ago

There're a couple issues that prevents a Context or StyleSheetManager approach:

1. There's no Context in Yew 0.18 (only agents).

2. To use a Context, the API needs to be either a Component or a hook.

I'd say that's a problem for the bindings, sure, but will be fixed in the upcoming version of yew. Inside stylist I'd prefer to see a binding agnostic solution (also so it's easier to adapt to potential server side rendering when that comes up). E.g: some kind StyleSheetManager trait coming from stylist. Implement it for a dummy struct GlobalDocumentManager that replicates the current behavior of mounting to window().document().head(). Add an additional mount(&self, &dyn StyleSheetManager) and forward to that in mount(&self). Up to debate: how to ensure that the same manager is passed to unmount and the interaction with unregister.

For the yew-bindings, there could be a impl StyleSheetManager for ComponentManager where ComponentManager holds a ComponentLink and the impl implements idea (3) of using the link to detect the iframe in which the component is rendered in (or fowards to the context whenever yew is updated).

I'm not sure Style itself should be the "smart" part that knows where to mount:

Let me know what you think :)

futursolo commented 3 years ago

I have added an experimental implementation of StyleManager in the manager branch. You can see the example below for usage.

https://github.com/futursolo/stylist-rs/blob/manager/examples/yew-shadow/src/main.rs

Let me know if it works.

futursolo commented 3 years ago
  • Creating a new Style is kinda expensive since it looks up and compares the Sheet in a global cache. Don't wanna do that too often
  • On the other hand Style already holds an Arc so that can be used as the identity later on instead of the Sheet
  • Multiple components want to use the same Style, but the place to mount is sensitive to that user

I am not sure if looking up cached Styles in the Registry is "expensive".

During the rewrite, I kept the StyleRegistry to reduce mounting (and parsing in the past). Looking up cached Styles in the Registry is a simple HashMap look up and a clone of Arc'ed reference, even with Rust's default Hashing algorithm being "slow", there should be no problem to facilitate millions of look ups per second.

I wrote some simple benchmarks, which is now available on the master branch, on my machine, the benchmark gives me the following result:

Benchmark Result
Parse Simple (100,000 iterations) 150.000ms
Parse Complex (10,000 iterations) 162.000ms
Cached Lookup (1,000,000 iterations) 371.000ms
Mounting (1,000 iterations) 177.000ms

With the benchmarks above and my comments in #14, I think we have established that:

  1. Runtime cost of parsing and Cache lookup is negligible comparing to mounting.
  2. Having multiple mounting points to per Style in the same Registry introduces complications to StyleRegistry.

If you have a case where the cache is slow, please let me know.